diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..99981672f --- /dev/null +++ b/404.html @@ -0,0 +1,2019 @@ + + + + + + + + + + + + + + + + + + + EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 000000000..1cf13b9f9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.83f73b43.min.js b/assets/javascripts/bundle.83f73b43.min.js new file mode 100644 index 000000000..43d8b70f6 --- /dev/null +++ b/assets/javascripts/bundle.83f73b43.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Di=Object.getOwnPropertyDescriptor;var Vi=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,Ni=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var zi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Vi(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Di(t,n))||o.enumerable});return e};var Mt=(e,t,r)=>(r=e!=null?Wi(Ni(e)):{},zi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function te(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,te())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var qr=xr((hy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof It=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof It=="object"?It.ClipboardJS=r():t.ClipboardJS=r()})(It,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(V){try{return document.execCommand(V)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(V){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=V,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var D=f()(F);return u("copy"),F.remove(),D},te=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=te;function k(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(V)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,D=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:D});if(Y)return F==="cut"?y(Y):J(Y,{container:D})},qe=ft;function Fe(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(V)}function ki(V,A){if(!(V instanceof A))throw new TypeError("Cannot call a class as a function")}function no(V,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof D.action=="function"?D.action:this.defaultAction,this.target=typeof D.target=="function"?D.target:this.defaultTarget,this.text=typeof D.text=="function"?D.text:this.defaultText,this.container=Fe(D.container)==="object"?D.container:document.body}},{key:"listenClick",value:function(D){var Y=this;this.listener=c()(D,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(D){var Y=D.delegateTarget||D.currentTarget,$e=this.action(Y)||"copy",Dt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Dt?"success":"error",{action:$e,text:Dt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(D){return vr("action",D)}},{key:"defaultTarget",value:function(D){var Y=vr("target",D);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(D){return vr("text",D)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(D){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(D,Y)}},{key:"cut",value:function(D){return y(D)}},{key:"isSupported",value:function(){var D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof D=="string"?[D]:D,$e=!!document.queryCommandSupported;return Y.forEach(function(Dt){$e=$e&&!!document.queryCommandSupported(Dt)}),$e}}]),M}(s()),Ui=Fi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],N(i)),N(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Ue.EMPTY;function qt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t}(j);var To=function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t}(g);var _r=function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var At={now:function(){return(At.delegate||Date).now()},delegate:void 0};var Ct=function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=At);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(gt);var Lo=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(yt);var kr=new Lo(Oo);var Mo=function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(gt);var _o=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Yt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Yt(Hr(e))?e.pop():void 0}function Bt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Gt(e){return H(e==null?void 0:e.then)}function Jt(e){return H(e[bt])}function Xt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Zt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var er=Zi();function tr(e){return H(e==null?void 0:e[er])}function rr(e){return fo(this,arguments,function(){var r,o,n,i;return Nt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function or(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Jt(e))return ea(e);if(xt(e))return ta(e);if(Gt(e))return ra(e);if(Xt(e))return Ao(e);if(tr(e))return oa(e);if(or(e))return na(e)}throw Zt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?De(t):Qo(function(){return new ir}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},te=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;te(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(te,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(te,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function $t(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?Ht(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function Tt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?Tt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function St(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Ve(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>Ve(e)),Q(Ve(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ne(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return Ne(e).pipe(m(({y:r})=>{let o=ce(e),n=St(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function ze(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function Pt(e){let t=matchMedia(e);return ar(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function Nr(e,t){return e.pipe(v(r=>r?t():S))}function zr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return zr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return z([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(ee("size")),n=z([o,r]).pipe(m(()=>Ve(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),Ot=JSON.parse(Ca.textContent);Ot.base=`${new URL(Ot.base,ye())}`;function xe(){return Ot}function B(e){return Ot.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?Ot.translations[e].replace("#",t.toString()):Ot.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Rt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Mt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=z([et(e),$t(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(Ne),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>z([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(Ht(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());z([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>$t(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return z([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),z([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>z([tn(e),Ne(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Da(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Da(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Mt(Br());var Va=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function Na(e){return ge(e).pipe(m(({width:t})=>({scrollable:St(e).width>t})),ee("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Va++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),Na(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function za(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),za(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?Tt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Dn=x("table");function Vn(e){return e.replaceWith(Dn),Dn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function Nn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));z([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=Ve(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ne(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=St(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function zn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Vn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>Nn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?Ne(o):I({x:0,y:0}),i=O(et(t),$t(t)).pipe(K());return z([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=Ve(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Rt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=ze("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>z([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(ee("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),ee("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ee("bottom"))));return z([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Pt("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Mt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(ee("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),ee("pathname"),v(()=>e),ee("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(ee("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Mt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function jt(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),ze("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),z([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let s=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(s)||(s=[s]);e:for(let p of s)for(let c of n.aliases.concat(n.version))if(new RegExp(p,"i").test(c)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),ze("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(jt)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return z([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));z([t.pipe(Ae(jt)),r],(i,a)=>a).pipe(ee("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ee("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);ze("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(re(r),Wr(t.pipe(Ae(jt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Vr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return z([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=Ve(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),De({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),De({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),De({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ee("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(ee("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(ee("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),ee("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return z([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),ee("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){z([ze("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?Tt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ut=sn(),Lt=ln(Ut),to=an(),Oe=gn(),hr=Pt("(min-width: 960px)"),Mi=Pt("(min-width: 1220px)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ut,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ut,Lt).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),Ft=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Lt})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>zn(e,{viewport$:Oe,target$:Lt,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ut}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:Ft})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Nr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft})):Nr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ut;window.target$=Lt;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.83f73b43.min.js.map + diff --git a/assets/javascripts/bundle.83f73b43.min.js.map b/assets/javascripts/bundle.83f73b43.min.js.map new file mode 100644 index 000000000..fe920b7d6 --- /dev/null +++ b/assets/javascripts/bundle.83f73b43.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n *\n * @class BehaviorSubject\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an + +
+

The Kubernetes Cluster Autoscaler is a popular Cluster Autoscaling solution maintained by SIG Autoscaling. It is responsible for ensuring that your cluster has enough nodes to schedule your pods without wasting resources. It watches for pods that fail to schedule and for nodes that are underutilized. It then simulates the addition or removal of nodes before applying the change to your cluster. The AWS Cloud Provider implementation within Cluster Autoscaler controls the .DesiredReplicas field of your EC2 Auto Scaling Groups.

+

+

This guide will provide a mental model for configuring the Cluster Autoscaler and choosing the best set of tradeoffs to meet your organization’s requirements. While there is no single best configuration, there are a set of configuration options that enable you to trade off performance, scalability, cost, and availability. Additionally, this guide will provide tips and best practices for optimizing your configuration for AWS.

+

Glossary

+

The following terminology will be used frequently throughout this document. These terms can have broad meaning, but are limited to the definitions below for the purposes of this document.

+

Scalability refers to how well the Cluster Autoscaler performs as your Kubernetes Cluster increases in number of pods and nodes. As scalability limits are reached, the Cluster Autoscaler’s performance and functionality degrades. As the Cluster Autoscaler exceeds its scalability limits, it may no longer add or remove nodes in your cluster.

+

Performance refers to how quickly the Cluster Autoscaler is able to make and execute scaling decisions. A perfectly performing Cluster Autoscaler would instantly make a decision and trigger a scaling action in response to stimuli, such as a pod becoming unschedulable.

+

Availability means that pods can be scheduled quickly and without disruption. This includes when newly created pods need to be scheduled and when a scaled down node terminates any remaining pods scheduled to it.

+

Cost is determined by the decision behind scale out and scale in events. Resources are wasted if an existing node is underutilized or a new node is added that is too large for incoming pods. Depending on the use case, there can be costs associated with prematurely terminating pods due to an aggressive scale down decision.

+

Node Groups are an abstract Kubernetes concept for a group of nodes within a cluster. It is not a true Kubernetes resource, but exists as an abstraction in the Cluster Autoscaler, Cluster API, and other components. Nodes within a Node Group share properties like labels and taints, but may consist of multiple Availability Zones or Instance Types.

+

EC2 Auto Scaling Groups can be used as an implementation of Node Groups on EC2. EC2 Auto Scaling Groups are configured to launch instances that automatically join their Kubernetes Clusters and apply labels and taints to their corresponding Node resource in the Kubernetes API.

+

EC2 Managed Node Groups are another implementation of Node Groups on EC2. They abstract away the complexity manually configuring EC2 Autoscaling Scaling Groups and provide additional management features like node version upgrade and graceful node termination.

+

Operating the Cluster Autoscaler

+

The Cluster Autoscaler is typically installed as a Deployment in your cluster. It uses leader election to ensure high availability, but work is done by a single replica at a time. It is not horizontally scalable. For basic setups, the default it should work out of the box using the provided installation instructions, but there are a few things to keep in mind.

+

Ensure that:

+
    +
  • The Cluster Autoscaler’s version matches the Cluster’s Version. Cross version compatibility is not tested or supported.
  • +
  • Auto Discovery is enabled, unless you have specific advanced use cases that prevent use of this mode.
  • +
+

Employ least privileged access to the IAM role

+

When the Auto Discovery is used, we strongly recommend that you employ least privilege access by limiting Actions autoscaling:SetDesiredCapacity and autoscaling:TerminateInstanceInAutoScalingGroup to the Auto Scaling groups that are scoped to the current cluster.

+

This will prevents a Cluster Autoscaler running in one cluster from modifying nodegroups in a different cluster even if the --node-group-auto-discovery argument wasn't scoped down to the nodegroups of the cluster using tags (for example k8s.io/cluster-autoscaler/<cluster-name>).

+
{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [
+                "autoscaling:SetDesiredCapacity",
+                "autoscaling:TerminateInstanceInAutoScalingGroup"
+            ],
+            "Resource": "*",
+            "Condition": {
+                "StringEquals": {
+                    "aws:ResourceTag/k8s.io/cluster-autoscaler/enabled": "true",
+                    "aws:ResourceTag/k8s.io/cluster-autoscaler/<my-cluster>": "owned"
+                }
+            }
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "autoscaling:DescribeAutoScalingGroups",
+                "autoscaling:DescribeAutoScalingInstances",
+                "autoscaling:DescribeLaunchConfigurations",
+                "autoscaling:DescribeScalingActivities",
+                "autoscaling:DescribeTags",
+                "ec2:DescribeImages",
+                "ec2:DescribeInstanceTypes",
+                "ec2:DescribeLaunchTemplateVersions",
+                "ec2:GetInstanceTypesFromInstanceRequirements",
+                "eks:DescribeNodegroup"
+            ],
+            "Resource": "*"
+        }
+    ]
+}
+
+

Configuring your Node Groups

+

Effective autoscaling starts with correctly configuring a set of Node Groups for your cluster. Selecting the right set of Node Groups is key to maximizing availability and reducing cost across your workloads. AWS implements Node Groups using EC2 Auto Scaling Groups, which are flexible to a large number of use cases. However, the Cluster Autoscaler makes some assumptions about your Node Groups. Keeping your EC2 Auto Scaling Group configurations consistent with these assumptions will minimize undesired behavior.

+

Ensure that:

+
    +
  • Each Node in a Node Group has identical scheduling properties, such as Labels, Taints, and Resources.
  • +
  • For MixedInstancePolicies, the Instance Types must be of the same shape for CPU, Memory, and GPU
  • +
  • The first Instance Type specified in the policy will be used to simulate scheduling.
  • +
  • If your policy has additional Instance Types with more resources, resources may be wasted after scale out.
  • +
  • If your policy has additional Instance Types with less resources, pods may fail to schedule on the instances.
  • +
  • Node Groups with many nodes are preferred over many Node Groups with fewer nodes. This will have the biggest impact on scalability.
  • +
  • Wherever possible, prefer EC2 features when both systems provide support (e.g. Regions, MixedInstancePolicy)
  • +
+

Note: We recommend using EKS Managed Node Groups. Managed Node Groups come with powerful management features, including features for Cluster Autoscaler like automatic EC2 Auto Scaling Group discovery and graceful node termination.

+

Optimizing for Performance and Scalability

+

Understanding the autoscaling algorithm’s runtime complexity will help you tune the Cluster Autoscaler to continue operating smoothly in large clusters with greater than 1,000 nodes.

+

The primary knobs for tuning scalability of the Cluster Autoscaler are the resources provided to the process, the scan interval of the algorithm, and the number of Node Groups in the cluster. There are other factors involved in the true runtime complexity of this algorithm, such as scheduling plugin complexity and number of pods. These are considered to be unconfigurable parameters as they are natural to the cluster’s workload and cannot easily be tuned.

+

The Cluster Autoscaler loads the entire cluster’s state into memory, including Pods, Nodes, and Node Groups. On each scan interval, the algorithm identifies unschedulable pods and simulates scheduling for each Node Group. Tuning these factors come with different tradeoffs which should be carefully considered for your use case.

+

Vertically Autoscaling the Cluster Autoscaler

+

The simplest way to scale the Cluster Autoscaler to larger clusters is to increase the resource requests for its deployment. Both memory and CPU should be increased for large clusters, though this varies significantly with cluster size. The autoscaling algorithm stores all pods and nodes in memory, which can result in a memory footprint larger than a gigabyte in some cases. Increasing resources is typically done manually. If you find that constant resource tuning is creating an operational burden, consider using the Addon Resizer or Vertical Pod Autoscaler.

+

Reducing the number of Node Groups

+

Minimizing the number of node groups is one way to ensure that the Cluster Autoscaler will continue to perform well on large clusters. This may be challenging for some organizations who structure their node groups per team or per application. While this is fully supported by the Kubernetes API, this is considered to be a Cluster Autoscaler anti-pattern with repercussions for scalability. There are many reasons to use multiple node groups (e.g. Spot or GPUs), but in many cases there are alternative designs that achieve the same effect while using a small number of groups.

+

Ensure that:

+
    +
  • Pod isolation is done using Namespaces rather than Node Groups.
  • +
  • This may not be possible in low-trust multi-tenant clusters.
  • +
  • Pod ResourceRequests and ResourceLimits are properly set to avoid resource contention.
  • +
  • Larger instance types will result in more optimal bin packing and reduced system pod overhead.
  • +
  • NodeTaints or NodeSelectors are used to schedule pods as the exception, not as the rule.
  • +
  • Regional resources are defined as a single EC2 Auto Scaling Group with multiple Availability Zones.
  • +
+

Reducing the Scan Interval

+

A low scan interval (e.g. 10 seconds) will ensure that the Cluster Autoscaler responds as quickly as possible when pods become unschedulable. However, each scan results in many API calls to the Kubernetes API and EC2 Auto Scaling Group or EKS Managed Node Group APIs. These API calls can result in rate limiting or even service unavailability for your Kubernetes Control Plane.

+

The default scan interval is 10 seconds, but on AWS, launching a node takes significantly longer to launch a new instance. This means that it’s possible to increase the interval without significantly increasing overall scale up time. For example, if it takes 2 minutes to launch a node, changing the interval to 1 minute will result a tradeoff of 6x reduced API calls for 38% slower scale ups.

+

Sharding Across Node Groups

+

The Cluster Autoscaler can be configured to operate on a specific set of Node Groups. Using this functionality, it’s possible to deploy multiple instances of the Cluster Autoscaler, each configured to operate on a different set of Node Groups. This strategy enables you use arbitrarily large numbers of Node Groups, trading cost for scalability. We only recommend using this as a last resort for improving performance.

+

The Cluster Autoscaler was not originally designed for this configuration, so there are some side effects. Since the shards do not communicate, it’s possible for multiple autoscalers to attempt to schedule an unschedulable pod. This can result in unnecessary scale out of multiple Node Groups. These extra nodes will scale back in after the scale-down-delay.

+
metadata:
+  name: cluster-autoscaler
+  namespace: cluster-autoscaler-1
+
+...
+
+--nodes=1:10:k8s-worker-asg-1
+--nodes=1:10:k8s-worker-asg-2
+
+---
+
+metadata:
+  name: cluster-autoscaler
+  namespace: cluster-autoscaler-2
+
+...
+
+--nodes=1:10:k8s-worker-asg-3
+--nodes=1:10:k8s-worker-asg-4
+
+

Ensure that:

+
    +
  • Each shard is configured to point to a unique set of EC2 Auto Scaling Groups
  • +
  • Each shard is deployed to a separate namespace to avoid leader election conflicts
  • +
+

Optimizing for Cost and Availability

+

Spot Instances

+

You can use Spot Instances in your node groups and save up to 90% off the on-demand price, with the trade-off the Spot Instances can be interrupted at any time when EC2 needs the capacity back. Insufficient Capacity Errors will occur when your EC2 Auto Scaling group cannot scale up due to lack of available capacity. Maximizing diversity by selecting many instance families can increase your chance of achieving your desired scale by tapping into many Spot capacity pools, and decrease the impact of Spot Instance interruptions on your cluster availability. Mixed Instance Policies with Spot Instances are a great way to increase diversity without increasing the number of node groups. Keep in mind, if you need guaranteed resources, use On-Demand Instances instead of Spot Instances.

+

It’s critical that all Instance Types have similar resource capacity when configuring Mixed Instance Policies. The autoscaler’s scheduling simulator uses the first InstanceType in the MixedInstancePolicy. If subsequent Instance Types are larger, resources may be wasted after a scale up. If smaller, your pods may fail to schedule on the new instances due to insufficient capacity. For example, M4, M5, M5a, and M5n instances all have similar amounts of CPU and Memory and are great candidates for a MixedInstancePolicy. The EC2 Instance Selector tool can help you identify similar instance types.

+

+

It's recommended to isolate On-Demand and Spot capacity into separate EC2 Auto Scaling groups. This is preferred over using a base capacity strategy because the scheduling properties are fundamentally different. Since Spot Instances be interrupted at any time (when EC2 needs the capacity back), users will often taint their preemptable nodes, requiring an explicit pod toleration to the preemption behavior. These taints result in different scheduling properties for the nodes, so they should be separated into multiple EC2 Auto Scaling Groups.

+

The Cluster Autoscaler has a concept of Expanders, which provide different strategies for selecting which Node Group to scale. The strategy --expander=least-waste is a good general purpose default, and if you're going to use multiple node groups for Spot Instance diversification (as described in the image above), it could help further cost-optimize the node groups by scaling the group which would be best utilized after the scaling activity.

+

Prioritizing a node group / ASG

+

You may also configure priority based autoscaling by using the Priority expander. --expander=priority enables your cluster to prioritize a node group / ASG, and if it is unable to scale for any reason, it will choose the next node group in the prioritized list. This is useful in situations where, for example, you want to use P3 instance types because their GPU provides optimal performance for your workload, but as a second option you can also use P2 instance types.

+
apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cluster-autoscaler-priority-expander
+  namespace: kube-system
+data:
+  priorities: |-
+    10:
+      - .*p2-node-group.*
+    50:
+      - .*p3-node-group.*
+
+

Cluster Autoscaler will try to scale up the EC2 Auto Scaling group matching the name p3-node-group. If this operation does not succeed within --max-node-provision-time, it will attempt to scale an EC2 Auto Scaling group matching the name p2-node-group. +This value defaults to 15 minutes and can be reduced for more responsive node group selection, though if the value is too low, it can cause unnecessary scale outs.

+

Overprovisioning

+

The Cluster Autoscaler minimizes costs by ensuring that nodes are only added to the cluster when needed and are removed when unused. This significantly impacts deployment latency because many pods will be forced to wait for a node scale up before they can be scheduled. Nodes can take multiple minutes to become available, which can increase pod scheduling latency by an order of magnitude.

+

This can be mitigated using overprovisioning, which trades cost for scheduling latency. Overprovisioning is implemented using temporary pods with negative priority, which occupy space in the cluster. When newly created pods are unschedulable and have higher priority, the temporary pods will be preempted to make room. The temporary pods then become unschedulable, triggering the Cluster Autoscaler to scale out new overprovisioned nodes.

+

There are other less obvious benefits to overprovisioning. Without overprovisioning, one of the side effects of a highly utilized cluster is that pods will make less optimal scheduling decisions using the preferredDuringSchedulingIgnoredDuringExecution rule of Pod or Node Affinity. A common use case for this is to separate pods for a highly available application across availability zones using AntiAffinity. Overprovisioning can significantly increase the chance that a node of the correct zone is available.

+

The amount of overprovisioned capacity is a careful business decision for your organization. At its core, it’s a tradeoff between performance and cost. One way to make this decision is to determine your average scale up frequency and divide it by the amount of time it takes to scale up a new node. For example, if on average you require a new node every 30 seconds and EC2 takes 30 seconds to provision a new node, a single node of overprovisioning will ensure that there’s always an extra node available, reducing scheduling latency by 30 seconds at the cost of a single additional EC2 Instance. To improve zonal scheduling decisions, overprovision a number of nodes equal to the number of availability zones in your EC2 Auto Scaling Group to ensure that the scheduler can select the best zone for incoming pods.

+

Prevent Scale Down Eviction

+

Some workloads are expensive to evict. Big data analysis, machine learning tasks, and test runners will eventually complete, but must be restarted if interrupted. The Cluster Autoscaler will attempt to scale down any node under the scale-down-utilization-threshold, which will interrupt any remaining pods on the node. This can be prevented by ensuring that pods that are expensive to evict are protected by a label recognized by the Cluster Autoscaler.

+

Ensure that:

+
    +
  • Expensive to evict pods have the annotation cluster-autoscaler.kubernetes.io/safe-to-evict=false
  • +
+

Advanced Use Cases

+

EBS Volumes

+

Persistent storage is critical for building stateful applications, such as database or distributed caches. EBS Volumes enable this use case on Kubernetes, but are limited to a specific zone. These applications can be highly available if sharded across multiple AZs using a separate EBS Volume for each AZ. The Cluster Autoscaler can then balance the scaling of the EC2 Autoscaling Groups.

+

Ensure that:

+
    +
  • Node group balancing is enabled by setting balance-similar-node-groups=true.
  • +
  • Node Groups are configured with identical settings except for different availability zones and EBS Volumes.
  • +
+

Co-Scheduling

+

Machine learning distributed training jobs benefit significantly from the minimized latency of same-zone node configurations. These workloads deploy multiple pods to a specific zone. This can be achieved by setting Pod Affinity for all co-scheduled pods or Node Affinity using topologyKey: failure-domain.beta.kubernetes.io/zone. The Cluster Autoscaler will then scale out a specific zone to match demands. You may wish to allocate multiple EC2 Auto Scaling Groups, one per availability zone to enable failover for the entire co-scheduled workload.

+

Ensure that:

+
    +
  • Node group balancing is enabled by setting balance-similar-node-groups=false
  • +
  • Node Affinity and/or Pod Preemption is used when clusters include both Regional and Zonal Node Groups.
  • +
  • Use Node Affinity to force or encourage regional pods to avoid zonal Node Groups, and vice versa.
  • +
  • If zonal pods schedule onto regional node groups, this will result in imbalanced capacity for your regional pods.
  • +
  • If your zonal workloads can tolerate disruption and relocation, configure Pod Preemption to enable regionally scaled pods to force preemption and rescheduling on a less contested zone.
  • +
+

Accelerators

+

Some clusters take advantage of specialized hardware accelerators such as GPU. When scaling out, the accelerator device plugin can take several minutes to advertise the resource to the cluster. The Cluster Autoscaler has simulated that this node will have the accelerator, but until the accelerator becomes ready and updates the node’s available resources, pending pods can not be scheduled on the node. This can result in repeated unnecessary scale out.

+

Additionally, nodes with accelerators and high CPU or Memory utilization will not be considered for scale down, even if the accelerator is unused. This behavior can be expensive due to the relative cost of accelerators. Instead, the Cluster Autoscaler can apply special rules to consider nodes for scale down if they have unoccupied accelerators.

+

To ensure the correct behavior for these cases, you can configure the kubelet on your accelerator nodes to label the node before it joins the cluster. The Cluster Autoscaler will use this label selector to trigger the accelerator optimized behavior.

+

Ensure that:

+
    +
  • The Kubelet for GPU nodes is configured with --node-labels k8s.amazonaws.com/accelerator=$ACCELERATOR_TYPE
  • +
  • Nodes with Accelerators adhere to the identical scheduling properties rule noted above.
  • +
+

Scaling from 0

+

Cluster Autoscaler is capable of scaling Node Groups to and from zero, which can yield significant cost savings. It detects the CPU, memory, and GPU resources of an Auto Scaling Group by inspecting the InstanceType specified in its LaunchConfiguration or LaunchTemplate. Some pods require additional resources like WindowsENI or PrivateIPv4Address or specific NodeSelectors or Taints which cannot be discovered from the LaunchConfiguration. The Cluster Autoscaler can account for these factors by discovering them from tags on the EC2 Auto Scaling Group. For example:

+
Key: k8s.io/cluster-autoscaler/node-template/resources/$RESOURCE_NAME
+Value: 5
+Key: k8s.io/cluster-autoscaler/node-template/label/$LABEL_KEY
+Value: $LABEL_VALUE
+Key: k8s.io/cluster-autoscaler/node-template/taint/$TAINT_KEY
+Value: NoSchedule
+
+

Note: Keep in mind, when scaling to zero your capacity is returned to EC2 and may be unavailable in the future.

+

Additional Parameters

+

There are many configuration options that can be used to tune the behavior and performance of the Cluster Autoscaler. +A complete list of parameters is available on GitHub.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterDescriptionDefault
scan-intervalHow often cluster is reevaluated for scale up or down10 seconds
max-empty-bulk-deleteMaximum number of empty nodes that can be deleted at the same time.10
scale-down-delay-after-addHow long after scale up that scale down evaluation resumes10 minutes
scale-down-delay-after-deleteHow long after node deletion that scale down evaluation resumes, defaults to scan-intervalscan-interval
scale-down-delay-after-failureHow long after scale down failure that scale down evaluation resumes3 minutes
scale-down-unneeded-timeHow long a node should be unneeded before it is eligible for scale down10 minutes
scale-down-unready-timeHow long an unready node should be unneeded before it is eligible for scale down20 minutes
scale-down-utilization-thresholdNode utilization level, defined as sum of requested resources divided by capacity, below which a node can be considered for scale down0.5
scale-down-non-empty-candidates-countMaximum number of non empty nodes considered in one iteration as candidates for scale down with drain. Lower value means better CA responsiveness but possible slower scale down latency. Higher value can affect CA performance with big clusters (hundreds of nodes). Set to non positive value to turn this heuristic off - CA will not limit the number of nodes it considers.“30
scale-down-candidates-pool-ratioA ratio of nodes that are considered as additional non empty candidates for scale down when some candidates from previous iteration are no longer valid. Lower value means better CA responsiveness but possible slower scale down latency. Higher value can affect CA performance with big clusters (hundreds of nodes). Set to 1.0 to turn this heuristics off - CA will take all nodes as additional candidates.0.1
scale-down-candidates-pool-min-countMinimum number of nodes that are considered as additional non empty candidates for scale down when some candidates from previous iteration are no longer valid. When calculating the pool size for additional candidates we take max(#nodes * scale-down-candidates-pool-ratio, scale-down-candidates-pool-min-count)50
+

Additional Resources

+

This page contains a list of Cluster Autoscaler presentations and demos. If you'd like to add a presentation or demo here, please send a pull request.

+ + + + + + + + + + + + + + + + + +
Presentation/DemoPresenters
Autoscaling and Cost Optimization on Kubernetes: From 0 to 100Guy Templeton, Skyscanner & Jiaxin Shan, Amazon
SIG-Autoscaling Deep DiveMaciek Pytel & Marcin Wielgus
+

References

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cluster-autoscaling/spot_mix_instance_policy.jpg b/cluster-autoscaling/spot_mix_instance_policy.jpg new file mode 100644 index 000000000..68bbfb1b1 Binary files /dev/null and b/cluster-autoscaling/spot_mix_instance_policy.jpg differ diff --git a/cost_optimization/awareness/index.html b/cost_optimization/awareness/index.html new file mode 100644 index 000000000..295edea1e --- /dev/null +++ b/cost_optimization/awareness/index.html @@ -0,0 +1,2636 @@ + + + + + + + + + + + + + + + + + + + + + + + Awareness - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Expenditure awareness

+

Expenditure awareness is understanding who, where and what is causing expenditures in your EKS cluster. Getting an accurate picture of this data will help raise awareness of your spend and highlight areas to remediate.

+

Recommendations

+

Use Cost Explorer

+

AWS Cost Explorer has an easy-to-use interface that lets you visualize, understand, and manage your AWS costs and usage over time. You can analyze cost and usage data, at various levels using the filters available in Cost Explorer.

+

EKS Control Plane and EKS Fargate costs

+

Using the filters, we can query the costs incurred for the EKS costs at the Control Plane and Fargate Pod as shown in the diagram below:

+

Cost Explorer - EKS Control Plane

+

Using the filters, we can query the aggregate costs incurred for the Fargate Pods across regions in EKS - which includes both vCPU-Hours per CPU and GB Hrs as shown in the diagram below:

+

Cost Explorer - EKS Fargate

+

Tagging of Resources

+

Amazon EKS supports adding AWS tags to your Amazon EKS clusters. This makes it easy to control access to the EKS API for managing your clusters. Tags added to an EKS cluster are specific to the AWS EKS cluster resource, they do not propagate to other AWS resources used by the cluster such as EC2 instances or load balancers. Today, cluster tagging is supported for all new and existing EKS clusters via the AWS API, Console, and SDKs.

+

AWS Fargate is a technology that provides on-demand, right-sized compute capacity for containers. Before you can schedule pods on Fargate in your cluster, you must define at least one Fargate profile that specifies which pods should use Fargate when they are launched.

+

Adding and Listing tags to an EKS cluster: +

$ aws eks tag-resource --resource-arn arn:aws:eks:us-west-2:xxx:cluster/ekscluster1 --tags team=devops,env=staging,bu=cio,costcenter=1234
+$ aws eks list-tags-for-resource --resource-arn arn:aws:eks:us-west-2:xxx:cluster/ekscluster1
+{
+    "tags": {
+        "bu": "cio",
+        "env": "staging",
+        "costcenter": "1234",
+        "team": "devops"
+    }
+}
+
+After you activate cost allocation tags in the AWS Cost Explorer, AWS uses the cost allocation tags to organize your resource costs on your cost allocation report, to make it easier for you to categorize and track your AWS costs.

+

Tags don't have any semantic meaning to Amazon EKS and are interpreted strictly as a string of characters. For example, you can define a set of tags for your Amazon EKS clusters to help you track each cluster's owner and stack level.

+

Use AWS Trusted Advisor

+

AWS Trusted Advisor offers a rich set of best practice checks and recommendations across five categories: cost optimization; security; fault tolerance; performance; and service limits.

+

For Cost Optimization, Trusted Advisor helps eliminate unused and idle resources and recommends making commitments to reserved capacity. The key action items that will help Amazon EKS will be around low utilsed EC2 instances, unassociated Elastic IP addresses, Idle Load Balancers, underutilized EBS volumes among other things. The complete list of checks are provided at https://aws.amazon.com/premiumsupport/technology/trusted-advisor/best-practice-checklist/.

+

The Trusted Advisor also provides Savings Plans and Reserved Instances recommendations for EC2 instances and Fargate which allows you to commit to a consistent usage amount in exchange for discounted rates.

+
+

Note

+

The recommendations from Trusted Advisor are generic recommendations and not specific to EKS.

+
+

Use the Kubernetes dashboard

+

Kubernetes dashboard

+

Kubernetes Dashboard is a general purpose, web-based UI for Kubernetes clusters, which provides information about the Kubernetes cluster including the resource usage at a cluster, node and pod level. The deployment of the Kubernetes dashboard on an Amazon EKS cluster is described in the Amazon EKS documentation.

+

Dashboard provides resource usage breakdowns for each node and pod, as well as detailed metadata about pods, services, Deployments, and other Kubernetes objects. This consolidated information provides visibility into your Kubernetes environment.

+

Kubernetes Dashboard

+

kubectl top and describe commands

+

Viewing resource usage metrics with kubectl top and kubectl describe commands. kubectl top will show current CPU and memory usage for the pods or nodes across your cluster, or for a specific pod or node. The kubectl describe command will give more detailed information about a specific node or a pod. +

$ kubectl top pods
+$ kubectl top nodes
+$ kubectl top pod pod-name --namespace mynamespace --containers
+

+

Using the top command, the output will displays the total amount of CPU (in cores) and memory (in MiB) that the node is using, and the percentages of the node’s allocatable capacity those numbers represent. You can then drill-down to the next level, container level within pods by adding a --containers flag.

+
$ kubectl describe node <node>
+$ kubectl describe pod <pod>
+
+

kubectl describe returns the percent of total available capacity that each resource request or limit represents.

+

kubectl top and describe, track the utilization and availability of critical resources such as CPU, memory, and storage across kubernetes pods, nodes and containers. This awareness will help in understanding resource usage and help in controlling costs.

+

Use CloudWatch Container Insights

+

Use CloudWatch Container Insights to collect, aggregate, and summarize metrics and logs from your containerized applications and microservices. Container Insights is available for Amazon Elastic Kubernetes Service on EC2, and Kubernetes platforms on Amazon EC2. The metrics include utilization for resources such as CPU, memory, disk, and network.

+

The installation of insights is given in the documentation.

+

CloudWatch creates aggregated metrics at the cluster, node, pod, task, and service level as CloudWatch metrics.

+

The following query shows a list of nodes, sorted by average node CPU utilization +

STATS avg(node_cpu_utilization) as avg_node_cpu_utilization by NodeName
+| SORT avg_node_cpu_utilization DESC 
+

+

CPU usage by Container name +

stats pct(container_cpu_usage_total, 50) as CPUPercMedian by kubernetes.container_name 
+| filter Type="Container"
+
+Disk usage by Container name +
stats floor(avg(container_filesystem_usage/1024)) as container_filesystem_usage_avg_kb by InstanceId, kubernetes.container_name, device 
+| filter Type="ContainerFS" 
+| sort container_filesystem_usage_avg_kb desc
+

+

More sample queries are given in the Container Insights documention

+

This awareness will help in understanding resource usage and help in controlling costs.

+

Using Kubecost for expenditure awareness and guidance

+

Third party tools like kubecost can also be deployed on Amazon EKS to get visibility into cost of running your Kubernetes cluster. Please refer to this AWS blog for tracking costs using Kubecost

+

Deploying kubecost using Helm 3: +

$ curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
+$ helm version --short
+v3.2.1+gfe51cd1
+$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/
+$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/c^C
+$ kubectl create namespace kubecost 
+namespace/kubecost created
+$ helm repo add kubecost https://kubecost.github.io/cost-analyzer/ 
+"kubecost" has been added to your repositories
+
+$ helm install kubecost kubecost/cost-analyzer --namespace kubecost --set kubecostToken="aGRoZEBqc2pzLmNvbQ==xm343yadf98"
+NAME: kubecost
+LAST DEPLOYED: Mon May 18 08:49:05 2020
+NAMESPACE: kubecost
+STATUS: deployed
+REVISION: 1
+TEST SUITE: None
+NOTES:
+--------------------------------------------------Kubecost has been successfully installed. When pods are Ready, you can enable port-forwarding with the following command:
+
+    kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090
+
+Next, navigate to http://localhost:9090 in a web browser.
+$ kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090
+
+Note: If you are using Cloud 9 or have a need to forward it to a different port like 8080, issue the following command
+$ kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 8080:9090
+
+Kubecost Dashboard - +Kubernetes Cluster Auto Scaler logs

+

Use Kubernetes Cost Allocation and Capacity Planning Analytics Tool

+

Kubernetes Opex Analytics is a tool to help organizations track the resources being consumed by their Kubernetes clusters to prevent overpaying. To do so it generates, short- (7 days), mid- (14 days) and long-term (12 months) usage reports showing relevant insights on what amount of resources each project is spending over time.

+

Kubernetes Opex Analytics

+

Magalix Kubeadvisor

+

KubeAdvisor continuously scans your Kubernetes clusters and reports how you can fix issues, apply best practices, and optimize your cluster (with recommendations of resources like CPU/Memory around cost-efficiency).

+

Spot.io, previously called Spotinst

+

Spotinst Ocean is an application scaling service. Similar to Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups, Spotinst Ocean is designed to optimize performance and costs by leveraging Spot Instances combined with On-Demand and Reserved Instances. Using a combination of automated Spot Instance management and the variety of instance sizes, the Ocean cluster autoscaler scales according to the pod resource requirements. Spotinst Ocean also includes a prediction algorithm to predict Spot Instance interruption 15 minutes ahead of time and spin up a new node in a different Spot capacity pool.

+

This is available as an AWS Quickstart developed by Spotinst, Inc. in collaboration with AWS.

+

The EKS workshop also has a module on Optimized Worker Node on Amazon EKS Management with Ocean by Spot.io which includes sections on cost allocation, right sizing and scaling strategies.

+

Yotascale

+

Yotascale helps with accurately allocating Kubernetes costs. Yotascale Kubernetes Cost Allocation feature utilizes actual cost data, which is inclusive of Reserved Instance discounts and spot instance pricing instead of generic market-rate estimations, to inform the total Kubernetes cost footprint

+

More details can be found at their website.

+

Alcide Advisor

+

Alcide is an AWS Partner Network (APN) Advanced Technology Partner. Alcide Advisor helps ensure your Amazon EKS cluster, nodes, and pods configuration are tuned to run according to security best practices and internal guidelines. Alcide Advisor is an agentless service for Kubernetes audit and compliance that’s built to ensure a frictionless and secured DevSecOps flow by hardening the development stage before moving to production.

+

More details can be found in this blog post.

+

Other tools

+

Kubernetes Garbage Collection

+

The role of the Kubernetes garbage collector is to delete certain objects that once had an owner, but no longer have an owner.

+

Fargate count

+

Fargatecount is an useful tool, which allows AWS customers to track, with a custom CloudWatch metric, the total number of EKS pods that have been deployed on Fargate in a specific region of a specific account. This helps in keeping track of all the Fargate pods running across an EKS cluster.

+

Popeye - A Kubernetes Cluster Sanitizer

+

Popeye - A Kubernetes Cluster Sanitizer is a utility that scans live Kubernetes cluster and reports potential issues with deployed resources and configurations. It sanitizes your cluster based on what's deployed and not what's sitting on disk. By scanning your cluster, it detects misconfigurations and helps you to ensure that best practices are in place

+

Resources

+

Refer to the following resources to learn more about best practices for cost optimization.

+

Documentation and Blogs

+ +

Tools

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cost_optimization/cfm_framework/index.html b/cost_optimization/cfm_framework/index.html new file mode 100644 index 000000000..a40c7d149 --- /dev/null +++ b/cost_optimization/cfm_framework/index.html @@ -0,0 +1,2241 @@ + + + + + + + + + + + + + + + + + + + + + + + Cloud Financial Management Framework - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Cost Optimization - Introduction

+

AWS Cloud Economics is a discipline that helps customers increase efficiency and reduce their costs through the adoption of modern compute technologies like Amazon EKS. The discipline recommends following a methodology called the “Cloud Financial Management (CFM) framework” which consists of 4 pillars:

+

CFM Framework

+

The See pillar: Measurement and accountability

+

The See pillar is a foundational set of activities and technologies that define how to measure, monitor and create accountability for cloud spend. It is often referred to as “Observability”, “Instrumentation”, or “Telemetry”. The capabilities and limitations of the “Observability” infrastructure dictate what can be optimized. Obtaining a clear picture of your costs is a critical first step in cost optimization as you need to know where you are starting from. This type of visibility will also guide the types of activities you will need to do to further optimize your environment.

+

Here is a brief overview of our best practices for the See pillar:

+
    +
  • Define and maintain a tagging strategy for your workloads.
      +
    • Use Instance Tagging, tagging EKS clusters allows you to see individual cluster costs and allocate them in your Cost & Usage Reports.
    • +
    +
  • +
  • Establish reporting and monitoring of EKS usage by using technologies like Kubecost. +
  • +
  • Allocate cloud costs to applications, Lines of Business (LoBs), and revenue streams.
  • +
  • Define, measure, and circulate efficiency/value KPIs with business stakeholders. For example, create a “unit metric” KPI that measures the cost per transaction, e.g. a ride sharing services might have a KPI for “cost per ride”.
  • +
+

For more details on the recommended technologies and activities associated with this pillar, please see the Cost Optimization - Observability section of this guide.

+

The Save pillar: Cost optimization

+

This pillar is based on the technologies and capabilities developed in the “See” pillar. The following activities typically fall under this pillar:

+
    +
  • Identify and eliminate waste in your environment.
  • +
  • Architect and design for cost efficiency.
  • +
  • Choose the best purchasing option, e.g. on-demand instances vs Spot instances.
  • +
  • Adapt as services evolve: as AWS services evolve, the way to efficiently use those services may change. Be willing to adapt to account for these changes.
  • +
+

Since these activities are operational, they are highly dependent on your environment’s characteristics. Ask yourself, what are the main drivers of costs? What business value do your different environments provide? What purchasing options and infrastructure choices, e.g. instance family types, are best suited for each environment?

+

Below is a prioritized list of the most common cost drivers for EKS clusters:

+
    +
  1. Compute costs: Combining multiple types of instance families, purchasing options, and balancing scalability with availability require careful consideration. For further information, see the recommendations in the Cost Optimization - Compute section of this guide.
  2. +
  3. Networking costs: using 3 AZs for EKS clusters can potentially increase inter-AZ traffic costs. For our recommendations on how to balance HA requirements with keeping network traffic costs down, please consult the Cost Optimization - Networking section of this guide.
  4. +
  5. Storage costs: Depending on the stateful/stateless nature of the workloads in the EKS clusters, and how the different storage types are used, storage can be considered as part of the workload. For considerations relating to EKS storage costs, please consult the Cost Optimization - Storage section of this guide.
  6. +
+

The Plan pillar: Planning and forecasting

+

Once the recommendations in the See pillar are implemented, clusters are optimized on an on-going basis. As experience is gained in operating clusters efficiently, planning and forecasting activities can focus on:

+
    +
  • Budgeting and forecasting cloud costs dynamically.
  • +
  • Quantifying the business value delivered by EKS container services.
  • +
  • Integrating EKS cluster cost management with IT financial management planning.
  • +
+

The Run pillar

+

Cost optimization is a continuous process and involves a flywheel of incremental improvements:

+

Cost optimization flywheel

+

Securing executive sponsorship for these types of activities is crucial for integrating EKS cluster optimization into the organization’s “FinOps” efforts. It allows stakeholder alignment through a shared understanding of EKS cluster costs, implementation of EKS cluster cost guardrails, and ensuring that the tooling, automation, and activities evolve with the organization’s needs.

+

References

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cost_optimization/cost_opt_compute/index.html b/cost_optimization/cost_opt_compute/index.html new file mode 100644 index 000000000..02000cb5f --- /dev/null +++ b/cost_optimization/cost_opt_compute/index.html @@ -0,0 +1,2632 @@ + + + + + + + + + + + + + + + + + + + + + + + Compute - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Cost Optimization - Compute and Autoscaling

+

As a developer, you'll make estimates about your application’s resource requirements, e.g. CPU and memory, but if you’re not continually adjusting them they may become outdated which could increase your costs and worsen performance and reliability. Continually adjusting an application's resource requirements is more important than getting them right the first time.

+

The best practices mentioned below will help you build and operate cost-aware workloads that achieve business outcomes while minimizing costs and allowing your organization to maximize its return on investment. A high level order of importance for optimizing your cluster compute costs are:

+
    +
  1. Right-size workloads
  2. +
  3. Reduce unused capacity
  4. +
  5. Optimize compute capacity types (e.g. Spot) and accelerators (e.g. GPUs)
  6. +
+

Right-size your workloads

+

In most EKS clusters, the bulk of cost come from the EC2 instances that are used to run your containerized workloads. You will not be able to right-size your compute resources without understanding your workloads requirements. This is why it is essential that you use the appropriate requests and limits and make adjustments to those settings as necessary. In addition, dependencies, such as instance size and storage selection, may effect workload performance which can have a variety of unintended consequences on costs and reliability.

+

Requests should align with the actual utilization. If a container's requests are too high there will be unused capacity which is a large factor in total cluster costs. Each container in a pod, e.g. application and sidecars, should have their own requests and limits set to make sure the aggregate pod limits are as accurate as possible.

+

Utilize tools such as Goldilocks, KRR, and Kubecost which estimate resource requests and limits for your containers. Depending on the nature of the applications, performance/cost requirements, and complexity you need to evaluate which metrics are best to scale on, at what point your application performance degrades (saturation point), and how to tweak request and limits accordingly. Please refer to Application right sizing for further guidance on this topic.

+

We recommend using the Horizontal Pod Autoscaler (HPA) to control how many replicas of your application should be running, the Vertical Pod Autoscaler (VPA) to adjust how many requests and limits your application needs per replica, and a node autoscaler like Karpenter or Cluster Autoscaler to continually adjust the total number of nodes in your cluster. Cost optimization techniques using Karpenter and Cluster Autoscaler are documented in a later section of this document.

+

The Vertical Pod Autoscaler can adjust the requests and limits assigned to containers so workloads run optimally. You should run the VPA in auditing mode so it does not automatically make changes and restart your pods. It will suggest changes based on observed metrics. With any changes that affect production workloads you should review and test those changes first in a non-production environment because these can have impact on your application’s reliability and performance.

+

Reduce consumption

+

The best way to save money is to provision fewer resources. One way to do that is to adjust workloads based on their current requirements. You should start any cost optimization efforts with making sure your workloads define their requirements and scale dynamically. This will require getting metrics from your applications and setting configurations such as PodDisruptionBudgets and Pod Readiness Gates to make sure your application can safely scale up and down dynamically. Its important to consider that restrictive PodDisruptionBudgets can prevent Cluster Autoscaler and Karpenter from scaling down Nodes, since both Cluster Autoscaler and Karpenter respect PodDisruptionBudgets. The 'minAvailable' value in the PodDisruptionBudget should always be lower than the number of pods in the deployment and you should keep a good buffer between the two e.g. In a deployment of 6 pods where you want a minimum of 4 pods running at all times, set the 'minAvailable' in your PodDisruptionBidget to 4. This will allow Cluster Autoscaler and Karpenter to safely drain and evict pods from the under-utilized nodes during a Node scale-down event. Please refer to Cluster Autoscaler FAQ doc.

+

The Horizontal Pod Autoscaler is a flexible workload autoscaler that can adjust how many replicas are needed to meet the performance and reliability requirements of your application. It has a flexible model for defining when to scale up and down based on various metrics such as CPU, memory, or custom metrics e.g. queue depth, number of connections to a pod, etc.

+

The Kubernetes Metrics Server enables scaling in response to built-in metrics like CPU and memory usage, but if you want to scale based on other metrics, such as Amazon CloudWatch or SQS queue depth, you should consider event driven autoscaling projects such as KEDA. Please refer to this blog post on how to use KEDA with CloudWatch metrics. If you are unsure, which metrics to monitor and scale based on, check out the best practices on monitoring metrics that matters.

+

Reducing workload consumption creates excess capacity in a cluster and with proper autoscaling configuration allows you to scale down nodes automatically and reduce your total spend. We recommend you do not try to optimize compute capacity manually. The Kubernetes scheduler and node autoscalers were designed to handle this process for you.

+

Reduce unused capacity

+

After you have determined the correct size for applications, reducing excess requests, you can begin to reduce the provisioned compute capacity. You should be able to do this dynamically if you have taken the time to correctly size your workloads from the sections above. There are two primary node autoscalers used with Kubernetes in AWS.

+

Karpenter and Cluster Autoscaler

+

Both Karpenter and the Kubernetes Cluster Autoscaler will scale the number of nodes in your cluster as pods are created or removed and compute requirements change. The primary goal of both is the same, but Karpenter takes a different approach for node management provisioning and de-provisioning which can help reduce costs and optimize cluster wide usage.

+

As clusters grow in size and the variety of workloads increases it becomes more difficult to pre-configure node groups and instances. Just like with workload requests it’s important to set an initial baseline and continually adjust as needed.

+

If you are using Cluster Autoscaler, it will respect the "minimum" and "maximum" values of each Auto Scaling group (ASG) and only adjust the "desired" value. It's important to pay attention while setting these values for the underlying ASG since Cluster Autoscaler will not be able to scale down an ASG beyond its "minimum" count. Set the "desired" count as the number of nodes you need during normal business hours and "minimum" as the number of nodes you need during off-business hours. Please refer to Cluster Autoscaler FAQ doc.

+

Cluster Autoscaler Priority Expander

+

The Kubernetes Cluster Autoscaler works by scaling groups of nodes — called a node group — up and down as applications scale up and down. If you are not dynamically scaling workloads then the Cluster Autoscaler will not help you save money. The Cluster Autoscaler requires a cluster admin to create node groups ahead of time for workloads to consume. The node groups need to configured to use instances that have the same "profile", i.e. roughly the same amount of CPU and memory.

+

You can have multiple node groups and the Cluster Autoscaler can be configured to set priority scaling levels and each node group can contain different sized nodes. Node groups can have different capacity types and the priority expander can be used to scale less expensive groups first.

+

Below is an example of a snippet of cluster configuration that uses a `ConfigMap`` to prioritize reserved capacity before using on-demand instances. You can use the same technique to prioritize Graviton or Spot Instances over other types.

+
apiVersion: eksctl.io/v1alpha5
+kind: ClusterConfig
+metadata:
+  name: my-cluster
+managedNodeGroups:
+  - name: managed-ondemand
+    minSize: 1
+    maxSize: 7
+    instanceType: m5.xlarge
+  - name: managed-reserved
+    minSize: 2
+    maxSize: 10
+    instanceType: c5.2xlarge
+
+
apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cluster-autoscaler-priority-expander
+  namespace: kube-system
+data:
+  priorities: |-
+    10:
+      - .*ondemand.*
+    50:
+      - .*reserved.*
+
+

Using node groups can help the underlying compute resources do the expected thing by default, e.g. spread nodes across AZs, but not all workloads have the same requirements or expectations and it’s better to let applications declare their requirements explicitly. For more information about Cluster Autoscaler, please see the best practices section.

+

Descheduler

+

The Cluster Autoscaler can add and remove node capacity from a cluster based on new pods needing to be scheduled or nodes being underutilized. It does not take a wholistic view of pod placement after it has been scheduled to a node. If you are using the Cluster Autoscaler you should also look at the Kubernetes descheduler to avoid wasting capacity in your cluster.

+

If you have 10 nodes in a cluster and each node is 60% utilized you are not using 40% of the provisioned capacity in the cluster. With the Cluster Autoscaler you can set the utilization threashold per node to 60%, but that would only try to scale down a single node after utilization dropped below 60%.

+

With the descheduler it can look at cluster capacity and utilization after pods have been scheduled or nodes have been added to the cluster. It attempts to keep the total capacity of the cluster above a specified threshold. It can also remove pods based on node taints or new nodes that join the cluster to make sure pods are running in their optimal compute environment. Note that, descheduler does not schedule replacement of evicted pods but relies on the default scheduler for that.

+

Karpenter Consolidation

+

Karpenter takes a “groupless” approach to node management. This approach is more flexible for different workload types and requires less up front configuration for cluster administrators. Instead of pre-defining groups and scaling each group as workloads need, Karpenter uses provisioners and node templates to define broadly what type of EC2 instances can be created and settings about the instances as they are created.

+

Bin packing is the practice of utilizing more of the instance’s resources by packing more workloads onto fewer, optimally sized, instances. While this helps to reduce your compute costs by only provisioning resources your workloads use, it has a trade-off. It can take longer to start new workloads because capacity has to be added to the cluster, especially during large scaling events. Consider the balance between cost optimization, performance, and availability when setting up bin packing.

+

Karpenter can continuously monitor and binpack to improve instance resource utilization and lower your compute costs. Karpenter can also select a more cost efficient worker node for your workload. This can be achieved by turning on “consolidation” flag to true in the provisioner (sample code snippet below). The example below shows an example provisioner that enables consolidation. At the time of writing this guide, Karpenter won’t replace a running Spot instance with a cheaper Spot instance. For further details on Karpenter consolidation, refer to this blog.

+
apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+  name: enable-binpacking
+spec:
+  consolidation:
+    enabled: true
+
+

For workloads that might not be interruptible e.g. long running batch jobs without checkpointing, consider annotating pods with the do-not-evict annotation. By opting pods out of eviction, you are telling Karpenter that it should not voluntarily remove nodes containing this pod. However, if a do-not-evict pod is added to a node while the node is draining, the remaining pods will still evict, but that pod will block termination until it is removed. In either case, the node will be cordoned to prevent additional work from being scheduled on the node. Below is an example showing how set the annotation:

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: label-demo
+  labels:
+    environment: production
+  annotations:  
+    "karpenter.sh/do-not-evict": "true"
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+
+

Remove under-utilized nodes by adjusting Cluster Autoscaler parameters

+

Node utilization is defined as the sum of requested resources divided by capacity. By default scale-down-utilization-threshold is set to 50%. This parameter can be used along with and scale-down-unneeded-time, which determines how long a node should be unneeded before it is eligible for scale down — the default is 10 minutes. Pods still running on a node that was scaled down will get scheduled on other nodes by kube-scheduler. Adjusting these settings can help remove nodes that are underutilized, but it’s important you test these values first so you don’t force the cluster to scale down prematurely.

+

You can prevent scale down from happening by ensuring that pods that are expensive to evict are protected by a label recognized by the Cluster Autoscaler. To do this, ensure that pods that are expensive to evict have the annotation cluster-autoscaler.kubernetes.io/safe-to-evict=false. Below is an example yaml to set the annotation:

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: label-demo
+  labels:
+    environment: production
+  annotations:  
+    "cluster-autoscaler.kubernetes.io/safe-to-evict": "false"
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+
+

Tag nodes with Cluster Autoscaler and Karpenter

+

AWS resource tags are used to organize your resources, and to track your AWS costs on a detailed level. They do not directly correlate with Kubernetes labels for cost tracking. It’s recommended to start with Kubernetes resource labeling and utilize tools like Kubecost to get infrastructure cost reporting based on Kubernetes labels on pods, namespaces etc.

+

Worker nodes need to have tags to show billing information in AWS Cost Explorer. With Cluster Autoscaler, tag your worker nodes inside a managed node group using launch template. For self managed node groups, tag your instances using EC2 auto scaling group. For instances provisioned by Karpenter, tag them using spec.tags in the node template.

+

Multi-tenant clusters

+

When working on clusters that are shared by different teams you may not have visibility to other workloads running on the same node. While resource requests can help isolate some “noisy neighbor” concerns, such as CPU sharing, they may not isolate all resource boundaries such as disk I/O exhaustion. Not every consumable resource by a workload can be isolated or limited. Workloads that consume shared resources at higher rates than other workloads should be isolated through node taints and tolerations. Another advanced technique for such workload is CPU pinning which ensures exclusive CPU instead of shared CPU for the container.

+

Isolating workloads at a node level can be more expensive, but it may be possible to schedule BestEffort jobs or take advantage of additional savings by using Reserved Instances, Graviton processors, or Spot.

+

Shared clusters may also have cluster level resource constraints such as IP exhaustion, Kubernetes service limits, or API scaling requests. You should review the scalability best practices guide to make sure your clusters avoid these limits.

+

You can isolate resources at a namespace or Karpenter provisioner level. Resource Quotas provide a way to set limits on how many resources workloads in a namespace can consume. This can be a good initial guard rail but it should be continually evaluated to make sure it doesn’t artificially restrict workloads from scaling.

+

Karpenter provisioners can set limits on some of the consumable resources in a cluster (e.g. CPU, GPU), but you will need to configure tenant applications to use the appropriate provisioner. This can prevent a single provisioner from creating too many nodes in a cluster, but it should be continually evaluated to make sure the limit isn’t set too low and in turn, prevent workloads from scaling.

+

Scheduled Autoscaling

+

You may have the need to scale down your clusters on weekends and off hours. This is particularly relevant for test and non-production clusters where you want to scale down to zero when they are not in use. Solutions like cluster-turndown can scale down the replicas to zero based on a cron schedule. You can also acheive this with Karpenter, outlined in the following AWS blog.

+

Optimize compute capacity types

+

After optimizing the total capacity of compute in your cluster and utilizing bin packing, you should look at what type of compute you have provisioned in your clusters and how you pay for those resources. AWS has Compute Savings plans that can reduce the cost for your compute which we will categorize into the following capacity types:

+
    +
  • Spot
  • +
  • Savings Plans
  • +
  • On-Demand
  • +
  • Fargate
  • +
+

Each capacity type has different trade-offs for management overhead, availability, and long term commitments and you will need to decide which is right for your environment. No environment should rely on a single capacity type and you can mix multiple run types in a single cluster to optimize specific workload requirements and cost.

+

Spot

+

The spot capacity type provisions EC2 instances from spare capacity in an Availability Zone. Spot offers the largest discounts—up to 90% — but those instances can be interrupted when they are needed elsewhere. Additionally, there may not always be capacity to provision new Spot instances and existing Spot instances can be reclaimed with a 2 minute interruption notice. If your application has a long startup or shutdown process, Spot instances may not be the best option.

+

Spot compute should use a wide variety of instance types to reduce the likelihood of not having spot capacity available. Instance interruptions need to be handled to safely shutdown nodes. Nodes provisioned with Karpenter or part of a Managed Node Group automatically support instance interruption notifications. If you are using self-managed nodes you will need to run the node termination handler separately to gracefully shutdown spot instances.

+

It is possible to balance spot and on-demand instances in a single cluster. With Karpenter you can create weighted provisioners to achieve a balance of different capacity types. With Cluster Autoscaler you can create mixed node groups with spot and on-demand or reserved instances.

+

Here is an example of using Karpenter to prioritize Spot instances ahead of On-Demand instances. When creating a provisioner, you can specify either Spot, On-Demand, or both (as shown below). When you specify both, and if the pod does not explicitly specify whether it needs to use Spot or On-Demand, then Karpenter prioritizes Spot when provisioning a node with price-capacity-optimization allocation strategy .

+
apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+  name: spot-prioritized
+spec:
+  requirements:
+    - key: "karpenter.sh/capacity-type" 
+      operator: In
+        values: ["spot", "on-demand"]
+
+

Savings Plans, Reserved Instances, and AWS EDP

+

You can reduce your compute spend by using a compute savings plan. Savings plans offer reduced prices for a 1 or 3 year commitment of compute usage. The usage can apply to EC2 instances in an EKS cluster but also applies to any compute usage such as Lambda and Fargate. With savings plans you can reduce costs and still pick any EC2 instance type during your commitment period.

+

Compute savings plan can reduce your EC2 cost by up to 66% without requiring commitments on what instance types, families, or regions you want to use. Savings are automatically applied to instances as you use them.

+

EC2 Instance Savings Plans provides up to 72% savings on compute with a commitment of usage in a specific region and EC2 family, e.g. instances from the C family. You can shift usage to any AZ within the region, use any generation of the instance family, e.g. c5 or c6, and use any size of instance within the family. The discount will automatically be applied for any instance in your account that matches the savings plan criteria.

+

Reserved Instances are similar to EC2 Instance Savings Plan but they also guarantee capacity in an Availability Zone or Region and reduce cost—up to 72% — over on-demand instances. Once you calculate how much reserved capacity you will need you can select how long you would like to reserve them for (1 year or 3 years). The discounts will automatically be applied as you run those EC2 instances in your account.

+

Customers also have the option to enroll in an Enterprise Agreement with AWS. Enterprise Agreements give customers the option to tailor agreements that best suit their needs. Customers can enjoy discounts on the pricing based on AWS EDP (Enterprise Discount Program). For additional information on Enterprise Agreements please contact your AWS sales representative.

+

On-Demand

+

On-Demand EC2 instances have the benefit of availability without interruptions — compared to spot — and no long term commitments — compared to savings plans. If you are looking to reduce costs in a cluster you should reduce your usage of on-demand EC2 instances.

+

After optimizing your workload requirements you should be able to calculate a minimum and maximum capacity for your clusters. This number may change over time but rarely goes down. Consider using a Savings Plan for everything under the minimum, and spot for capacity that will not affect your application’s availability. Anything else that may not be continuously used or is required for availability can use on-demand.

+

As mentioned in this section, the best way to reduce your usage is to consume fewer resources and utilize the resources you provision to the fullest extent possible. With the Cluster Autoscaler you can remove underutilized nodes with the scale-down-utilization-threshold setting. With Karpenter it is recommended to enable consolidation.

+

To manually identify EC2 instance types that can be used with your workloads you should use ec2-instance-selector which can show instances that are available in each region as well as instances compatible with EKS. Example usage for instances with x86 process architecture, 4 Gb of memory, 2 vCPUs and available in the us-east-1 region.

+
ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 \
+  -r us-east-1 --service eks
+c5.large
+c5a.large
+c5ad.large
+c5d.large
+c6a.large
+c6i.large
+t2.medium
+t3.medium
+t3a.medium
+
+

For non-production environments you can automatically have clusters scaled down during unused hours such as night and weekends. The kubecost project cluster-turndown is an example of a controller that can automatically scale your cluster down based on a set schedule.

+

Fargate compute

+

Fargate compute is a fully managed compute option for EKS clusters. It provides pod isolation by scheduling one pod per node in a Kubernetes cluster. It allows you to size your compute nodes to the CPU and RAM requirements of your workload to tightly control workload usage in a cluster.

+

Fargate can scale workloads as small as .25 vCPU with 0.5 GB memory and as large as 16 vCPU with 120 GB memory. There are limits on how many pod size variations are available and you will need to understand how your workload best fits into a Fargate configuration. For example, if your workload requires 1 vCPU with 0.5 GB of memory the smallest Fargate pod will be 1 vCPU with 2 GB of memory.

+

While Fargate has many benefits such as no EC2 instance or operating system management, it may require more compute capacity than traditional EC2 instances due to the fact that every deployed pod is isolated as a separate node in the cluster. This requires more duplication for things like the Kubelet, logging agents, and any DaemonSets you would typically deploy to a node. DaemonSets are not supported in Fargate and they will need to be converted into pod “sidecars“ and run alongside the application.

+

Fargate cannot benefit from bin packing or CPU over provisioning because the boundary for the workload is a node which is not burstable or shareable between workloads. Fargate will save you EC2 instance management time which itself has a cost, but CPU and memory costs may be more expensive than other EC2 capacity types. Fargate pods can take advantage of compute savings plan to reduce the on-demand cost.

+

Optimize Compute Usage

+

Another way to save money on your compute infrastructure is to use more efficient compute for the workload. This can come from more performant general purpose compute like Graviton processors which are up to 20% cheaper and 60% more energy efficient than x86—or workload specific accelerators such as GPUs and FPGAs. You will need to build containers that can run on arm architecture and set up nodes with the right accelerators for your workloads.

+

EKS has the ability to run clusters with mixed architecture (e.g. amd64 and arm64) and if your containers are compiled for multiple architectures you can take advantage of Graviton processors with Karpenter by allowing both architectures in your provisioner. To keep consistent performance, however, it is recommended you keep each workload on a single compute architecture and only use different architecture if there is no additional capacity available.

+

Provisioners can be configured with multiple architectures and workloads can also request specific architectures in their workload specification.

+
apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+  name: default
+spec:
+  requirements:
+  - key: "kubernetes.io/arch"
+    operator: In
+    values: ["arm64", "amd64"]
+
+

With Cluster Autoscaler you will need to create a node group for Graviton instances and set node tolerations on your workload to utilize the new capacity.

+

GPUs and FPGAs can greatly increase the performance for your workload, but the workload will need to be optimized to use the accelerator. Many workload types for machine learning and artificial intelligence can use GPUs for compute and instances can be added to a cluster and mounted into a workload using resource requests.

+
spec:
+  template:
+    spec:
+    - containers:
+      ...
+      resources:
+          limits:
+            nvidia.com/gpu: "1"
+
+

Some GPU hardware can be shared across multiple workloads so a single GPU can be provisioned and used. To see how to configure workload GPU sharing see the virtual GPU device plugin for more information. You can also refer to the following blogs:

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cost_optimization/cost_opt_networking/index.html b/cost_optimization/cost_opt_networking/index.html new file mode 100644 index 000000000..8bcbb46c5 --- /dev/null +++ b/cost_optimization/cost_opt_networking/index.html @@ -0,0 +1,2799 @@ + + + + + + + + + + + + + + + + + + + + + + + Network - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Cost Optimization - Networking

+

Architecting systems for high availability (HA) is a best practice in order to accomplish resilience and fault-tolerance. In practice, this means spreading your workloads and the underlying infrastructure across multiple Availability Zones (AZs) in a given AWS Region. Ensuring these characteristics are in place for your Amazon EKS environment will enhance the overall reliability of your system. In conjunction with this, your EKS environments will likely also be composed of a variety of constructs (i.e. VPCs), components (i.e. ELBs), and integrations (i.e. ECR and other container registries).

+

The combination of highly available systems and other use-case specific components can play a significant role in how data is transferred and processed. This will in turn have an impact on the costs incurred due to data transfer and processing.

+

The practices detailed below will help you design and optimize your EKS environments in order to achieve cost-effectiveness for different domains and use cases.

+

Pod to Pod Communication

+

Depending on your setup, network communication and data transfer between Pods can have a significant impact on the overall cost of running Amazon EKS workloads. This section will cover different concepts and approaches to mitigating the costs tied to inter-pod communication, while considering highly available (HA) architectures, application performance and resilience.

+

Restricting Traffic to an Availability Zone

+

Frequent egress cross-zone traffic (traffic distributed between AZs) can have a major impact on your network-related costs. Below are some strategies on how to control the amount of cross-zone traffic between Pods in your EKS cluster.

+

If you want granular visibility into the amount of cross-zone traffic between Pods in your cluster (such as the amount of data transferred in bytes), refer to this post.

+

Using Topology Aware Routing (formerly known as Topology Aware Hints)

+

Topology aware routing

+

When using topology aware routing, it's important to understand how Services, EndpointSlices and the kube-proxy work together when routing traffic. As the diagram above depicts, Services are the stable network abstraction layer that receive traffic destined for your Pods. When a Service is created, multiple EndpointSlices are created. Each EndpointSlice has a list of endpoints containing a subset of Pod addresses along with the nodes they're running on and any additional topology information. kube-proxy is a daemonset that runs on every node in your cluster and also fulfills a role of internal routing, but it does so based on what it consumes from the created EndpointSlices.

+

When topology aware routing is enabled and implemented on a Kubernetes Service, the EndpointSlice controller will proportionally allocate endpoints to the different zones that your cluster is spread across. For each of those endpoints, the EndpointSlice controller will also set a hint for the zone. Hints describe which zone an endpoint should serve traffic for. kube-proxy will then route traffic from a zone to an endpoint based on the hints that get applied.

+

The diagram below shows how EndpointSlices with hints are organized in such a way that kube-proxy can know what destination they should go to based on their zonal point of origin. Without hints, there is no such allocation or organization and traffic will be proxied to different zonal destinations regardless of where it’s coming from.

+

Endpoint Slice

+

In some cases, the EndpointSlice controller may apply a hint for a different zone, meaning the endpoint could end up serving traffic originating from a different zone. The reason for this is to try and maintain an even distribution of traffic between endpoints in different zones.

+

Below is a code snippet on how to enable topology aware routing for a Service.

+
apiVersion: v1
+kind: Service
+metadata:
+  name: orders-service
+  namespace: ecommerce
+    annotations:
+      service.kubernetes.io/topology-mode: Auto
+spec:
+  selector:
+    app: orders
+  type: ClusterIP
+  ports:
+  - protocol: TCP
+    port: 3003
+    targetPort: 3003
+
+

The screenshot below shows the result of the EndpointSlice controller having successfully applied a hint to an endpoint for a Pod replica running in the AZ eu-west-1a.

+

Slice shell

+
+

Note

+

It’s important to note that topology aware routing is still in beta. Also, this feature is more predictable when workloads are widely and evenly distributed across the cluster topology. Therefore, it is highly recommended to use it in conjunction with scheduling constraints that increase the availability of an application such as pod topology spread constraints.

+
+

Using Autoscalers: Provision Nodes to a Specific AZ

+

We strongly recommend running your workloads in highly available environments across multiple AZs. This improves the reliability of your applications, especially when there is an incident of an issue with an AZ. In the case you're willing to sacrifice reliability for the sake of reducing their network-related costs, you can restrict your nodes to a single AZ.

+

To run all your Pods in the same AZ, either provision the worker nodes in the same AZ or schedule the Pods on the worker nodes running on the same AZ. To provision nodes within a single AZ, define a node group with subnets belonging to the same AZ with Cluster Autoscaler (CA). For Karpenter, use “topology.kubernetes.io/zone” and specify the AZ where you’d like to create the worker nodes. For example, the below Karpenter provisioner snippet provisions the nodes in the us-west-2a AZ.

+

Karpenter

+
apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+name: single-az
+spec:
+  requirements:
+  - key: "topology.kubernetes.io/zone“
+    operator: In
+    values: ["us-west-2a"]
+
+

Cluster Autoscaler (CA)

+
apiVersion: eksctl.io/v1alpha5
+kind: ClusterConfig
+metadata:
+  name: my-ca-cluster
+  region: us-east-1
+  version: "1.21"
+availabilityZones:
+- us-east-1a
+managedNodeGroups:
+- name: managed-nodes
+  labels:
+    role: managed-nodes
+  instanceType: t3.medium
+  minSize: 1
+  maxSize: 10
+  desiredCapacity: 1
+...
+
+

Using Pod Assignment and Node Affinity

+

Alternatively, if you have worker nodes running in multiple AZs, each node would have the label topology.kubernetes.io/zone with the value of its AZ (such as us-west-2a or us-west-2b). You can utilize nodeSelector or nodeAffinity to schedule Pods to the nodes in a single AZ. For example, the following manifest file will schedule the Pod inside a node running in AZ us-west-2a.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: nginx
+  labels:
+    env: test
+spec:
+  nodeSelector:
+    topology.kubernetes.io/zone: us-west-2a
+  containers:
+  - name: nginx
+    image: nginx 
+    imagePullPolicy: IfNotPresent
+
+

Restricting Traffic to a Node

+

There are cases where restricting traffic at a zonal level isn’t sufficient. Apart from reducing costs, you may have the added requirement of reducing network latency between certain applications that have frequent inter-communication. In order to achieve optimal network performance and reduce costs, you need a way to restrict traffic to a specific node. For example, Microservice A should always talk to Microservice B on Node 1, even in highly available (HA) setups. Having Microservice A on Node 1 talk to Microservice B on Node 2 may have a negative impact on the desired performance for applications of this nature, especially if Node 2 is in a separate AZ altogether.

+

Using the Service Internal Traffic Policy

+

In order to restrict Pod network traffic to a node, you can make use of the Service internal traffic policy. By default, traffic sent to a workload’s Service will be randomly distributed across the different generated endpoints. So in a HA architecture, that means traffic from Microservice A could go to any replica of Microservice B on any given node across the different AZs. However, with the Service's internal traffic policy set to Local, traffic will be restricted to endpoints on the node that the traffic originated from. This policy dictates the exclusive use of node-local endpoints. By implication, your network traffic-related costs for that workload will be lower than if the distribution was cluster wide. Also, the latency will be lower, making your application more performant.

+
+

Note

+

It’s important to note that this feature cannot be combined with topology aware routing in Kubernetes.

+
+

Local internal traffic

+

Below is a code snippet on how to set the internal traffic policy for a Service.

+
apiVersion: v1
+kind: Service
+metadata:
+  name: orders-service
+  namespace: ecommerce
+spec:
+  selector:
+    app: orders
+  type: ClusterIP
+  ports:
+  - protocol: TCP
+    port: 3003
+    targetPort: 3003
+  internalTrafficPolicy: Local
+
+

To avoid unexpected behaviour from your application due to traffic drops, you should consider the following approaches:

+ +

In this example, you have 2 replicas of Microservice A and 3 replicas of Microservice B. If Microservice A has its replicas spread between Nodes 1 and 2, and Microservice B has all 3 of its replicas on Node 3, then they won't be able to communicate because of the Local internal traffic policy. When there are no available node-local endpoints the traffic is dropped.

+

node-local_no_peer

+

If Microservice B does have 2 of its 3 replicas on Nodes 1 and 2, then there will be communication between the peer applications. But you would still have an isolated replica of Microservice B without any peer replica to communicate with.

+

node-local_with_peer

+
+

Note

+

In some scenarios, an isolated replica like the one depicted in the above diagram may not be a cause for concern if it still serves a purpose (such as serving requests from external incoming traffic).

+
+

Using the Service Internal Traffic Policy with Topology Spread Constraints

+

Using the internal traffic policy in conjunction with topology spread constraints can be useful to ensure that you have the right number of replicas for communicating microservices on different nodes.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: express-test
+spec:
+  replicas: 6
+  selector:
+    matchLabels:
+      app: express-test
+  template:
+    metadata:
+      labels:
+        app: express-test
+        tier: backend
+    spec:
+      topologySpreadConstraints:
+      - maxSkew: 1
+        topologyKey: "topology.kubernetes.io/zone"
+        whenUnsatisfiable: ScheduleAnyway
+        labelSelector:
+          matchLabels:
+            app: express-test
+
+

Using the Service Internal Traffic Policy with Pod Affinity Rules

+

Another approach is to make use of Pod affinity rules when using the Service internal traffic policy. With Pod affinity, you can influence the scheduler to co-locate certain Pods because of their frequent communication. By applying strict scheduling constraints (requiredDuringSchedulingIgnoredDuringExecution) on certain Pods, this will give you better results for Pod co-location when the Scheduler is placing Pods on nodes.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: graphql
+  namespace: ecommerce
+  labels:
+    app.kubernetes.io/version: "0.1.6"
+    ...
+    spec:
+      serviceAccountName: graphql-service-account
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+              - key: app
+                operator: In
+                values:
+                - orders
+            topologyKey: "kubernetes.io/hostname"
+
+

Load Balancer to Pod Communication

+

EKS workloads are typically fronted by a load balancer that distributes traffic to the relevant Pods in your EKS cluster. Your architecture may comprise internal and/or external facing load balancers. Depending on your architecture and network traffic configurations, the communication between load balancers and Pods can contribute a significant amount to data transfer charges.

+

You can use the AWS Load Balancer Controller to automatically manage the creation of ELB resources (ALB and NLB). The data transfer charges you incur in such setups will depend on the path taken by the network traffic. The AWS Load Balancer Controller supports two network traffic modes, instance mode, and ip mode.

+

When using instance mode, a NodePort will be opened on each node in your EKS cluster. The load balancer will then proxy traffic evenly across the nodes. If a node has the destination Pod running on it, then there will be no data transfer costs incurred. However, if the destination Pod is on a separate node and in a different AZ than the NodePort receiving the traffic, then there will be an extra network hop from the kube-proxy to the destination Pod. In such a scenario, there will be cross-AZ data transfer charges. Because of the even distribution of traffic across the nodes, it is highly likely that there will be additional data transfer charges associated with cross-zone network traffic hops from kube-proxies to the relevant destination Pods.

+

The diagram below depicts a network path for traffic flowing from the load balancer to the NodePort, and subsequently from the kube-proxy to the destination Pod on a separate node in a different AZ. This is an example of the instance mode setting.

+

LB to Pod

+

When using ip mode, network traffic is proxied from the load balancer directly to the destination Pod. As a result, there are no data transfer charges involved in this approach.

+
+

Tip

+

It is recommended that you set your load balancer to ip traffic mode to reduce data transfer charges. For this setup, it’s also important to make sure that your load balancer is deployed across all the subnets in your VPC.

+
+

The diagram below depicts network paths for traffic flowing from the load balancer to Pods in the network ip mode.

+

IP mode

+

Data Transfer from Container Registry

+

Amazon ECR

+

Data transfer into the Amazon ECR private registry is free. In-region data transfer incurs no cost, but data transfer out to the internet and across regions will be charged at Internet Data Transfer rates on both sides of the transfer.

+

You should utilize ECRs built-in image replication feature to replicate the relevant container images into the same region as your workloads. This way the replication would be charged once, and all the same region (intra-region) image pulls would be free.

+

You can further reduce data transfer costs associated with pulling images from ECR (data transfer out) by using Interface VPC Endpoints to connect to the in-region ECR repositories. The alternative approach of connecting to ECR’s public AWS endpoint (via a NAT Gateway and an Internet Gateway) will incur higher data processing and transfer costs. The next section will cover reducing data transfer costs between your workloads and AWS Services in greater detail.

+

If you’re running workloads with especially large images, you can build your own custom Amazon Machine Images (AMIs) with pre-cached container images. This can reduce the initial image pull time and potential data transfer costs from a container registry to the EKS worker nodes.

+

Data Transfer to Internet & AWS Services

+

It's a common practice to integrate Kubernetes workloads with other AWS services or third-party tools and platforms via the Internet. The underlying network infrastructure used to route traffic to and from the relevant destination can impact the costs incurred in the data transfer process.

+

Using NAT Gateways

+

NAT Gateways are network components that perform network address translation (NAT). The diagram below depicts Pods in an EKS cluster communicating with other AWS services (Amazon ECR, DynamoDB, and S3), and third-party platforms. In this example, the Pods are running in private subnets in separate AZs. To send and receive traffic from the Internet, a NAT Gateway is deployed to the public subnet of one AZ, allowing any resources with private IP addresses to share a single public IP address to access the Internet. This NAT Gateway in turn communicates with the Internet Gateway component, allowing for packets to be sent to their final destination.

+

NAT Gateway

+

When using NAT Gateways for such use cases, you can minimize the data transfer costs by deploying a NAT Gateway in each AZ. This way, traffic routed to the Internet will go through the NAT Gateway in the same AZ, avoiding inter-AZ data transfer. However, even though you’ll save on the cost of inter-AZ data transfer, the implication of this setup is that you’ll incur the cost of an additional NAT Gateway in your architecture.

+

This recommended approach is depicted in the diagram below.

+

Recommended approach

+

Using VPC Endpoints

+

To further reduce costs in such architectures, you should use VPC Endpoints to establish connectivity between your workloads and AWS services. VPC Endpoints allow you to access AWS services from within a VPC without data/network packets traversing the Internet. All traffic is internal and stays within the AWS network. There are two types of VPC Endpoints: Interface VPC Endpoints (supported by many AWS services) and Gateway VPC Endpoints (only supported by S3 and DynamoDB).

+

Gateway VPC Endpoints

+

There are no hourly or data transfer costs associated with Gateway VPC Endpoints. When using Gateway VPC Endpoints, it's important to note that they are not extendable across VPC boundaries. They can't be used in VPC peering, VPN networking, or via Direct Connect.

+

Interface VPC Endpoint

+

VPC Endpoints have an hourly charge and, depending on the AWS service, may or may not have an additional charge associated with data processing via the underlying ENI. To reduce inter-AZ data transfer costs related to Interface VPC Endpoints, you can create a VPC Endpoint in each AZ. You can create multiple VPC Endpoints in the same VPC even if they're pointing to the same AWS service.

+

The diagram below shows Pods communicating with AWS services via VPC Endpoints.

+

VPC Endpoints

+

Data Transfer between VPCs

+

In some cases, you may have workloads in distinct VPCs (within the same AWS region) that need to communicate with each other. This can be accomplished by allowing traffic to traverse the public internet through Internet Gateways attached to the respective VPCs. Such communication can be enabled by deploying infrastructure components like EC2 instances, NAT Gateways or NAT instances in public subnets. However, a setup including these components will incur charges for processing/transferring data in and out of the VPCs. If the traffic to and from the separate VPCs is moving across AZs, then there will be an additional charge in the transfer of data. The diagram below depicts a setup that uses NAT Gateways and Internet Gateways to establish communication between workloads in different VPCs.

+

Between VPCs

+

VPC Peering Connections

+

To reduce costs for such use cases, you can make use of VPC Peering. With a VPC Peering connection, there are no data transfer charges for network traffic that stays within the same AZ. If traffic crosses AZs, there will be a cost incurred. Nonetheless, the VPC Peering approach is recommended for cost-effective communication between workloads in separate VPCs within the same AWS region. However, it’s important to note that VPC peering is primarily effective for 1:1 VPC connectivity because it doesn’t allow for transitive networking.

+

The diagram below is a high-level representation of workloads communication via a VPC peering connection.

+

Peering

+

Transitive Networking Connections

+

As pointed out in the previous section, VPC Peering connections do not allow for transitive networking connectivity. If you want to connect 3 or more VPCs with transitive networking requirements, then you should use a Transit Gateway (TGW). This will enable you to overcome the limits of VPC Peering or any operational overhead associated with having multiple VPC Peering connections between multiple VPCs. You are billed on an hourly basis and for data sent to the TGW. There is no destination cost associated with inter-AZ traffic that flows through the TGW.

+

The diagram below shows inter-AZ traffic flowing through a TGW between workloads in different VPCs but within the same AWS region.

+

Transitive

+

Using a Service Mesh

+

Service meshes offer powerful networking capabilities that can be used to reduce network related costs in your EKS cluster environments. However, you should carefully consider the operational tasks and complexity that a service mesh will introduce to your environment if you adopt one.

+

Restricting Traffic to Availability Zones

+

Using Istio’s Locality Weighted Distribution

+

Istio enables you to apply network policies to traffic after routing occurs. This is done using Destination Rules such as locality weighted distribution. Using this feature, you can control the weight (expressed as a percentage) of traffic that can go to a certain destination based on its origin. The source of this traffic can either be from an external (or public facing) load balancer or a Pod within the cluster itself. When all the Pod endpoints are available, the locality will be selected based on a weighted round-robin load balancing algorithm. In the case that certain endpoints are unhealthy or unavailable, the locality weight will be automatically adjusted to reflect this change in the available endpoints.

+
+

Note

+

Before implementing locality weighted distribution, you should start by understanding your network traffic patterns and the implications that the Destination Rule policy may have on your application’s behaviour. As such, it’s important to have distributed tracing mechanisms in place with tools such as AWS X-Ray or Jaeger.

+
+

The Istio Destination Rules detailed above can also be applied to manage traffic from a load balancer to Pods in your EKS cluster. Locality weighted distribution rules can be applied to a Service that receives traffic from a highly available load balancer (specifically the Ingress Gateway). These rules allow you to control how much traffic goes where based on its zonal origin - the load balancer in this case. If configured correctly, less egress cross-zone traffic will be incurred compared to a load balancer that distributes traffic evenly or randomly to Pod replicas in different AZs.

+

Below is a code block example of a Destination Rule resource in Istio. As can be seen below, this resource specifies weighted configurations for incoming traffic from 3 different AZs in the eu-west-1 region. These configurations declare that a majority of the incoming traffic (70% in this case) from a given AZ should be proxied to a destination in the same AZ from which it originates.

+
apiVersion: networking.istio.io/v1beta1
+kind: DestinationRule
+metadata:
+  name: express-test-dr
+spec:
+  host: express-test.default.svc.cluster.local
+  trafficPolicy:
+    loadBalancer:                        
+      localityLbSetting:
+        distribute:
+        - from: eu-west-1/eu-west-1a/    
+          to:
+            "eu-west-1/eu-west-1a/*": 70 
+            "eu-west-1/eu-west-1b/*": 20
+            "eu-west-1/eu-west-1c/*": 10
+        - from: eu-west-1/eu-west-1b/*    
+          to:
+            "eu-west-1/eu-west-1a/*": 20 
+            "eu-west-1/eu-west-1b/*": 70
+            "eu-west-1/eu-west-1c/*": 10
+        - from: eu-west-1/eu-west-1c/*    
+          to:
+            "eu-west-1/eu-west-1a/*": 20 
+            "eu-west-1/eu-west-1b/*": 10
+            "eu-west-1/eu-west-1c/*": 70**
+    connectionPool:
+      http:
+        http2MaxRequests: 10
+        maxRequestsPerConnection: 10
+    outlierDetection:
+      consecutiveGatewayErrors: 1
+      interval: 1m
+      baseEjectionTime: 30s
+
+
+

Note

+

The minimum weight that can be distributed destination is 1%. The reason for this is to maintain failover regions and zones in the case that the endpoints in the main destination become unhealthy or unavailable.

+
+

The diagram below depicts a scenario in which there is a highly available load balancer in the eu-west-1 region and locality weighted distribution is applied. The Destination Rule policy for this diagram is configured to send 60% of traffic coming from eu-west-1a to Pods in the same AZ, whereas 40% of the traffic from eu-west-1a should go to Pods in eu-west-1b.

+

Istio Traffic Control

+

Restricting Traffic to Availability Zones and Nodes

+

Using the Service Internal Traffic Policy with Istio

+

To mitigate network costs associated with external incoming traffic and internal traffic between Pods, you can combine Istio’s Destination Rules and the Kubernetes Service internal traffic policy. The way to combine Istio destination rules with the service internal traffic policy will largely depend on 3 things:

+
    +
  • The role of the microservices
  • +
  • Network traffic patterns across the microservices
  • +
  • How the microservices should be deployed across the Kubernetes cluster topology
  • +
+

The diagram below shows what the network flow would look like in the case of a nested request and how the aforementioned policies would control the traffic.

+

External and Internal traffic policy

+
    +
  1. The end user makes a request to APP A, which in turn makes a nested request to APP C. This request is first sent to a highly available load balancer, which has instances in AZ 1 and AZ 2 as the above diagram shows.
  2. +
  3. The external incoming request is then routed to the correct destination by the Istio Virtual Service.
  4. +
  5. After the request is routed, the Istio Destination Rule controls how much traffic goes to the respective AZs based on where it originated from (AZ 1 or AZ 2).
  6. +
  7. The traffic then goes to the Service for APP A, and is then proxied to the respective Pod endpoints. As shown in the diagram, 80% of the incoming traffic is sent to Pod endpoints in AZ 1, and 20% of the incoming traffic is sent to AZ 2.
  8. +
  9. APP A then makes an internal request to APP C. APP C's Service has an internal traffic policy enabled (internalTrafficPolicy``: Local).
  10. +
  11. The internal request from APP A (on NODE 1) to APP C is successful because of the available node-local endpoint for APP C.
  12. +
  13. The internal request from APP A (on NODE 3) to APP C fails because there are no available node-local endpoints for APP C. As the diagram shows, APP C has no replicas on NODE 3. ****
  14. +
+

The screenshots below are captured from a live example of this approach. The first set of screenshots demonstrate a successful external request to a graphql and a successful nested request from the graphql to a co-located orders replica on the node ip-10-0-0-151.af-south-1.compute.internal.

+

Before +Before results

+

With Istio, you can verify and export the statistics of any upstream clusters and endpoints that your proxies are aware of. This can help provide a picture of the network flow as well as the share of distribution among the services of a workload. Continuing with the same example, the orders endpoints that the graphql proxy is aware of can be obtained using the following command:

+
kubectl exec -it deploy/graphql -n ecommerce -c istio-proxy -- curl localhost:15000/clusters | grep orders 
+
+
...
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_error::0**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_success::119**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_timeout::0**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_total::119**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**health_flags::healthy**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**region::af-south-1**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**zone::af-south-1b**
+...
+
+

In this case, the graphql proxy is only aware of the orders endpoint for the replica that it shares a node with. If you remove the internalTrafficPolicy: Local setting from the orders Service, and re-run a command like the one above, then the results will return all the endpoints of the replicas spread across the different nodes. Furthermore, by examining the rq_total for the respective endpoints, you'll notice a relatively even share in network distribution. Consequently, if the endpoints are associated with upstream services running in different AZs, then this network distribution across zones will result in higher costs.

+

As mentioned in a previous section above, you can co-locate frequently communicating Pods by making use of pod-affinity.

+
...
+spec:
+...
+  template:
+    metadata:
+      labels:
+        app: graphql
+        role: api
+        workload: ecommerce
+    spec:
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+              - key: app
+                operator: In
+                values:
+                - orders
+            topologyKey: "kubernetes.io/hostname"
+      nodeSelector:
+        managedBy: karpenter
+        billing-team: ecommerce
+...
+
+

When the graphql and orders replicas don't co-exist on the same node (ip-10-0-0-151.af-south-1.compute.internal), the first request to graphql is successful as noted by the 200 response code in the Postman screenshot below, whereas the second nested request from graphql to orders fails with a 503 response code.

+

After +After results

+

Additional Resources

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cost_optimization/cost_opt_observability/index.html b/cost_optimization/cost_opt_observability/index.html new file mode 100644 index 000000000..2c8b8068f --- /dev/null +++ b/cost_optimization/cost_opt_observability/index.html @@ -0,0 +1,2820 @@ + + + + + + + + + + + + + + + + + + + + + + + Observability - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Cost Optimization - Observability

+

Introduction

+

Observability tools help you efficiently detect, remediate and investigate your workloads. The cost of telemetry data naturally increases as your use of EKS increases. At times, it can be challenging to balance your operational needs and measuring what matters to your business and keeping observability costs in check. This guide focuses on cost optimization strategies for the three pillars of observability: logs, metrics and traces. Each of these best practices can be applied independently to fit your organization’s optimization goals.

+

Logging

+

Logging plays a vital role in monitoring and troubleshooting the applications in your cluster. There are several strategies that can be employed to optimize logging costs. The best practice strategies listed below include examining your log retention policies to implement granular controls on how long log data is kept, sending log data to different storage options based on importance, and utilizing log filtering to narrow down the types of logs messages that are stored. Efficiently managing log telemetry can lead to cost savings for your environments.

+

EKS Control Plane

+

Optimize Your Control Plane Logs

+

The Kubernetes control plane is a set of components that manage the clusters and these components send different types of information as log streams to a log group in Amazon CloudWatch. While there are benefits to enabling all control plane log types, you should be aware of the information in each log and the associated costs to storing all the log telemetry. You are charged for the standard CloudWatch Logs data ingestion and storage costs for logs sent to Amazon CloudWatch Logs from your clusters. Before enabling them, evaluate whether each log stream is necessary.

+

For example, in non-production clusters, selectively enable specific log types, such as the api server logs, only for analysis and deactivate afterward. But for production clusters, where you might not be able to reproduce events, and resolving issues requires more log information, then you can enable all log types. Further control plane cost optimization implementation details are in this blog post.

+

Stream Logs to S3

+

Another cost optimization best practice is streaming control plane logs to S3 via CloudWatch Logs subscriptions. Leveraging CloudWatch Logs subscriptions allows you to selectively forward logs to S3 which provides more cost efficient long term storage compared to retaining logs indefinitely in CloudWatch. For example, for production clusters, you can create a critical log group and leverage subscriptions to stream these logs to S3 after 15 days. This will ensure you have have quick access to the logs for analysis but also save on cost by moving logs to a more cost efficient storage.

+
+

Attention

+

As of 9/5/2023 EKS logs are classified as Vended Logs in Amazon CloudWatch Logs. Vended Logs are specific AWS service logs natively published by AWS services on behalf of the customer and available at volume discount pricing. Please visit the Amazon CloudWatch pricing page to learn more about Vended Logs pricing.

+
+

EKS Data Plane

+

Log Retention

+

Amazon CloudWatch’s default retention policy is to keep logs indefinitely and never expire, incurring storage costs applicable to your AWS region. In order to reduce the storage costs, you can customize the retention policy for each log group based on your workload requirements.

+

In a development environment, a lengthy retention period may not be necessary. But in a production environment, you can set a longer retention policy to meet troubleshooting, compliance, and capacity planning requirements. For example, if you are running an e-commerce application during the peak holiday season the system is under heavier load and issues can arise that may not be immediately noticeable, you will want to set a longer log retention for detailed troubleshooting and post event analysis.

+

You can configure your retention periods in the AWS CloudWatch console or AWS API with the duration from 1 day to 10 years based on each log group. Having a flexible retention period can save log storage costs, while also maintaining critical logs.

+

Log Storage Options

+

Storage is a large driver of observability costs therefore it is crucial to optimize your log storage strategy. Your strategies should align with your workloads requirements while maintaining performance and scalability. One strategy to reduce the costs of storing logs is to leverage AWS S3 buckets and its different storage tiers.

+

Forward logs directly to S3

+

Consider forwarding less critical logs, such as development environments, directly to S3 instead of Cloudwatch. This can have an immediate impact on log storage costs. One option is to forward the logs straight to S3 using Fluentbit. You define this in the [OUTPUT] section, the destination where FluentBit transmits container logs for retention. Review additional configurations parameter here.

+
[OUTPUT]
+        Name eks_to_s3
+        Match application.* 
+        bucket $S3_BUCKET name
+        region us-east-2
+        store_dir /var/log/fluentbit
+        total_file_size 30M
+        upload_timeout 3m
+
+

Forward logs to CloudWatch only for short term analysis

+

For more critical logs, such as a production environments where you might need to perform immediate analysis on the data, consider forwarding the logs to CloudWatch. You define this in the [OUTPUT] section, the destination where FluentBit transmits container logs for retention. Review additional configurations parameter here.

+
[OUTPUT]
+        Name eks_to_cloudwatch_logs
+        Match application.*
+        region us-east-2
+        log_group_name fluent-bit-cloudwatch
+        log_stream_prefix from-fluent-bit-
+        auto_create_group On
+
+

However, this will not have an instant affect on your cost savings. For additional savings, you will have to export these logs to Amazon S3.

+

Export to Amazon S3 from CloudWatch

+

For storing Amazon CloudWatch logs long term, we recommend exporting your Amazon EKS CloudWatch logs to Amazon Simple Storage Service (Amazon S3). You can forward the logs to Amazon S3 bucket by creating an export task via the Console or the API. After you have done so, Amazon S3 presents many options to further reduce cost. You can define your own Amazon S3 Lifecycle rules to move your logs to a storage class that a fits your needs, or leverage the Amazon S3 Intelligent-Tiering storage class to have AWS automatically move data to long-term storage based on your usage pattern. Please refer to this blog for more details. For example, for your production environment logs reside in CloudWatch for more than 30 days then exported to Amazon S3 bucket. You can then use Amazon Athena to query the data in Amazon S3 bucket if you need to refer back to the logs at a later time.

+

Reduce Log Levels

+

Practice selective logging for your application. Both your applications and nodes output logs by default. For your application logs, adjust the log levels to align with the criticality of the workload and environment. For example, the java application below is outputting INFO logs which is the typical default application configuration and depending on the code can result in a high volume of log data.

+
import org.apache.log4j.*;
+
+public class LogClass {
+   private static org.apache.log4j.Logger log = Logger.getLogger(LogClass.class);
+
+   public static void main(String[] args) {
+      log.setLevel(Level.INFO);
+
+      log.debug("This is a DEBUG message, check this out!");
+      log.info("This is an INFO message, nothing to see here!");
+      log.warn("This is a WARN message, investigate this!");
+      log.error("This is an ERROR message, check this out!");
+      log.fatal("This is a FATAL message, investigate this!");
+   }
+}
+
+

In a development environment, change your log level to DEBUG, as this can help you debug issues or catch potential ones before they get into production.

+
      log.setLevel(Level.DEBUG);
+
+

In a production environment, consider modifying your log level to ERROR or FATAL. This will output log only when your application has errors, reducing the log output and help you focus on important data about your application status.

+
      log.setLevel(Level.ERROR);
+
+

You can fine tune various Kubernetes components log levels. For example, if you are using Bottlerocket as your EKS Node operating system, there are configuration settings that allow you to adjust the kubelet process log level. A snippet of this configuration setting is below. Note the default log level of 2 which adjusts the logging verbosity of the kubelet process.

+
[settings.kubernetes]
+log-level = "2"
+image-gc-high-threshold-percent = "85"
+image-gc-low-threshold-percent = "80"
+
+

For a development environment, you can set the log level greater than 2 in order to view additional events, this is good for debugging. For a production environment, you can set the level to 0 in order to view only critical events.

+

Leverage Filters

+

When using a default EKS Fluentbit configuration to send container logs to Cloudwatch, FluentBit captures and send ALL application container logs enriched with Kubernetes metadata to Cloudwatch as shown in the [INPUT] configuration block below.

+
    [INPUT]
+        Name                tail
+        Tag                 application.*
+        Exclude_Path        /var/log/containers/cloudwatch-agent*, /var/log/containers/fluent-bit*, /var/log/containers/aws-node*, /var/log/containers/kube-proxy*
+        Path                /var/log/containers/*.log
+        Docker_Mode         On
+        Docker_Mode_Flush   5
+        Docker_Mode_Parser  container_firstline
+        Parser              docker
+        DB                  /var/fluent-bit/state/flb_container.db
+        Mem_Buf_Limit       50MB
+        Skip_Long_Lines     On
+        Refresh_Interval    10
+        Rotate_Wait         30
+        storage.type        filesystem
+        Read_from_Head      ${READ_FROM_HEAD}
+
+

The [INPUT] section above is ingesting all the container logs. This can generate a large amount of data that might not be necessary. Filtering out this data can reduce the amount of log data sent to CloudWatch therefore reducing your cost. You can apply a filter to you logs before it outputs to CloudWatch. Fluentbit defines this in the [FILTER] section. For example, filtering out the Kubernetes metadata from being appended to log events can reduce your log volume.

+
    [FILTER]
+        Name                nest
+        Match               application.*
+        Operation           lift
+        Nested_under        kubernetes
+        Add_prefix          Kube.
+
+    [FILTER]
+        Name                modify
+        Match               application.*
+        Remove              Kube.<Metadata_1>
+        Remove              Kube.<Metadata_2>
+        Remove              Kube.<Metadata_3>
+
+    [FILTER]
+        Name                nest
+        Match               application.*
+        Operation           nest
+        Wildcard            Kube.*
+        Nested_under        kubernetes
+        Remove_prefix       Kube.
+
+

Metrics

+

Metrics provide valuable information regarding the performance of your system. By consolidating all system-related or available resource metrics in a centralized location, you gain the capability to compare and analyze performance data. This centralized approach enables you to make more informed strategic decisions, such as scaling up or scaling down resources. Additionally, metrics play a crucial role in assessing the health of resources, allowing you to take proactive measures when necessary. Generally observability costs scale with telemetry data collection and retention. Below are a few strategies you can implement to reduce the cost of metric telemetry: collecting only metrics that matter, reducing the cardinality of your telemetry data, and fine tuning the granularity of your telemetry data collection.

+

Monitor what matters and collect only what you need

+

The first cost reduction strategy is to reduce the number of metrics you are collecting and in turn, reduce retention costs.

+
    +
  1. Begin by working backwards from your and/or your stakeholder’s requirements to determine the metrics that are most important. Success metrics are different for everyone! Know what good looks like and measure for it.
  2. +
  3. Consider diving deep into the workloads you are supporting and identifying its Key Performance Indicators (KPIs) a.k.a 'Golden Signals'. These should align to business and stake-holder requirements. Calculating SLIs, SLOs, and SLAs using Amazon CloudWatch and Metric Math is crucial for managing service reliability. Follow the best practices outlined in this guide to effectively monitor and maintain the performance of your EKS environment.
  4. +
  5. Then continue through the different layers of infrastructure to connect and correlate EKS cluster, node and additional infrastructure metrics to your workload KPIs. Store your business metrics and operational metrics in a system where you can correlate them together and draw conclusions based on observed impacts to both.
  6. +
  7. EKS exposes metrics from the control plane, cluster kube-state-metrics, pods, and nodes. The relevance of all these metrics is dependent on your needs, however it’s likely that you will not need every single metric across the different layers. You can use this EKS essential metrics guide as a baseline for monitoring the overall health of an EKS cluster and your workloads.
  8. +
+

Here is an example prometheus scrape config where we are using the relabel_config to keep only kubelet metrics and metric_relabel_config to drop all container metrics.

+
  kubernetes_sd_configs:
+  - role: endpoints
+    namespaces:
+      names:
+      - kube-system
+  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+  tls_config:
+    insecure_skip_verify: true
+  relabel_configs:
+  - source_labels: [__meta_kubernetes_service_label_k8s_app]
+    regex: kubelet
+    action: keep
+
+  metric_relabel_configs:
+  - source_labels: [__name__]
+    regex: container_(network_tcp_usage_total|network_udp_usage_total|tasks_state|cpu_load_average_10s)
+    action: drop
+
+

Reduce cardinality where applicable

+

Cardinality refers to the uniqueness of the data values in combination with its dimensions (eg. prometheus labels) for a specific metrics set. High cardinality metrics have many dimensions and each dimension metric combination has higher uniqueness. Higher cardinality results in larger metric telemetry data size and storage needs which increases cost.

+

In the high cardinality example below, we see that the Metric, Latency, has Dimensions, RequestID, CustomerID, and Service and each Dimension has many unique values. Cardinality is the measure of the combination of the number of possible values per Dimension. In Prometheus, each set of unique dimensions/labels are consider as a new metric, therefore high cardinality means more metrics.

+

high cardinality

+

In EKS environments with many metrics and dimensions/labels per metric (Cluster, Namespace, Service, Pod, Container, etc), the cardinality tends to grow. In order to optimize cost, consider the cardinality of the metrics you are collecting carefully. For example, if you are aggregating a specific metric for visualization at the cluster level, then you can drop additional labels that are at a lower layer such as the namespace label.

+

In order to identify high cardinality metrics in prometheus you can run the following PROMQL query to determine which scrape targets have the highest number of metrics (cardinality):

+
topk_max(5, max_over_time(scrape_samples_scraped[1h]))
+
+

and the following PROMQL query can help you determine which scrape targets have the highest metrics churn (how many new metrics series were created in a given scrape) rates :

+
topk_max(5, max_over_time(scrape_series_added[1h]))
+
+

If you are using grafana you can use Grafana Lab’s Mimirtool to analyze your grafana dashboards and prometheus rules to identify unused high-cardinality metrics. Follow this guide on how to use the mimirtool analyze and mimirtool analyze prometheus commands to identify active metrics which are not referenced in your dashboards.

+

Consider metric granularity

+

Collecting metrics at a higher granularity like every second vs every minute can have a big impact on how much telemetry is collected and stored which increases cost. Determine sensible scrape or metrics collection intervals that balance between enough granularity to see transient issues and low enough to be cost effective. Decrease granularity for metrics that are used for capacity planning and larger time window analysis.

+

Below is a snippet from the default AWS Distro for Opentelemetry (ADOT) EKS Addon Collector configuration.

+
+

Attention

+

the global prometheus scrape interval is set to 15s. This scrape interval can be increased resulting in a decrease in the amount of metric data collected in prometheus.

+
+
apiVersion: opentelemetry.io/v1alpha1
+kind: OpenTelemetryCollector
+metadata:
+  name: my-collector-amp
+
+...
+
+  config: |
+    extensions:
+      sigv4auth:
+        region: "<YOUR_AWS_REGION>"
+        service: "aps"
+
+    receivers:
+      #
+      # Scrape configuration for the Prometheus Receiver
+      # This is the same configuration used when Prometheus is installed using the community Helm chart
+      # 
+      prometheus:
+        config:
+          global:
+  scrape_interval: 15s
+            scrape_timeout: 10s
+
+

Tracing

+

The primary cost associated with tracing stem from trace storage generation. With tracing, the aim is to gather sufficient data to diagnose and understand performance aspects. However, as X-Ray traces costs are based on data forwarded to to X-Ray, erasing traces after it has been forward will not reduce your costs. Let’s review ways to lower your costs for tracing while maintaining data for you to perform proper analysis.

+

Apply Sampling rules

+

The X-Ray sampling rate is conservative by default. Define sampling rules where you can control the amount of data that you gather. This will improve performance efficiency while reducing costs. By decreasing the sampling rate, you can collect traces from the request only what your workloads needs while maintaining a lower cost structure.

+

For example, you have java application that you want to debug the traces of all the requests for 1 problematic route.

+

Configure via the SDK to load sampling rules from a JSON document

+
{
+"version": 2,
+  "rules": [
+    {
+"description": "debug-eks",
+      "host": "*",
+      "http_method": "PUT",
+      "url_path": "/history/*",
+      "fixed_target": 0,
+      "rate": 1,
+      "service_type": "debug-eks"
+    }
+  ],
+  "default": {
+"fixed_target": 1,
+    "rate": 0.1
+  }
+}
+
+

Via the Console

+

console

+

Apply Tail Sampling with AWS Distro for OpenTelemetry (ADOT)

+

ADOT Tail Sampling allows you to control the volume of traces ingested in the service. However, Tail Sampling allows you to define the sampling policies after all the spans in the request have been completed instead of at the beginning. This further limits the amount of raw data transferred to CloudWatch, hence reducing cost.

+

For example, if you’re sampling 1% of traffic to a landing page and 10% of the requests to a payment page this might leave you with 300 traces for an 30 minute period. With an ADOT Tail Sampling rule of that filters specific errors, you could be left with 200 traces which decreases the number of traces stored.

+
processors:
+  groupbytrace:
+    wait_duration: 10s
+    num_traces: 300 
+    tail_sampling:
+    decision_wait: 1s # This value should be smaller than wait_duration
+    policies:
+      - ..... # Applicable policies**
+  batch/tracesampling:
+    timeout: 0s # No need to wait more since this will happen in previous processors
+    send_batch_max_size: 8196 # This will still allow us to limit the size of the batches sent to subsequent exporters
+
+service:
+  pipelines:
+    traces/tailsampling:
+      receivers: [otlp]
+      processors: [groupbytrace, tail_sampling, batch/tracesampling]
+      exporters: [awsxray]
+
+

Leverage Amazon S3 Storage options

+

You should leverage AWS S3 bucket and its different storage classes to store the traces. Export traces to S3 before the retention period expires. Use Amazon S3 Lifecycle rules to move the trace data to the storage class that meets your requirements.

+

For example, if you have traces that are 90 days old, Amazon S3 Intelligent-Tiering can automatically move the data to long-term storage based on your usage pattern. You can use Amazon Athena to query the data in Amazon S3 if you need to refer back to the traces at a later time. This can further reduce your cost for distributed tracing.

+

Additional Resources:

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cost_optimization/cost_opt_storage/index.html b/cost_optimization/cost_opt_storage/index.html new file mode 100644 index 000000000..65ac7051c --- /dev/null +++ b/cost_optimization/cost_opt_storage/index.html @@ -0,0 +1,2597 @@ + + + + + + + + + + + + + + + + + + + + + + + Storage - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Cost Optimization - Storage

+

Overview

+

There are scenarios where you may want to run applications that need to preserve data for a short or long term basis. For such use cases, volumes can be defined and mounted by Pods so that their containers can tap into different storage mechanisms. Kubernetes supports different types of volumes for ephemeral and persistent storage. The choice of storage largely depends on application requirements. For each approach, there are cost implications, and the practices detailed below which will help you accomplish cost efficiency for workloads needing some form of storage in your EKS environments.

+

Ephemeral Volumes

+

Ephemeral volumes are for applications that require transient local volumes but don't require data to be persisted after restarts. Examples of this include requirements for scratch space, caching, and read-only input data like configuration data and secrets. You can find more details of Kubernetes ephemeral volumes here. Most of ephemeral volumes (e.g. emptyDir, configMap, downwardAPI, secret, hostpath) are backed by locally-attached writable devices (usually the root disk) or RAM, so it's important to choose the most cost efficient and performant host volume.

+

Using EBS Volumes

+

We recommend starting with gp3 as the host root volume. It is the latest general purpose SSD volume offered by Amazon EBS and also offers a lower price (up to 20%) per GB compared to gp2 volumes.

+

Using Amazon EC2 Instance Stores

+

Amazon EC2 instance stores provide temporary block-level storage for your EC2 instances. The storage provided by EC2 instance stores is accessible through disks that are physically attached to the hosts. Unlike Amazon EBS, you can only attach instance store volumes when the instance is launched, and these volumes only exist during the lifetime of the instance. They cannot be detached and re-attached to other instances. You can learn more about Amazon EC2 instance stores here. There are no additional fees associated with an instance store volume. This makes them (instance store volumes) more cost efficient than the general EC2 instances with large EBS volumes.

+

To use local store volumes in Kubernetes, you should partition, configure, and format the disks using the Amazon EC2 user-data so that volumes can be mounted as a HostPath in the pod spec. Alternatively, you can leverage the Local Persistent Volume Static Provisioner to simplify local storage management. The Local Persistent Volume static provisioner allows you to access local instance store volumes through the standard Kubernetes PersistentVolumeClaim (PVC) interface. Furthermore, it will provision PersistentVolumes (PVs) that contains node affinity information to schedule Pods to the correct nodes. Although it uses Kubernetes PersistentVolumes, EC2 instance store volumes are ephemeral in nature. Data written to ephemeral disks is only available during the instance’s lifetime. When the instance is terminated, so is the data. Please refer to this blog for more details.

+

Keep in mind that when using Amazon EC2 instance store volumes, the total IOPS limit is shared with the host and it binds Pods to a specific host. You should thoroughly review your workload requirements before adopting Amazon EC2 instance store volumes.

+

Persistent Volumes

+

Kubernetes is typically associated with running stateless applications. However, there are scenarios where you may want to run microservices that need to preserve persistent data or information from one request to the next. Databases are a common example for such use cases. However, Pods, and the containers or processes inside them, are ephemeral in nature. To persist data beyond the lifetime of a Pod, you can use PVs to define access to storage at a specific location that is independent from the Pod. The costs associated with PVs is highly dependent on the type of storage being used and how applications are consuming it.

+

There are different types of storage options that support Kubernetes PVs on Amazon EKS listed here. The storage options covered below are Amazon EBS, Amazon EFS, Amazon FSx for Lustre, Amazon FSx for NetApp ONTAP.

+

Amazon Elastic Block Store (EBS) Volumes

+

Amazon EBS volumes can be consumed as Kubernetes PVs to provide block-level storage volumes. These are well suited for databases that rely on random reads & writes and throughput-intensive applications that perform long, continuous reads and writes. The Amazon Elastic Block Store Container Storage Interface (CSI) driver allows Amazon EKS clusters to manage the lifecycle of Amazon EBS volumes for persistent volumes. The Container Storage Interface enables and facilitates interaction between Kubernetes and a storage system. When a CSI driver is deployed to your EKS cluster, you can access it’s capabilities through the native Kubernetes storage resources such as Persistent Volumes (PVs), Persistent Volume Claims (PVCs) and Storage Classes (SCs). This link provides practical examples of how to interact with Amazon EBS volumes with Amazon EBS CSI driver.

+

Choosing the right volume

+

We recommend using the latest generation of block storage (gp3) as it provides the right balance between price and performance. It also allows you to scale volume IOPS and throughput independently of volume size without needing to provision additional block storage capacity. If you’re currently using gp2 volumes, we highly recommend migrating to gp3 volumes. This blog explains how to migrate from gp2 on gp3 on Amazon EKS clusters.

+

When you have applications that require higher performance and need volumes larger than what a single gp3 volume can support, you should consider using io2 block express. This type of storage is ideal for your largest, most I/O intensive, and mission critical deployment such as SAP HANA or other large databases with low latency requirements. Keep in mind that an instance's EBS performance is bounded by the instance's performance limits, so not all the instances support io2 block express volumes. You can check the supported instance types and other considerations in this doc.

+

A single gp3 volume can support up to up to 16,000 max IOPS, 1,000 MiB/s max throughput, max 16TiB. The latest generation of Provisioned IOPS SSD volume that provides up to 256,000 IOPS, 4,000 MiB/s, throughput, and 64TiB.

+

Among these options, you should best tailor your storage performance and cost to the needs of your applications.

+

Monitor and optimize over time

+

It's important to understand your application's baseline performance and monitor it for selected volumes to check if it's meeting your requirements/expectations or if it's over-provisioned (e.g. a scenario where provisioned IOPS are not being fully utilized).

+

Instead of allocating a large volume from the beginning, you can gradually increase the size of the volume as you accumulate data. You can dynamically re-size volumes using the volume resizing feature in the Amazon Elastic Block Store CSI driver (aws-ebs-csi-driver). Keep in mind that you can only increase the EBS volume size.

+

To identify and remove any dangling EBS volumes, you can use AWS trusted advisor’s cost optimization category. This feature helps you identify unattached volumes or volumes with very low write activity for a period of time. There is a cloud-native open-source, read-only tool called Popeye that scans live Kubernetes clusters and reports potential issues with deployed resources and configurations. For example, it can scan for unused PVs and PVCs and check whether they are bound or whether there is any volume mount error.

+

For a deep dive on monitoring, please refer to the EKS cost optimization observability guide.

+

One other option you can consider is the AWS Compute Optimizer Amazon EBS volume recommendations. This tool automatically identifies the optimal volume configuration and correct level of performance needed. For example, it can be used for optimal settings pertaining to provisioned IOPS, volume sizes, and types of EBS volumes based on the maximum utilization during the past 14 days. It also quantifies the potential monthly cost savings derived from its recommendations. You can review this blog for more details.

+

Backup retention policy

+

You can back up the data on your Amazon EBS volumes by taking point-in-time snapshots. The Amazon EBS CSI driver supports volume snapshots. You can learn how to create a snapshot and restore an EBS PV using the steps outlined here.

+

Subsequent snapshots are incremental backups, meaning that only the blocks on the device that have changed after your most recent snapshot are saved. This minimizes the time required to create the snapshot and saves on storage costs by not duplicating data. However, growing the number of old EBS snapshots without a proper retention policy can cause unexpected costs when operating at scale. If you’re directly backing up Amazon EBS volumes through AWS API, you can leverage Amazon Data Lifecycle Manager (DLM) that provides an automated, policy-based lifecycle management solution for Amazon Elastic Block Store (EBS) Snapshots and EBS-backed Amazon Machine Images (AMIs). The console makes it easier to automate the creation, retention, and deletion of EBS Snapshots and AMIs.

+
+

Note

+

There is currently no way to make use of Amazon DLM via the Amazon EBS CSI driver.

+
+

In a Kubernetes environment, you can leverage an open-source tool called Velero to backup your EBS Persistent Volumes. You can set a TTL flag when scheduling the job to expire backups. Here is a guide from Velero as an example.

+

Amazon Elastic File System (EFS)

+

Amazon Elastic File System (EFS) is a serverless, fully elastic file system that lets you share file data using standard file system interface and file system semantics for a broad spectrum of workloads and applications. Examples of workloads and applications include Wordpress and Drupal, developer tools like JIRA and Git, and shared notebook system such as Jupyter as well as home directories.

+

One of main benefits of Amazon EFS is that it can be mounted by multiple containers spread across multiple nodes and multiple availability zones. Another benefit is that you only pay for the storage you use. EFS file systems will automatically grow and shrink as you add and remove files which eliminates the need for capacity planning.

+

To use Amazon EFS in Kubernetes, you need to use the Amazon Elastic File System Container Storage Interface (CSI) Driver, aws-efs-csi-driver. Currently, the driver can dynamically create access points. However, the Amazon EFS file system has to be provisioned first and provided as an input to the Kubernetes storage class parameter.

+

Choosing the right EFS storage class

+

Amazon EFS offers four storage classes.

+

Two standard storage classes:

+ +

Two one-zone storage classes:

+ +

The Infrequent Access (IA) storage classes are cost-optimized for files that are not accessed every day. With Amazon EFS lifecycle management, you can move files that have not been accessed for the duration of the lifecycle policy (7, 14, 30, 60, or 90 days) to the IA storage classes which can reduce the storage cost by up to 92 percent compared to EFS Standard and EFS One Zone storage classes respectively.

+

With EFS Intelligent-Tiering, lifecycle management monitors the access patterns of your file system and automatically move files to the most optimal storage class.

+
+

Note

+

aws-efs-csi-driver currently doesn’t have a control on changing storage classes, lifecycle management or Intelligent-Tiering. Those should be setup manually in the AWS console or through the EFS APIs.

+
+
+

Note

+

aws-efs-csi-driver isn’t compatible with Window-based container images.

+
+
+

Note

+

There is a known memory issue when vol-metrics-opt-in (to emit volume metrics) is enabled due to the DiskUsage function that consumes an amount of memory that is proportional to the size of your filesystem. Currently, we recommend to disable the --vol-metrics-opt-in option on large filesystems to avoid consuming too much memory. Here is a github issue link for more details.

+
+

Amazon FSx for Lustre

+

Lustre is a high-performance parallel file system commonly used in workloads requiring throughput up to hundreds of GB/s and sub-millisecond per-operation latencies. It’s used for scenarios such as machine learning training, financial modeling, HPC, and video processing. Amazon FSx for Lustre provides a fully managed shared storage with the scalability and performance, seamlessly integrated with Amazon S3.

+

You can use Kubernetes persistent storage volumes backed by FSx for Lustre using the FSx for Lustre CSI driver from Amazon EKS or your self-managed Kubernetes cluster on AWS. See the Amazon EKS documentation for more details and examples.

+ +

It's recommended to link a highly durable long-term data repository residing on Amazon S3 with your FSx for Lustre file system. Once linked, large datasets are lazy-loaded as needed from Amazon S3 to FSx for Lustre file systems. You can also run your analyses and your results back to S3, and then delete your [Lustre] file system.

+

Choosing the right deployment and storage options

+

FSx for Lustre provides different deployment options. The first option is called scratch and it doesn’t replicate data, while the second option is called persistent which, as the name implies, persists data.

+

The first option (scratch) can be used to reduce the cost of temporary shorter-term data processing. The persistent deployment option is designed for longer-term storage that automatically replicates data within an AWS Availability Zone. It also supports both SSD and HDD storage.

+

You can configure the desired deployment type under parameters in the FSx for lustre filesystem’s Kubernetes StorageClass. Here is an link that provides sample templates.

+
+

Note

+

For latency-sensitive workloads or workloads requiring the highest levels of IOPS/throughput, you should choose SSD storage. For throughput-focused workloads that aren’t latency-sensitive, you should choose HDD storage.

+
+

Enable data compression

+

You can also enable data compression on your file system by specifying “LZ4” as the Data Compression Type. Once it’s enabled, all newly-written files will be automatically compressed on FSx for Lustre before they are written to disk and uncompressed when they are read. LZ4 data compression algorithm is lossless so the original data can be fully reconstructed from the compressed data.

+

You can configure the data compression type as LZ4 under parameters in the FSx for lustre filesystem’s Kubernetes StorageClass. Compression is disabled when the value is set to NONE, which is default. This link provides sample templates.

+
+

Note

+

Amazon FSx for Lustre isn’t compatible with Window-based container images.

+
+

Amazon FSx for NetApp ONTAP

+

Amazon FSx for NetApp ONTAP is a fully managed shared storage built on NetApp’s ONTAP file system. FSx for ONTAP provides feature-rich, fast, and flexible shared file storage that’s broadly accessible from Linux, Windows, and macOS compute instances running in AWS or on premises.

+

Amazon FSx for NetApp ONTAP supports two tiers of storage: 1/primary tier and 2/capacity pool tier.

+

The primary tier is a provisioned, high-performance SSD-based tier for active, latency-sensitive data. The fully elastic capacity pool tier is cost-optimized for infrequently accessed data, automatically scales as data is tiered to it, and offers virtually unlimited petabytes of capacity. You can enable data compression and deduplication on capacity pool storage and further reduce the amount of storage capacity your data consumes. NetApp’s native, policy-based FabricPool feature continually monitors data access patterns, automatically transferring data bidirectionally between storage tiers to optimize performance and cost.

+

NetApp's Astra Trident provides dynamic storage orchestration using a CSI driver which allows Amazon EKS clusters to manage the lifecycle of persistent volumes PVs backed by Amazon FSx for NetApp ONTAP file systems. To get started, see Use Astra Trident with Amazon FSx for NetApp ONTAP in the Astra Trident documentation.

+

Other considerations

+

Minimize the size of container image

+

Once containers are deployed, container images are cached on the host as multiple layers. By reducing the size of images, the amount of storage required on the host can be reduced.

+

By using slimmed-down base images such as scratch images or distroless container images (that contain only your application and its runtime dependencies) from the beginning, you can reduce storage cost in addition to other ancillary benefits such as a reducing the attack surface area and shorter image pull times.

+

You should also consider using open source tools, such as Slim.ai that provides an easy, secure way to create minimal images.

+

Multiple layers of packages, tools, application dependencies, libraries can easily bloat the container image size. By using multi-stage builds, you can selectively copy artifacts from one stage to another, excluding everything that isn’t necessary from the final image. You can check more image-building best practices here.

+

Another thing to consider is how long to persist cached images. You may want to clean up the stale images from the image cache when a certain amount of disk is utilized. Doing so will help make sure you have enough space for the host’s operation. By default, the kubelet performs garbage collection on unused images every five minutes and on unused containers every minute.

+

To configure options for unused container and image garbage collection, tune the kubelet using a configuration file and change the parameters related to garbage collection using the KubeletConfiguration resource type.

+

You can learn more about it in the Kubernetes documentation.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cost_optimization/cost_optimization_index/index.html b/cost_optimization/cost_optimization_index/index.html new file mode 100644 index 000000000..2da7c620f --- /dev/null +++ b/cost_optimization/cost_optimization_index/index.html @@ -0,0 +1,2087 @@ + + + + + + + + + + + + + + + + + + + + + + + Home - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Amazon EKS Best Practices Guide for Cost Optimization

+

Cost Optimization is achieving your business outcomes at the lowest price point. By following the documentation in this guide you will optimize your Amazon EKS workloads.

+

General Guidelines

+

In the cloud, there are a number of general guidelines that can help you achieve cost optimization of your microservices: ++ Ensure that workloads running on Amazon EKS are independent of specific infrastructure types for running your containers, this will give greater flexibility with regards to running them on the least expensive types of infrastructure. While using Amazon EKS with EC2, there can be exceptions when we have workloads that require specific type of EC2 Instance types like requiring a GPU or other instance types, due to the nature of the workload. ++ Select optimally profiled container instances — profile your production or pre-production environments and monitor critical metrics like CPU and memory, using services like Amazon CloudWatch Container Insights for Amazon EKS or third party tools that are available in the Kubernetes ecosystem. This will ensure that we can allocate the right amount of resources and avoid wastage of resources. ++ Take advantage of the different purchasing options that are available in AWS for running EKS with EC2, e.g. On-Demand, Spot and Savings Plan.

+

EKS Cost Optimization Best Practices

+

There are three general best practice areas for cost optimization in the cloud:

+
    +
  • Cost-effective resources (Auto Scaling, Down Scaling, Policies and Purchasing Options)
  • +
  • Expenditure awareness (Using AWS and third party tools)
  • +
  • Optimizing over time (Right Sizing)
  • +
+

As with any guidance there are trade-offs. Ensure you work with your organization to understand the priorities for this workload and which best practices are most important.

+

How to use this guide

+

This guide is meant for devops teams who are responsible for implementing and managing the EKS clusters and the workloads they support. The guide is organized into different best practice areas for easier consumption. Each topic has a list of recommendations, tools to use and best practices for cost optimization of your EKS clusters. The topics do not need to read in a particular order.

+

Key AWS Services and Kubernetes features

+

Cost optimization is supported by the following AWS services and features: ++ EC2 Instance types, Savings Plan (and Reserved Instances) and Spot Instances, at different prices. ++ Auto Scaling along with Kubernetes native Auto Scaling policies. Consider Savings Plan (Previously Reserved Instances) for predictable workloads. Use managed data stores like EBS and EFS, for elasticity and durability of the application data. ++ The Billing and Cost Management console dashboard along with AWS Cost Explorer provides an overview of your AWS usage. Use AWS Organizations for granular billing details. Details of several third party tools have also been shared. ++ Amazon CloudWatch Container Metrics provides metrics around usage of resources by the EKS cluster. In addition to the Kubernetes dashboard, there are several tools in the Kubernetes ecosystem that can be used to reduce wastage.

+

This guide includes a set of recommendations that you can use to improve the cost optimization of your Amazon EKS cluster.

+

Feedback

+

This guide is being released on GitHub so as to collect direct feedback and suggestions from the broader EKS/Kubernetes community. If you have a best practice that you feel we ought to include in the guide, please file an issue or submit a PR in the GitHub repository. Our intention is to update the guide periodically as new features are added to the service or when a new best practice evolves.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cost_optimization/optimizing_WIP/index.html b/cost_optimization/optimizing_WIP/index.html new file mode 100644 index 000000000..a39d7eab4 --- /dev/null +++ b/cost_optimization/optimizing_WIP/index.html @@ -0,0 +1,2148 @@ + + + + + + + + + + + + + + + + + + + Optimizing over time (Right Sizing) - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Optimizing over time (Right Sizing)

+

Right Sizing as per the AWS Well-Architected Framework, is “… using the lowest cost resource that still meets the technical specifications of a specific workload”.

+

When you specify the resource requests for the Containers in a Pod, the scheduler uses this information to decide which node to place the Pod on. When you specify a resource limits for a Container, the kubelet enforces those limits so that the running container is not allowed to use more of that resource than the limit you set. The details of how Kubernetes manages resources for containers are given in the documentation.

+

In Kubernetes, this means setting the right compute resources (CPU and memory are collectively referred to as compute resources) - setting the resource requests that align as close as possible to the actual utilization. The tools for getting the actual resource usags of Pods are given in the section on Rexommendations below.

+

Amazon EKS on AWS Fargate: When pods are scheduled on Fargate, the vCPU and memory reservations within the pod specification determine how much CPU and memory to provision for the pod. If you do not specify a vCPU and memory combination, then the smallest available combination is used (.25 vCPU and 0.5 GB memory). The list of vCPU and memory combinations that are available for pods running on Fargate are listed in the Amazon EKS User Guide.

+

Amazon EKS on EC2: When you create a Pod, you can specify how much of each resource like CPU and Memory, a Container needs. It is important we do not over-provision (which will lead to wastage) or under-provision (will lead to throttling) the resources allocated to the containers.

+

Recommendations

+

FairwindsOps Goldilocks: The FairwindsOps Goldilocks is a tool that creates a Vertical Pod Autoscaler (VPA) for each deployment in a namespace and then queries them for information. Once the VPAs are in place, we see recommendations appear in the Goldilocks dashboard.

+

Deploy the Vertical Pod Autoscaler as per the documentation.

+

Enable Namespace - Pick an application namespace and label it like so in order to see some data, in the following example we are specifying the default namespace:

+
$ kubectl label ns default goldilocks.fairwinds.com/enabled=true
+
+

Viewing the Dashboard - The default installation creates a ClusterIP service for the dashboard. You can access via port forward:

+
$ kubectl -n goldilocks port-forward svc/goldilocks-dashboard 8080:80
+
+

Then open your browser to http://localhost:8080

+

Goldilocks recommendation Page

+

Use Application Profiling tools like CloudWatch Container Insights and Prometheus Metrics in Amazon CloudWatch

+

Use CloudWatch Container Insights to see how you can use native CloudWatch features to monitor your EKS Cluster performance. You can use CloudWatch Container Insights to collect, aggregate, and summarize metrics and logs from your containerized applications and microservices running on Amazon Elastic Kubernetes Service. The metrics include utilization for resources such as CPU, memory, disk, and network - which can help with right-sizing Pods and save costs.

+

Container Insights Prometheus Metrics Monitoring At present, support for Prometheus metrics is still in beta. CloudWatch Container Insights monitoring for Prometheus automates the discovery of Prometheus metrics from containerized systems and workloads. Prometheus is an open-source systems monitoring and alerting toolkit. All Prometheus metrics are collected in the ContainerInsights/Prometheus namespace.

+

The Metrics provided by cAdvisor and kube-state-metrics can be used for monitoring pods on Amazon EKS on AWS Fargate using Prometheus and Grafana, which can then be used to implement requests in your containers. Please refer to this blog for more details.

+

Right Size Guide: The right size guide (rsg) is a simple CLI tool that provides you with memory and CPU recommendations for your application. This tool works across container orchestrators, including Kubernetes and easy to deploy.

+

By using tools like CloudWatch Container Insights, Kube Resource Report, Goldilocks and others, applications running in the Kubernetes cluster can be right sized and potentially lower your costs.

+

Resources

+

Refer to the following resources to learn more about best practices for cost optimization.

+

Documentation and Blogs

+ +

Tools

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/images/ClusterAS-HPA.png b/images/ClusterAS-HPA.png new file mode 100644 index 000000000..e254b6759 Binary files /dev/null and b/images/ClusterAS-HPA.png differ diff --git a/images/Compute-savings-plan.png b/images/Compute-savings-plan.png new file mode 100644 index 000000000..9bfe3afa6 Binary files /dev/null and b/images/Compute-savings-plan.png differ diff --git a/images/Goldilocks.png b/images/Goldilocks.png new file mode 100644 index 000000000..3b08a58ec Binary files /dev/null and b/images/Goldilocks.png differ diff --git a/images/after-results.png b/images/after-results.png new file mode 100644 index 000000000..46db9338e Binary files /dev/null and b/images/after-results.png differ diff --git a/images/after.png b/images/after.png new file mode 100644 index 000000000..2ad1f080d Binary files /dev/null and b/images/after.png differ diff --git a/images/before-results.png b/images/before-results.png new file mode 100644 index 000000000..5688166e6 Binary files /dev/null and b/images/before-results.png differ diff --git a/images/before.png b/images/before.png new file mode 100644 index 000000000..ef7c76ae6 Binary files /dev/null and b/images/before.png differ diff --git a/images/between_vpcs.png b/images/between_vpcs.png new file mode 100644 index 000000000..4c018552e Binary files /dev/null and b/images/between_vpcs.png differ diff --git a/images/cfm_framework.png b/images/cfm_framework.png new file mode 100644 index 000000000..ffa26a796 Binary files /dev/null and b/images/cfm_framework.png differ diff --git a/images/cluster-auto-scaler.png b/images/cluster-auto-scaler.png new file mode 100644 index 000000000..5e11a2a8f Binary files /dev/null and b/images/cluster-auto-scaler.png differ diff --git a/images/console.png b/images/console.png new file mode 100644 index 000000000..0a2fbec9c Binary files /dev/null and b/images/console.png differ diff --git a/images/eks-controlplane-costexplorer.png b/images/eks-controlplane-costexplorer.png new file mode 100644 index 000000000..8a0366679 Binary files /dev/null and b/images/eks-controlplane-costexplorer.png differ diff --git a/images/eks-fargate-costexplorer.png b/images/eks-fargate-costexplorer.png new file mode 100644 index 000000000..afbe337dd Binary files /dev/null and b/images/eks-fargate-costexplorer.png differ diff --git a/images/endpoint_slice.png b/images/endpoint_slice.png new file mode 100644 index 000000000..b71b35fd6 Binary files /dev/null and b/images/endpoint_slice.png differ diff --git a/images/external-and-internal-traffic-policy.png b/images/external-and-internal-traffic-policy.png new file mode 100644 index 000000000..a4b683898 Binary files /dev/null and b/images/external-and-internal-traffic-policy.png differ diff --git a/images/flywheel.png b/images/flywheel.png new file mode 100644 index 000000000..128ac5c4a Binary files /dev/null and b/images/flywheel.png differ diff --git a/images/high-cardinality.png b/images/high-cardinality.png new file mode 100644 index 000000000..5eb70199d Binary files /dev/null and b/images/high-cardinality.png differ diff --git a/images/ip_mode.png b/images/ip_mode.png new file mode 100644 index 000000000..5efecafae Binary files /dev/null and b/images/ip_mode.png differ diff --git a/images/istio-traffic-control.png b/images/istio-traffic-control.png new file mode 100644 index 000000000..7d1d1ddaf Binary files /dev/null and b/images/istio-traffic-control.png differ diff --git a/images/kube-cost.png b/images/kube-cost.png new file mode 100644 index 000000000..f2e48955b Binary files /dev/null and b/images/kube-cost.png differ diff --git a/images/kube-down-scaler.png b/images/kube-down-scaler.png new file mode 100644 index 000000000..b256129ac Binary files /dev/null and b/images/kube-down-scaler.png differ diff --git a/images/kube-opex-analytics.png b/images/kube-opex-analytics.png new file mode 100644 index 000000000..6b8295634 Binary files /dev/null and b/images/kube-opex-analytics.png differ diff --git a/images/kube-resource-report1.png b/images/kube-resource-report1.png new file mode 100644 index 000000000..9a1ad7665 Binary files /dev/null and b/images/kube-resource-report1.png differ diff --git a/images/kube-resource-report2.png b/images/kube-resource-report2.png new file mode 100644 index 000000000..3f4d17160 Binary files /dev/null and b/images/kube-resource-report2.png differ diff --git a/images/kube-resource-report3.png b/images/kube-resource-report3.png new file mode 100644 index 000000000..6ad57a9f4 Binary files /dev/null and b/images/kube-resource-report3.png differ diff --git a/images/kubernetes-dashboard.png b/images/kubernetes-dashboard.png new file mode 100644 index 000000000..a67b70ec7 Binary files /dev/null and b/images/kubernetes-dashboard.png differ diff --git a/images/lb_2_pod.png b/images/lb_2_pod.png new file mode 100644 index 000000000..085a8c368 Binary files /dev/null and b/images/lb_2_pod.png differ diff --git a/images/local_traffic.png b/images/local_traffic.png new file mode 100644 index 000000000..c22614f2b Binary files /dev/null and b/images/local_traffic.png differ diff --git a/images/nat_gw.png b/images/nat_gw.png new file mode 100644 index 000000000..a9b9989d5 Binary files /dev/null and b/images/nat_gw.png differ diff --git a/images/no_node_local_1.png b/images/no_node_local_1.png new file mode 100644 index 000000000..7ce8b5af5 Binary files /dev/null and b/images/no_node_local_1.png differ diff --git a/images/no_node_local_2.png b/images/no_node_local_2.png new file mode 100644 index 000000000..9e6a957c9 Binary files /dev/null and b/images/no_node_local_2.png differ diff --git a/images/peering.png b/images/peering.png new file mode 100644 index 000000000..097f684a2 Binary files /dev/null and b/images/peering.png differ diff --git a/images/recommended_approach.png b/images/recommended_approach.png new file mode 100644 index 000000000..d71f1ccce Binary files /dev/null and b/images/recommended_approach.png differ diff --git a/images/reliability-ca-asg.jpg b/images/reliability-ca-asg.jpg new file mode 100644 index 000000000..e1af45251 Binary files /dev/null and b/images/reliability-ca-asg.jpg differ diff --git a/images/slice_shell.png b/images/slice_shell.png new file mode 100644 index 000000000..e11e90bcf Binary files /dev/null and b/images/slice_shell.png differ diff --git a/images/spot_diagram.png b/images/spot_diagram.png new file mode 100644 index 000000000..deed8e5df Binary files /dev/null and b/images/spot_diagram.png differ diff --git a/images/topo_aware_routing.png b/images/topo_aware_routing.png new file mode 100644 index 000000000..3e3db0db6 Binary files /dev/null and b/images/topo_aware_routing.png differ diff --git a/images/transititive.png b/images/transititive.png new file mode 100644 index 000000000..7b0734b32 Binary files /dev/null and b/images/transititive.png differ diff --git a/images/vpc_endpoints.png b/images/vpc_endpoints.png new file mode 100644 index 000000000..8c9e04d19 Binary files /dev/null and b/images/vpc_endpoints.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..61ffbf85d --- /dev/null +++ b/index.html @@ -0,0 +1,2159 @@ + + + + + + + + + + + + + + + + + + + + + Introduction - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Introduction

+

Welcome to the EKS Best Practices Guides. The primary goal of this project is to offer a set of best practices for day 2 operations for Amazon EKS. We elected to publish this guidance to GitHub so we could iterate quickly, provide timely and effective recommendations for variety of concerns, and easily incorporate suggestions from the broader community.

+

We currently have published guides for the following topics:

+ +

We also open sourced a Python based CLI (Command Line Interface) called hardeneks to check some of the recommendations from this guide.

+

In the future we will be publishing best practices guidance for performance, cost optimization, and operational excellence.

+ +

In addition to the EKS User Guide, AWS has published several other guides that may help you with your implementation of EKS.

+ +

Contributing

+

We encourage you to contribute to these guides. If you have implemented a practice that has proven to be effective, please share it with us by opening an issue or a pull request. Similarly, if you discover an error or flaw in the guidance we've already published, please submit a PR to correct it. The guidelines for submitting PRs can be found in our Contributing Guidelines.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/karpenter/index.html b/karpenter/index.html new file mode 100644 index 000000000..681273964 --- /dev/null +++ b/karpenter/index.html @@ -0,0 +1,2887 @@ + + + + + + + + + + + + + + + + + + + + + + + Karpenter - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Karpenter Best Practices

+

Karpenter

+

Karpenter is an open-source project that provides node lifecycle management for Kubernetes clusters. It automates provisioning and deprovisioning of nodes based on the scheduling needs of pods, allowing efficient scaling and cost optimization. Its main functions are:

+
    +
  • Monitor pods that the Kubernetes scheduler cannot schedule due to resource constraints.
  • +
  • Evaluate the scheduling requirements (resource requests, node selectors, affinities, tolerations, etc.) of the unschedulable pods.
  • +
  • Provision new nodes that meet the requirements of those pods.
  • +
  • Remove nodes when they are no longer needed.
  • +
+

With Karpenter, you can define NodePools with constraints on node provisioning like taints, labels, requirements (instance types, zones, etc.), and limits on total provisioned resources. +When deploying workloads, you can specify scheduling constraints in the pod spec like resource requests/limits, node selectors, node/pod affinities, tolerations, and topology spread constraints. Karpenter will then provision right sized nodes for those pods.

+

Reasons to use Karpenter

+

Before the launch of Karpenter, Kubernetes users relied primarily on Amazon EC2 Auto Scaling groups and the Kubernetes Cluster Autoscaler (CAS) to dynamically adjust the compute capacity of their clusters. With Karpenter, you don’t need to create dozens of node groups to achieve the flexibility and diversity you get with Karpenter. Moreover, Karpenter is not as tightly coupled to Kubernetes versions (as CAS is) and doesn’t require you to jump between AWS and Kubernetes APIs.

+

Karpenter consolidates instance orchestration responsibilities within a single system, which is simpler, more stable and cluster-aware. Karpenter was designed to overcome some of the challenges presented by Cluster Autoscaler by providing simplified ways to:

+
    +
  • Provision nodes based on workload requirements.
  • +
  • Create diverse node configurations by instance type, using flexible NodePool options. Instead of managing many specific custom node groups, Karpenter could let you manage diverse workload capacity with a single, flexible NodePool.
  • +
  • Achieve improved pod scheduling at scale by quickly launching nodes and scheduling pods.
  • +
+

For information and documentation on using Karpenter, visit the karpenter.sh site.

+

Recommendations

+

Best practices are divided into sections on Karpenter itself, NodePools, and pod scheduling.

+

Karpenter best practices

+

The following best practices cover topics related to Karpenter itself.

+

Use Karpenter for workloads with changing capacity needs

+

Karpenter brings scaling management closer to Kubernetes native APIs than do Autoscaling Groups (ASGs) and Managed Node Groups (MNGs). ASGs and MNGs are AWS-native abstractions where scaling is triggered based on AWS level metrics, such as EC2 CPU load. Cluster Autoscaler bridges the Kubernetes abstractions into AWS abstractions, but loses some flexibility because of that, such as scheduling for a specific availability zone.

+

Karpenter removes a layer of AWS abstraction to bring some of the flexibility directly into Kubernetes. Karpenter is best used for clusters with workloads that encounter periods of high, spiky demand or have diverse compute requirements. MNGs and ASGs are good for clusters running workloads that tend to be more static and consistent. You can use a mix of dynamically and statically managed nodes, depending on your requirements.

+

Consider other autoscaling projects when...

+

You need features that are still being developed in Karpenter. Because Karpenter is a relatively new project, consider other autoscaling projects for the time being if you have a need for features that are not yet part of Karpenter.

+

Run the Karpenter controller on EKS Fargate or on a worker node that belongs to a node group

+

Karpenter is installed using a Helm chart. The Helm chart installs the Karpenter controller and a webhook pod as a Deployment that needs to run before the controller can be used for scaling your cluster. We recommend a minimum of one small node group with at least one worker node. As an alternative, you can run these pods on EKS Fargate by creating a Fargate profile for the karpenter namespace. Doing so will cause all pods deployed into this namespace to run on EKS Fargate. Do not run Karpenter on a node that is managed by Karpenter.

+

No custom launch templates support with Karpenter

+

There is no custom launch template support with v1beta1 APIs (v0.32+). You can use custom user data and/or directly specifying custom AMIs in the EC2NodeClass. More information on how to do this is available at NodeClasses.

+

Exclude instance types that do not fit your workload

+

Consider excluding specific instances types with the node.kubernetes.io/instance-type key if they are not required by workloads running in your cluster.

+

The following example shows how to avoid provisioning large Graviton instances.

+
- key: node.kubernetes.io/instance-type
+  operator: NotIn
+  values:
+  - m6g.16xlarge
+  - m6gd.16xlarge
+  - r6g.16xlarge
+  - r6gd.16xlarge
+  - c6g.16xlarge
+
+

Enable Interruption Handling when using Spot

+

Karpenter supports native interruption handling and can handle involuntary interruption events like Spot Instance interruptions, scheduled maintenance events, instance termination/stopping events that could disrupt your workloads. When Karpenter detects such events for nodes, it automatically taints, drains and terminates the affected nodes ahead of time to start graceful cleanup of workloads before disruption. +For Spot interruptions with 2 minute notice, Karpenter quickly starts a new node so pods can be moved before the instance is reclaimed. To enable interruption handling, you configure the --interruption-queue CLI argument with the name of the SQS queue provisioned for this purpose. +It is not advised to use Karpenter interruption handling alongside Node Termination Handler as explained here.

+

Pods that require checkpointing or other forms of graceful draining, requiring the 2-mins before shutdown should enable Karpenter interruption handling in their clusters.

+

Amazon EKS private cluster without outbound internet access

+

When provisioning an EKS Cluster into a VPC with no route to the internet, you have to make sure you’ve configured your environment in accordance with the private cluster requirements that appear in EKS documentation. In addition, you need to make sure you’ve created an STS VPC regional endpoint in your VPC. If not, you will see errors similar to those that appear below.

+
{"level":"FATAL","time":"2024-02-29T14:28:34.392Z","logger":"controller","message":"Checking EC2 API connectivity, WebIdentityErr: failed to retrieve credentials\ncaused by: RequestError: send request failed\ncaused by: Post \"https://sts.<region>.amazonaws.com/\": dial tcp 54.239.32.126:443: i/o timeout","commit":"596ea97"}
+
+

These changes are necessary in a private cluster because the Karpenter Controller uses IAM Roles for Service Accounts (IRSA). Pods configured with IRSA acquire credentials by calling the AWS Security Token Service (AWS STS) API. If there is no outbound internet access, you must create and use an AWS STS VPC endpoint in your VPC.

+

Private clusters also require you to create a VPC endpoint for SSM. When Karpenter tries to provision a new node, it queries the Launch template configs and an SSM parameter. If you do not have a SSM VPC endpoint in your VPC, it will cause the following error:

+
{"level":"ERROR","time":"2024-02-29T14:28:12.889Z","logger":"controller","message":"Unable to hydrate the AWS launch template cache, RequestCanceled: request context canceled\ncaused by: context canceled","commit":"596ea97","tag-key":"karpenter.k8s.aws/cluster","tag-value":"eks-workshop"}
+...
+{"level":"ERROR","time":"2024-02-29T15:08:58.869Z","logger":"controller.nodeclass","message":"discovering amis from ssm, getting ssm parameter \"/aws/service/eks/optimized-ami/1.27/amazon-linux-2/recommended/image_id\", RequestError: send request failed\ncaused by: Post \"https://ssm.<region>.amazonaws.com/\": dial tcp 67.220.228.252:443: i/o timeout","commit":"596ea97","ec2nodeclass":"default","query":"/aws/service/eks/optimized-ami/1.27/amazon-linux-2/recommended/image_id"}
+
+

There is no VPC endpoint for the Price List Query API. +As a result, pricing data will go stale over time. +Karpenter gets around this by including on-demand pricing data in its binary, but only updates that data when Karpenter is upgraded. +Failed requests for pricing data will result in the following error messages:

+
{"level":"ERROR","time":"2024-02-29T15:08:58.522Z","logger":"controller.pricing","message":"retreiving on-demand pricing data, RequestError: send request failed\ncaused by: Post \"https://api.pricing.<region>.amazonaws.com/\": dial tcp 18.196.224.8:443: i/o timeout; RequestError: send request failed\ncaused by: Post \"https://api.pricing.<region>.amazonaws.com/\": dial tcp 18.185.143.117:443: i/o timeout","commit":"596ea97"}
+
+

Refer to this documentation to use Karpenter in a completely Private EKS Clusters and to know which VPC endpoints to be created.

+

Creating NodePools

+

The following best practices cover topics related to creating NodePools.

+

Create multiple NodePools when...

+

When different teams are sharing a cluster and need to run their workloads on different worker nodes, or have different OS or instance type requirements, create multiple NodePools. For example, one team may want to use Bottlerocket, while another may want to use Amazon Linux. Likewise, one team might have access to expensive GPU hardware that wouldn’t be needed by another team. Using multiple NodePools makes sure that the most appropriate assets are available to each team.

+

Create NodePools that are mutually exclusive or weighted

+

It is recommended to create NodePools that are either mutually exclusive or weighted to provide consistent scheduling behavior. If they are not and multiple NodePools are matched, Karpenter will randomly choose which to use, causing unexpected results. Useful examples for creating multiple NodePools include the following:

+

Creating a NodePool with GPU and only allowing special workloads to run on these (expensive) nodes:

+
# NodePool for GPU Instances with Taints
+apiVersion: karpenter.sh/v1beta1
+kind: NodePool
+metadata:
+  name: gpu
+spec:
+  disruption:
+    consolidateAfter: 1m0s
+    consolidationPolicy: WhenEmpty
+    expireAfter: Never
+  template:
+    metadata: {}
+    spec:
+      nodeClassRef:
+        name: default
+      requirements:
+      - key: node.kubernetes.io/instance-type
+        operator: In
+        values:
+        - p3.8xlarge
+        - p3.16xlarge
+      - key: kubernetes.io/os
+        operator: In
+        values:
+        - linux
+      - key: kubernetes.io/arch
+        operator: In
+        values:
+        - amd64
+      - key: karpenter.sh/capacity-type
+        operator: In
+        values:
+        - on-demand
+      taints:
+      - effect: NoSchedule
+        key: nvidia.com/gpu
+        value: "true"
+
+

Deployment with toleration for the taint:

+
# Deployment of GPU Workload will have tolerations defined
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: inflate-gpu
+spec:
+  ...
+    spec:
+      tolerations:
+      - key: "nvidia.com/gpu"
+        operator: "Exists"
+        effect: "NoSchedule"
+
+

For a general deployment for another team, the NodePool spec could include nodeAffinity. A Deployment could then use nodeSelectorTerms to match billing-team.

+
# NodePool for regular EC2 instances
+apiVersion: karpenter.sh/v1beta1
+kind: NodePool
+metadata:
+  name: generalcompute
+spec:
+  disruption:
+    expireAfter: Never
+  template:
+    metadata:
+      labels:
+        billing-team: my-team
+    spec:
+      nodeClassRef:
+        name: default
+      requirements:
+      - key: node.kubernetes.io/instance-type
+        operator: In
+        values:
+        - m5.large
+        - m5.xlarge
+        - m5.2xlarge
+        - c5.large
+        - c5.xlarge
+        - c5a.large
+        - c5a.xlarge
+        - r5.large
+        - r5.xlarge
+      - key: kubernetes.io/os
+        operator: In
+        values:
+        - linux
+      - key: kubernetes.io/arch
+        operator: In
+        values:
+        - amd64
+      - key: karpenter.sh/capacity-type
+        operator: In
+        values:
+        - on-demand
+
+

Deployment using nodeAffinity:

+
# Deployment will have spec.affinity.nodeAffinity defined
+kind: Deployment
+metadata:
+  name: workload-my-team
+spec:
+  replicas: 200
+  ...
+    spec:
+      affinity:
+        nodeAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+            nodeSelectorTerms:
+              - matchExpressions:
+                - key: "billing-team"
+                  operator: "In"
+                  values: ["my-team"]
+
+

Use timers (TTL) to automatically delete nodes from the cluster

+

You can use timers on provisioned nodes to set when to delete nodes that are devoid of workload pods or have reached an expiration time. Node expiry can be used as a means of upgrading, so that nodes are retired and replaced with updated versions. See Expiration in the Karpenter documentation for information on using spec.disruption.expireAfter to configure node expiry.

+

Avoid overly constraining the Instance Types that Karpenter can provision, especially when utilizing Spot

+

When using Spot, Karpenter uses the Price Capacity Optimized allocation strategy to provision EC2 instances. This strategy instructs EC2 to provision instances from the deepest pools for the number of instances that you are launching and have the lowest risk of interruption. EC2 Fleet then requests Spot instances from the lowest priced of these pools. The more instance types you allow Karpenter to utilize, the better EC2 can optimize your spot instance’s runtime. By default, Karpenter will use all Instance Types EC2 offers in the region and availability zones your cluster is deployed in. Karpenter intelligently chooses from the set of all instance types based on pending pods to make sure your pods are scheduled onto appropriately sized and equipped instances. For example, if your pod does not require a GPU, Karpenter will not schedule your pod to an EC2 instance type supporting a GPU. When you're unsure about which instance types to use, you can run the Amazon ec2-instance-selector to generate a list of instance types that match your compute requirements. For example, the CLI takes memory vCPU, architecture, and region as input parameters and provides you with a list of EC2 instances that satisfy those constraints.

+
$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r ap-southeast-1
+c5.large
+c5a.large
+c5ad.large
+c5d.large
+c6i.large
+t2.medium
+t3.medium
+t3a.medium
+
+

You shouldn’t place too many constraints on Karpenter when using Spot instances because doing so can affect the availability of your applications. Say, for example, all of the instances of a particular type are reclaimed and there are no suitable alternatives available to replace them. Your pods will remain in a pending state until the spot capacity for the configured instance types is replenished. You can reduce the risk of insufficient capacity errors by spreading your instances across different availability zones, because spot pools are different across AZs. That said, the general best practice is to allow Karpenter to use a diverse set of instance types when using Spot.

+

Scheduling Pods

+

The following best practices relate to deploying pods In a cluster using Karpenter for node provisioning.

+

Follow EKS best practices for high availability

+

If you need to run highly available applications, follow general EKS best practice recommendations. See Topology Spread in Karpenter documentation for details on how to spread pods across nodes and zones. Use Disruption Budgets to set the minimum available pods that need to be maintained, in case there are attempts to evict or delete pods.

+

Use layered Constraints to constrain the compute features available from your cloud provider

+

Karpenter’s model of layered constraints allows you to create a complex set of NodePool and pod deployment constraints to get the best possible matches for pod scheduling. Examples of constraints that a pod spec can request include the following:

+
    +
  • Needing to run in availability zones where only particular applications are available. Say, for example, you have pod that has to communicate with another application that runs on an EC2 instance residing in a particular availability zone. If your aim is to reduce cross-AZ traffic in your VPC, you may want to co-locate the pods in the AZ where the EC2 instance is located. This sort of targeting is often accomplished using node selectors. For additional information on Node selectors, please refer to the Kubernetes documentation.
  • +
  • Requiring certain kinds of processors or other hardware. See the Accelerators section of the Karpenter docs for a podspec example that requires the pod to run on a GPU.
  • +
+

Create billing alarms to monitor your compute spend

+

When you configure your cluster to automatically scale, you should create billing alarms to warn you when your spend has exceeded a threshold and add resource limits to your Karpenter configuration. Setting resource limits with Karpenter is similar to setting an AWS autoscaling group’s maximum capacity in that it represents the maximum amount of compute resources that can be instantiated by a Karpenter NodePool.

+
+

Note

+

It is not possible to set a global limit for the whole cluster. Limits apply to specific NodePools.

+
+

The snippet below tells Karpenter to only provision a maximum of 1000 CPU cores and 1000Gi of memory. Karpenter will stop adding capacity only when the limit is met or exceeded. When a limit is exceeded the Karpenter controller will write memory resource usage of 1001 exceeds limit of 1000 or a similar looking message to the controller’s logs. If you are routing your container logs to CloudWatch logs, you can create a metrics filter to look for specific patterns or terms in your logs and then create a CloudWatch alarm to alert you when your configured metrics threshold is breached.

+

For further information using limits with Karpenter, see Setting Resource Limits in the Karpenter documentation.

+
spec:
+  limits:
+    cpu: 1000
+    memory: 1000Gi
+
+

If you don’t use limits or constrain the instance types that Karpenter can provision, Karpenter will continue adding compute capacity to your cluster as needed. While configuring Karpenter in this way allows your cluster to scale freely, it can also have significant cost implications. It is for this reason that we recommend that configuring billing alarms. Billing alarms allow you to be alerted and proactively notified when the calculated estimated charges in your account(s) exceed a defined threshold. See Setting up an Amazon CloudWatch Billing Alarm to Proactively Monitor Estimated Charges for additional information.

+

You may also want to enable Cost Anomaly Detection which is an AWS Cost Management feature that uses machine learning to continuously monitor your cost and usage to detect unusual spends. Further information can be found in the AWS Cost Anomaly Detection Getting Started guide. If you’ve gone so far as to create a budget in AWS Budgets, you can also configure an action to notify you when a specific threshold has been breached. With budget actions you can send an email, post a message to an SNS topic, or send a message to a chatbot like Slack. For further information see Configuring AWS Budgets actions.

+

Use the karpenter.sh/do-not-disrupt annotation to prevent Karpenter from deprovisioning a node

+

If you are running a critical application on a Karpenter-provisioned node, such as a long running batch job or stateful application, and the node’s TTL has expired, the application will be interrupted when the instance is terminated. By adding a karpenter.sh/do-not-disrupt annotation to the pod, you are instructing Karpenter to preserve the node until the Pod is terminated or the karpenter.sh/do-not-disrupt annotation is removed. See Distruption documentation for further information.

+

If the only non-daemonset pods left on a node are those associated with jobs, Karpenter is able to target and terminate those nodes so long as the job status is succeed or failed.

+

Configure requests=limits for all non-CPU resources when using consolidation

+

Consolidation and scheduling in general work by comparing the pods resource requests vs the amount of allocatable resources on a node. The resource limits are not considered. As an example, pods that have a memory limit that is larger than the memory request can burst above the request. If several pods on the same node burst at the same time, this can cause some of the pods to be terminated due to an out of memory (OOM) condition. Consolidation can make this more likely to occur as it works to pack pods onto nodes only considering their requests.

+

Use LimitRanges to configure defaults for resource requests and limits

+

Because Kubernetes doesn’t set default requests or limits, a container’s consumption of resources from the underlying host, CPU, and memory is unbound. The Kubernetes scheduler looks at a pod’s total requests (the higher of the total requests from the pod’s containers or the total resources from the pod’s Init containers) to determine which worker node to schedule the pod onto. Similarly, Karpenter considers a pod’s requests to determine which type of instance it provisions. You can use a limit range to apply a sensible default for a namespace, in case resource requests are not specified by some pods.

+

See Configure Default Memory Requests and Limits for a Namespace

+

Apply accurate resource requests to all workloads

+

Karpenter is able to launch nodes that best fit your workloads when its information about your workloads requirements is accurate. This is particularly important if using Karpenter's consolidation feature.

+

See Configure and Size Resource Requests/Limits for all Workloads

+

CoreDNS recommendations

+

Update the configuration of CoreDNS to maintain reliability

+

When deploying CoreDNS pods on nodes managed by Karpenter, given Karpenter's dynamic nature in rapidly terminating/creating new nodes to align with demand, it is advisable to adhere to the following best practices:

+

CoreDNS lameduck duration

+

CoreDNS readiness probe

+

This will ensure that DNS queries are not directed to a CoreDNS Pod that is not yet ready or has been terminated.

+

Karpenter Blueprints

+

As Karpenter takes an application-first approach to provision compute capacity for to the Kubernetes data plane, there are common workload scenarios that you might be wondering how to configure them properly. Karpenter Blueprints is a repository that includes a list of common workload scenarios following the best practices described here. You'll have all the resources you need to even create an EKS cluster with Karpenter configured, and test each of the blueprints included in the repository. You can combine different blueprints to finally create the one you need for your workload(s).

+

Additional Resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cluster-autoscaling/index.html b/ko/cluster-autoscaling/index.html new file mode 100644 index 000000000..a98c3ecd6 --- /dev/null +++ b/ko/cluster-autoscaling/index.html @@ -0,0 +1,2859 @@ + + + + + + + + + + + + + + + + + + + + + + + Cluster Autoscaler - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

쿠버네티스 Cluster Autoscaler

+ + +

개요

+

쿠버네티스 Cluster AutoscalerSIG 오토스케일링에서 유지 관리하는 인기 있는 클러스터 오토스케일링 솔루션입니다. 이는 클러스터에 리소스를 낭비하지 않고 파드를 스케줄링할 수 있는 충분한 노드가 있는지 확인하는 역할을 합니다. Cluster Autoscaler는 스케줄링에 실패한 파드와 활용도가 낮은 노드를 감시합니다. 그런 다음 클러스터에 변경 사항을 적용하기 전에 노드 추가 또는 제거를 시뮬레이션합니다. Cluster Autoscaler 내의 AWS 클라우드 공급자 구현은 EC2 Auto Scaling 그룹의 .desireReplicas 필드를 제어합니다.

+

+

이 가이드는 Cluster Autoscaler를 구성하고 조직의 요구 사항에 가장 적합한 절충안을 선택하기 위한 멘탈 모델을 제공합니다. 최상의 단일 구성은 없지만 성능, 확장성, 비용 및 가용성을 절충할 수 있는 구성 옵션 집합이 있습니다.또한 이 안내서는 AWS 구성을 최적화하기 위한 팁과 모범 사례를 제공합니다.

+

용어집

+

다음 용어는 이 문서 전체에서 자주 사용됩니다. 이런 용어는 광범위한 의미를 가질 수 있지만 이 문서의 목적상 아래 정의로만 제한됩니다.

+

확장성은 쿠버네티스 클러스터의 파드 및 노드 수가 증가할 때 Cluster Autoscaler가 얼마나 잘 작동하는지를 나타냅니다. 확장성 한계에 도달하면 Cluster Autoscaler의 성능과 기능이 저하됩니다. Cluster Autoscaler가 확장성 제한을 초과하면 더 이상 클러스터에서 노드를 추가하거나 제거할 수 없습니다.

+

성능은 Cluster Autoscaler가 규모 조정 결정을 얼마나 빨리 내리고 실행할 수 있는지를 나타냅니다. 완벽하게 작동하는 Cluster Autoscaler는 파드를 스케줄링할 수 없는 등의 이벤트에 대응하여 즉시 결정을 내리고 스케일링 조치를 트리거합니다.

+

가용성은 파드를 중단 없이 신속하게 스케줄링할 수 있다는 뜻이다. 여기에는 새로 생성된 파드를 스케줄링해야 하는 경우와 축소된 노드가 스케줄링된 나머지 파드를 종료하는 경우가 포함됩니다.

+

비용은 스케일-아웃 및 스케일-인 이벤트에 대한 결정에 따라 결정됩니다. 기존 노드의 활용도가 낮거나 들어오는 파드에 비해 너무 큰 새 노드를 추가하면 리소스가 낭비됩니다. 사용 사례에 따라 공격적인 규모 축소 결정으로 인해 파드를 조기에 종료하는 데 따른 비용이 발생할 수 있다.

+

노드 그룹은 클러스터 내 노드 그룹에 대한 추상적인 쿠버네티스 개념입니다. 이는 쿠버네티스 리소스는 아니지만 Cluster Autoscaler, 클러스터 API 및 기타 구성 요소에 추상화된 형태로 존재합니다. 노드 그룹 내의 노드는 레이블 및 테인트와 같은 속성을 공유하지만 여러 가용영역 또는 인스턴스 유형으로 구성될 수 있습니다.

+

EC2 Auto Scaling 그룹은 EC2의 노드 그룹 구현으로 사용할 수 있습니다. EC2 Auto Scaling 그룹은 쿠버네티스 클러스터에 자동으로 가입하고 쿠버네티스 API의 해당 노드 리소스에 레이블과 테인트를 적용하는 인스턴스를 시작하도록 구성되어 있습니다.

+

EC2 관리형 노드 그룹은 EC2에 노드 그룹을 구현한 또 다른 예입니다. EC2 오토스케일링 그룹을 수동으로 구성하는 복잡성을 없애고 노드 버전 업그레이드 및 정상적인 노드 종료와 같은 추가 관리 기능을 제공합니다.

+

Cluster Autoscaler 운영

+

Cluster Autoscaler는 일반적으로 클러스터에 디플로이먼트 타입으로 설치됩니다. 고가용성을 보장하기 위해 리더선출를 사용하지만 작업은 하나의 레플리카에서만 수행됩니다. 수평적으로 확장할 수는 없습니다. 기본 설정의 경우 제공된 설치지침을 사용하면 기본값이 기본적으로 작동하지만 몇 가지 유의해야 할 사항이 있습니다.

+

다음 사항을 확인하세요:

+
    +
  • Cluster Autoscaler 버전이 클러스터 버전과 일치하는지 확인합니다. 쿠버네티스 버전 별 공식 호환되는 버전 외에 타 버전의 호환성은 테스트 되지 않거나 지원되지 않습니다.
  • +
  • 이 모드를 사용하지 못하게 하는 특정 고급 사용 사례가 없는 한 Auto Discovery를 활성화했는지 확인합니다.
  • +
+

IAM 역할에 최소 접근 권한 적용

+

Auto Discovery를 사용하는 경우, autoscaling:SetDesiredCapacityautoscaling:TerminateInstanceInAutoScalingGroup작업을 현재 클러스터로 범위가 지정된 오토스케일링그룹으로 제한하여 최소 접근 권한을 사용하는 것을 권장합니다.

+

이렇게 하면 --node-group-auto-discovery 인수가 태그를 사용하여 클러스터의 노드 그룹으로 범위를 좁히지 않았더라도 (예: k8s.io/cluster-autoscaler/<cluster-name>) 한 클러스터에서 실행 중인 Cluster Autoscaler가 다른 클러스터의 노드 그룹을 수정할 수 없게 됩니다.

+
{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [
+                "autoscaling:SetDesiredCapacity",
+                "autoscaling:TerminateInstanceInAutoScalingGroup"
+            ],
+            "Resource": "*",
+            "Condition": {
+                "StringEquals": {
+                    "aws:ResourceTag/k8s.io/cluster-autoscaler/enabled": "true",
+                    "aws:ResourceTag/k8s.io/cluster-autoscaler/<my-cluster>": "owned"
+                }
+            }
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "autoscaling:DescribeAutoScalingGroups",
+                "autoscaling:DescribeAutoScalingInstances",
+                "autoscaling:DescribeLaunchConfigurations",
+                "autoscaling:DescribeScalingActivities",
+                "autoscaling:DescribeTags",
+                "ec2:DescribeImages",
+                "ec2:DescribeInstanceTypes",
+                "ec2:DescribeLaunchTemplateVersions",
+                "ec2:GetInstanceTypesFromInstanceRequirements",
+                "eks:DescribeNodegroup"
+            ],
+            "Resource": "*"
+        }
+    ]
+}
+
+

노드 그룹 구성

+

효과적인 오토스케일링은 클러스터의 노드 그룹 세트를 올바르게 구성하는 것에서 시작됩니다. 워크로드 전반에서 가용성을 극대화하고 비용을 절감하려면 올바른 노드 그룹 세트를 선택하는 것이 중요합니다. AWS는 다양한 사용 사례에 유연하게 적용할 수 있는 EC2 Auto Scaling 그룹을 사용하여 노드 그룹을 구현합니다. 하지만 Cluster Autoscaler는 노드 그룹에 대해 몇 가지 가정을 합니다. EC2 Auto Scaling 그룹 구성을 이런 가정과 일관되게 유지하면 원치 않는 동작을 최소화할 수 있습니다.

+

다음을 확인하십시오.

+
    +
  • 노드 그룹의 각 노드는 레이블, 테인트, 리소스와 같은 동일한 스케줄링 속성을 가집니다.
  • +
  • 혼합 인스턴스 정책의 경우 인스턴스 유형은 동일한 스펙의 CPU, 메모리 및 GPU이여야 합니다.
  • +
  • 정책에 지정된 첫 번째 인스턴스 유형은 스케줄링을 시뮬레이션하는 데 사용됩니다.
  • +
  • 정책에 더 많은 리소스가 포함된 추가 인스턴스 유형이 있는 경우 확장 후 리소스가 낭비될 수 있습니다.
  • +
  • 정책에 리소스가 적은 추가 인스턴스 유형이 있는 경우, 파드가 해당 인스턴스에서 일정을 예약하지 못할 수 있습니다.
  • +
  • 노드 수가 적은 노드 그룹보다 노드가 많은 노드 그룹이 선호됩니다. 이는 확장성에 가장 큰 영향을 미칩니다.
  • +
  • 가능하면 두 시스템 모두 지원을 제공하는 EC2 기능 (예: 지역, 혼합 인스턴스 정책) 을 선호하십시오.
  • +
+

참고: EKS 관리형 노드 그룹을 사용하는 것을 권장합니다. 관리형 노드 그룹에는 자동 EC2 Auto Scaling 그룹 검색 및 정상적인 노드 종료와 같은 Cluster Autoscaler 기능을 비롯한 강력한 관리 기능이 포함되어 있습니다.

+

성능 및 확장성 최적화

+

오토스케일링 알고리즘의 런타임 복잡성을 이해하면 1,000개 노드를 초과하는 대규모 클러스터에서 계속 원활하게 작동하도록 Cluster Autoscaler를 튜닝하는 데 도움이 됩니다.

+

Cluster Autoscaler의 확장성을 조정하기 위한 주요한 요소는 프로세스에 제공되는 리소스, 알고리즘의 스캔 간격, 클러스터의 노드 그룹 수입니다. 이 알고리즘의 실제 런타임 복잡성에는 스케줄링 플러그인 복잡성 및 파드 수와 같은 다른 요인도 있습니다. 이런 파라미터는 클러스터의 워크로드에 자연스럽게 영향을 미치며 쉽게 조정할 수 없기 때문에 구성할 수 없는 파라미터로 간주됩니다.

+

Cluster Autoscaler는 파드, 노드, 노드 그룹을 포함하여 전체 클러스터의 상태를 메모리에 로드합니다. 알고리즘은 각 스캔 간격마다 스케줄링할 수 없는 파드를 식별하고 각 노드 그룹에 대한 스케줄링을 시뮬레이션합니다. 이런 요소를 조정하는 것은 서로 다른 장단점이 있으므로 사용 사례에 맞게 신중하게 고려해야 합니다.

+

Cluster Autoscaler의 수직 오토스케일링

+

Cluster Autoscaler를 대규모 클러스터로 확장하는 가장 간단한 방법은 배포를 위한 리소스 요청을 늘리는 것입니다. 클러스터 크기에 따라 크게 다르지만 대규모 클러스터의 경우 메모리와 CPU를 모두 늘려야 합니다. 오토스케일링 알고리즘은 모든 파드와 노드를 메모리에 저장하므로 경우에 따라 메모리 사용량이 1GB보다 커질 수 있습니다. 리소스 증가는 일반적으로 수동으로 수행됩니다. 지속적인 리소스 튜닝으로 인해 운영상의 부담이 생긴다면 Addon Resizer 또는 Vertical Pod Autoscaler 사용을 고려해 보세요.

+

노드 그룹 수 줄이기

+

대규모 클러스터에서 Cluster Autoscaler가 계속 잘 작동하도록 하는 한 가지 방법은 노드 그룹 수를 최소화하는 것입니다. 팀 또는 응용 프로그램별로 노드 그룹을 구성하는 일부 조직에서는 이것이 어려울 수 있습니다. 이는 Kubernetes API에서 완벽하게 지원되지만 확장성에 영향을 미치는 Cluster Autoscaler 비권장 패턴으로 간주됩니다. 다중 노드 그룹(예: 스팟 또는 GPU)을 사용하는 데에는 여러 가지 이유가 있지만, 대부분의 경우 적은 수의 그룹을 사용하면서 동일한 효과를 얻을 수 있는 대안 설계가 있습니다.

+

다음을 확인합니다:

+
    +
  • 파드 격리는 노드 그룹이 아닌 네임스페이스를 사용하여 수행됩니다.
  • +
  • 신뢰도가 낮은 멀티테넌트 클러스터에서는 불가능할 수 있습니다.
  • +
  • 파드 리소스 요청(request)과 리소스 제한(limit)은 리소스 경합을 방지하기 위해 적절하게 설정되었다.
  • +
  • 인스턴스 유형이 클수록 빈 패킹이 최적화되고 시스템 파드 오버헤드가 줄어듭니다.
  • +
  • NodeTaints 또는 NodeSelector는 파드를 예외적으로 스케줄링하는 데 사용되는 것이지, 규칙이 아닙니다.
  • +
  • 리전 리소스는 멀티 가용영역을 포함하는 단일 EC2 Auto Scaling 그룹으로 정의됩니다.
  • +
+

스캔 간격 줄이기

+

스캔 간격(예: 10초)을 짧게 설정하면 파드를 스케줄링할 수 없을 때 Cluster Autoscaler가 최대한 빨리 응답할 수 있습니다. 하지만 스캔할 때마다 쿠버네티스 API 및 EC2 Auto Scaling 그룹 또는 EKS 관리형 노드 그룹 API에 대한 API 호출이 많이 발생합니다. 이런 API 호출로 인해 Kubernetes 컨트롤 플레인의 속도가 제한되거나 서비스를 사용할 수 없게 될 수도 있습니다.

+

디폴트 스캔 간격은 10초이지만 AWS에서 새로운 노드를 시작하는 데는 새 인스턴스를 시작하는 등 훨씬 더 오랜 시간이 소요됩니다. 즉, 전체 스케일업 시간을 크게 늘리지 않고도 스캔 간격을 늘릴 수 있습니다. 예를 들어 노드를 시작하는 데 2분이 걸리는 경우 스캔 간격을 1분으로 변경하면 API 호출이 6배 줄어들고 확장이 38% 느려지는 절충점이 발생합니다.

+

노드 그룹 간 샤딩

+

Cluster Autoscaler는 특정 노드 그룹 집합에서 작동하도록 구성할 수 있습니다. 이 기능을 사용하면 각각 다른 노드 그룹 집합에서 작동하도록 구성된 Cluster Autoscaler 인스턴스를 여러 개 배포할 수 있습니다. 이 전략을 사용하면 임의로 많은 수의 노드 그룹을 사용할 수 있으므로 확장성에 비용을 투자할 수 있습니다. 이 방법은 성능 개선을 위한 최후의 수단으로만 사용하는 것이 좋습니다.

+

Cluster Autoscaler는 원래 이 구성용으로 설계되지 않았으므로 몇 가지 부작용이 있습니다. 샤드(독립적으로 운영되는 노드 그룹)는 서로 통신하지 않기 때문에 여러 오토스케일러에서 동시에 스케줄링할 수 없는 파드를 스케줄링하려고 시도할 수 있습니다. 이로 인해 여러 노드 그룹이 불필요하게 확장될 수 있습니다. 이런 추가 노드는 scale-down-delay 이후 다시 축소됩니다.

+
metadata:
+  name: cluster-autoscaler
+  namespace: cluster-autoscaler-1
+
+...
+
+--nodes=1:10:k8s-worker-asg-1
+--nodes=1:10:k8s-worker-asg-2
+
+---
+
+metadata:
+  name: cluster-autoscaler
+  namespace: cluster-autoscaler-2
+
+...
+
+--nodes=1:10:k8s-worker-asg-3
+--nodes=1:10:k8s-worker-asg-4
+
+

다음을 확인하십시오.

+
    +
  • 각 샤드는 고유한 EC2 Auto Scaling 그룹 세트를 가리키도록 구성되어 있습니다.
  • +
  • 리더 선출 충돌을 방지하기 위해 각 샤드는 별도의 네임스페이스에 배포됩니다.
  • +
+

비용 및 가용성 최적화

+

스팟 인스턴스

+

노드 그룹에서 스팟 인스턴스를 사용하면 온디맨드 요금에서 최대 90% 까지 절약할 수 있습니다. 반면 EC2에서 용량을 다시 필요로 하는 경우 언제든지 스팟 인스턴스를 중단할 수 있습니다. 사용 가능한 용량이 부족하여 EC2 Auto Scaling 그룹을 확장할 수 없는 경우 용량 부족 오류가 발생합니다. 여러 인스턴스 패밀리를 선택하여 다양성을 극대화하면 많은 스팟 용량 풀을 활용하여 원하는 규모를 달성할 가능성이 높아지고 스팟 인스턴스 중단이 클러스터 가용성에 미치는 영향을 줄일 수 있습니다. 스팟 인스턴스를 사용한 혼합 인스턴스 정책은 노드 그룹 수를 늘리지 않고도 다양성을 높일 수 있는 좋은 방법입니다. 보장된 리소스가 필요한 경우 스팟 인스턴스 대신 온디맨드 인스턴스를 사용하세요.

+

혼합 인스턴스 정책을 구성할 때는 모든 인스턴스 유형의 리소스 용량이 비슷해야 합니다. 오토스케일러의 스케줄링 시뮬레이터는 혼합 인스턴스 정책의 첫 번째 인스턴스 유형을 사용합니다. 후속 인스턴스 유형이 더 크면 확장 후 리소스가 낭비될 수 있습니다. 크기가 작으면 용량 부족으로 인해 파드가 새 인스턴스를 스케쥴되지 못할 수 있습니다. 예를 들어 M4, M5, M5a 및 M5n 인스턴스는 모두 비슷한 양의 CPU와 메모리를 가지고 있으며 혼합 인스턴스 정책을 적용하기에 적합합니다.EC2 Instance Selector 도구를 사용하면 유사한 인스턴스 유형을 식별할 수 있습니다.

+

+

온디맨드 및 스팟 용량을 별도의 EC2 Auto Scaling 그룹으로 분리하는 것이 좋습니다. 스케줄링 속성이 근본적으로 다르기 때문에 기본 용량 전략을 사용하는 것보다 이 방법을 사용하는 것이 좋습니다. 스팟 인스턴스는 (EC2에서 용량을 다시 확보해야 할 때) 언제든지 중단되므로 사용자는 명환한 선점 동작을 위해 선점 가능한 노드를 테인트시킵니다. 이런 경우에 파드는 명시적인 톨러레이션이 요구됩니다. 이런 테인트로 인해 노드의 스케줄 속성이 달라지므로 여러 EC2 Auto Scaling 그룹으로 분리해야 합니다.

+

Cluster Autoscaler에는 Expanders라는 개념이 있으며, 확장할 노드 그룹을 선택하기 위한 다양한 전략을 제공합니다.--expander=least-waste 전략은 일반적인 용도의 기본 전략으로, 스팟 인스턴스 다양화를 위해 여러 노드 그룹을 사용하려는 경우 (위 이미지 설명 참조) 조정 활동 이후에 가장 잘 활용되는 그룹을 확장하여 노드 그룹의 비용을 더욱 최적화하는 데 도움이 될 수 있습니다.

+

노드 그룹 / ASG 우선 순위 지정

+

Priority expander를 사용하여 우선순위 기반 오토스케일링을 구성할 수도 있습니다. --expander=priority를 사용하면 클러스터가 노드 그룹/ASG의 우선 순위를 지정할 수 있으며, 어떤 이유로든 확장할 수 없는 경우 우선 순위 목록에서 다음 노드 그룹을 선택합니다. 이는 예를 들어, GPU가 워크로드에 최적화된 성능을 제공하기 때문에 P3 인스턴스 유형을 사용하려는 경우에 유용하지만 두 번째 옵션으로 P2 인스턴스 유형을 사용할 수도 있습니다.

+
apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cluster-autoscaler-priority-expander
+  namespace: kube-system
+data:
+  priorities: |-
+    10:
+      - .*p2-node-group.*
+    50:
+      - .*p3-node-group.*
+
+

Cluster Autoscaler는 p3-node-group이라는 이름과 일치하는 EC2 Auto Scaling 그룹을 확장하려고 시도합니다. 이 작업이 --max-node-provision-time 내에 성공하지 못하면 p2-node-group이라는 이름과 일치하는 EC2 Auto Scaling 그룹을 확장하려고 시도합니다. +이 값은 기본적으로 15분으로 설정되며 노드 그룹 선택의 속도를 높이기 위해 줄일 수 있습니다. 하지만 값이 너무 낮으면 불필요한 크기 조정이 발생할 수 있습니다.

+

오버프로비저닝

+

Cluster Autoscaler는 필요한 경우에만 클러스터에 노드를 추가하고 사용하지 않을 때는 노드를 제거함으로써 비용을 최소화합니다. 이는 많은 파드가 스케줄링되기 전에 노드 확장이 완료될 때까지 기다려야 하기 때문에 배포 지연 시간에 상당한 영향을 미칩니다. 노드를 사용할 수 있게 되기까지 몇 분이 걸릴 수 있으며, 이로 인해 파드 스케줄링 지연 시간이 평소보다 몇 배나 증가할 수 있습니다.

+

이런 스케줄링 지연은 오버프로비저닝을 사용하면 비용은 증가하나 완화될 수 있습니다. 오버프로비저닝은 클러스터 내 공간을 차지하는 우선 순위가 낮은(보통 음수) 임시 파드를 사용하여 구현됩니다. 새로 생성된 파드를 unschedulable 상태이고 우선 순위가 더 높은 경우, 임시 파드를 선점하여 공간을 확보합니다. 그러면 임시 파드는 unschedulable 상태가 되고 Cluster Autoscaler는 새 노드를 확장하도록 트리거됩니다.

+

오버프로비저닝으로 얻을 수 있는 또다른 다른 이점도 있습니다. 오버프로비저닝 구성이 안되어 있는 경우 사용률이 높은 클러스터에서는 파드의 preferredDuringSchedulingIgnoredDuringExecution 규칙이나 노드 어피니티 규칙으로 인하여 최적의 스케줄링 결정을 내리지 못할 수 있습니다. 이에 대한 일반적인 사용 사례는 AntiAffinity를 사용하여 가용성 영역 간에 가용성이 높은 애플리케이션의 파드를 분리하는 것입니다. 오버프로비저닝은 올바른 영역의 노드를 사용할 수 있는 가능성을 크게 높일 수 있습니다.

+

얼마나 많은 용량을 오버프로비저닝할 지는 조직 내에서 신중히 결정해야 할 비즈니스 사항입니다. 핵심은 성능과 비용 간의 균형입니다. 이 결정을 내리는 한 가지 방법은 평균적으로 얼마나 자주 오토스케일링되는지 빈도를 계산하고 이 값을 새 노드를 확장하는 데 걸리는 시간으로 나누는 것입니다. 예를 들어 평균적으로 30초마다 새 노드가 필요하고 EC2에서 새 노드를 프로비저닝하는 데 30초가 걸린다면, 단일 노드를 오버프로비저닝하면 항상 추가 노드를 사용할 수 있게 되므로 EC2 인스턴스 하나를 추가하는 데 드는 비용으로 예약 지연 시간을 30초까지 줄일 수 있습니다. 영역 스케줄링 결정을 개선하려면 EC2 Auto Scaling 그룹의 가용영역 수와 동일한 수의 노드를 오버프로비저닝하여 스케줄러가 수신 파드에 가장 적합한 영역을 선택할 수 있도록 하십시오.

+

스케일 다운 축출 방지

+

일부 워크로드는 제거하는데 비용이 많이 듭니다. 빅데이터 분석, 머신 러닝 작업, 테스트 러너는 결국에는 완료되지만 중단될 경우 다시 시작해야 합니다. Cluster Autoscaler는 scale-down-utilization-threshold 이하로 모든 노드를 축소하려고 시도하며, 이로 인해 노드에 남아 있는 모든 파드가 중단될 수 있습니다. 제거 비용이 많이 드는 파드를 Cluster Autoscaler에서 인식하는 레이블로 보호함으로써 이를 방지할 수 있습니다.

+

다음을 확인하십시오.

+
    +
  • 파드를 제거하는 데 비용이 많이 드는 코드에는 cluster-autoscaler.kubernetes.io/safe-to-evict=false라는 어노케이션이 붙어 있습니다.
  • +
+

고급 사용 사례

+

EBS 볼륨

+

영구 스토리지는 데이터베이스 또는 분산 캐시와 같은 스테이트풀(stateful) 애플리케이션을 구축하는 데 매우 중요합니다. EBS 볼륨은 쿠버네티스에서 이런 사용 사례를 지원하지만 특정 영역으로 제한됩니다. 각 AZ별로 별도의 EBS 볼륨을 사용하여 여러 AZ에서 샤딩하면 애플리케이션의 가용성이 높아질 수 있습니다. 그러면 Cluster Autoscaler가 EC2 오토스케일링 그룹 스케일링의 균형을 맞출 수 있습니다.

+

다음을 확인하십시오.

+
    +
  • 노드 그룹 밸런싱은 balance-similar-node-groups=true로 설정하여 활성화됩니다.
  • +
  • 노드 그룹은 가용영역과 EBS 볼륨이 다르다는 점을 제외하면 동일한 설정으로 구성됩니다.
  • +
+

공동 스케줄링

+

머신 러닝 분산 트레이닝 작업은 동일 가용영역에 노드 구성을 통해 레이턴시를 최소화함으로써 상당한 이점을 얻을 수 있습니다. 이런 워크로드는 특정 영역에 여러 개의 파드를 배포합니다. 이는 모든 공동 스케줄링된 파드에 파드 어피니티를 설정하거나 topologyKey: failure-domain.beta.kubernetes.io/zone을 사용하여 노드 어피니티를 설정함으로써 구성할 수 있다. 그러면 Cluster Autoscaler가 수요에 맞춰 특정 영역을 확장합니다. 가용영역당 하나씩 여러 EC2 Auto Scaling 그룹을 할당하여 함께 예약된 전체 워크로드에 대해 페일오버를 활성화할 수 있습니다.

+

다음을 확인하십시오.

+
    +
  • balance-similar-node-groups=false를 설정하여 노드 그룹 밸런싱을 구성할 수 있습니다.
  • +
  • 클러스터가 리전 내 멀티 가용영역 노드 그룹과 단일 가용영역 노드 그룹으로 구성된 경우 노드 어피니티 또는 파드 선점(Preemption)을 사용되어야 합니다.
  • +
  • 노드 어피니티를 사용하여 멀티 가용영역 파드가 단일 가용영역 노드 그룹에 (또는 그 반대의 경우) 스케쥴링되지 않도록 하여야 합니다.
  • +
  • 단일 가용영역에 배포되어야 되는 파드가 멀티 가용영역 노드 그룹에 스케줄링되면 멀티 가용영역 파드의 용량 불균형을 초래할 수 있습니다.
  • +
  • 단일 가용영역 워크로드가 중단 및 재배치를 허용할 수 있는 경우, Pod Preemption을 구성하여 지역적으로 규모가 조정된 파드가 경쟁이 덜한 구역을 선점하고 일정을 조정할 수 있도록 하십시오.
  • +
+

가속 하드웨어

+

일부 클러스터는 GPU와 같은 특수 하드웨어 가속기를 활용합니다. 스케일아웃 시 가속기 장치 플러그인이 리소스를 클러스터에 알리는 데 몇 분 정도 걸릴 수 있습니다. Cluster Autoscaler는 이 노드에 가속기가 있을 것이라고 시뮬레이션했지만, 가속기가 준비되고 노드의 가용 리소스를 업데이트하기 전까지는 노드에서 보류 중인 파드를 스케줄링할 수 없습니다. 이로 인해 반복적인 불필요한 확장이 발생할 수 있습니다.

+

또한 가속기가 있고 CPU 또는 메모리 사용률이 높은 노드는 가속기를 사용하지 않더라도 축소가 고려되지 않습니다. 이 동작은 가속기의 상대적 비용 때문에 비용이 많이 들 수 있습니다. 대신 Cluster Autoscaler는 비어있는 가속기가 있는 경우 노드 축소를 고려하는 특수 규칙을 적용할 수 있습니다.

+

이런 경우에 올바르게 동작하도록 가속기 노드가 클러스터에 조인하기 전에 해당 노드 kubelet에 레이블을 추가하여 설정할 수 있습니다. Cluster Autoscaler는 이 레이블을 통해 가속기 최적화 동작을 트리거합니다.

+

다음을 확인하세오.

+
    +
  • GPU 노드용 Kubelet은 --node-labels k8s.amazonaws.com/accelerator=$ACCELERATOR_TYPE으로 구성되어 있습니다.
  • +
  • 가속기가 있는 노드는 위에서 언급한 것과 동일한 스케줄링 속성 규칙을 준수합니다.
  • +
+

0부터 스케일링

+

Cluster Autoscaler(CA)는 노드 그룹을 0까지 또는 0부터 확장할 수 있어 비용을 크게 절감할 수 있습니다. CA는 오토 스케일링 그룹(ASG)의 LaunchConfiguration 또는 LaunchTemplate에 지정된 인스턴스 유형을 검사하여 ASG의 CPU, 메모리 및 GPU 리소스를 파악합니다. 일부 파드는 LaunchConfiguration에서 검색할 수 없는 WindowsENI, PrivateIPv4Address, NodeSelector 또는 테인트와 같은 추가 리소스가 필요합니다. Cluster Autoscaler는 EC2 ASG의 태그에서 이런 요소를 발견하여 이런 요소를 처리할 수 있습니다. 예를 들면 다음과 같습니다.

+
Key: k8s.io/cluster-autoscaler/node-template/resources/$RESOURCE_NAME
+Value: 5
+Key: k8s.io/cluster-autoscaler/node-template/label/$LABEL_KEY
+Value: $LABEL_VALUE
+Key: k8s.io/cluster-autoscaler/node-template/taint/$TAINT_KEY
+Value: NoSchedule
+
+

참고: 0으로 확장할 경우 용량은 EC2로 반환되며 향후에는 사용할 수 없게 될 수 있습니다.

+

추가 파라미터

+

Cluster Autoscaler의 동작과 성능을 조정하는 데 사용할 수 있는 많은 설정 옵션이 있습니다. +파라미터의 전체 목록은 Github에서 확인할 수 있습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
파라미터설명Default
scan-interval클러스터 확장 또는 축소를 위한 재평가 빈도10 초
max-empty-bulk-delete동시에 삭제할 수 있는 빈 노드의 최대 수10
scale-down-delay-after-add스케일 업 후 스케일 다운 평가가 재개되는 시간10 분
scale-down-delay-after-delete노드 삭제 후 스케일 다운 평가가 재개되는 시간, 기본값은 scan-intervalscan-interval
scale-down-delay-after-failure스케일 다운 실패 후 스케일 다운 평가가 재개되는 기간3 분
scale-down-unneeded-time노드를 축소할 수 있으려면 해당 노드가 불필요해야 하는 기간10 분
scale-down-unready-time준비되지 않은 노드가 스케일 다운 대상이 되기까지 불필요하게 되는 기간20분
scale-down-utilization-threshold노드 사용률 수준, 요청된 리소스의 합계를 용량으로 나눈 값으로 정의되며, 이 수준 이하로 노드를 축소할 수 있음0.5
scale-down-non-empty-candidates-count한 번의 반복에서 드레인을 통한 스케일 다운 대상으로 간주되는 비어 있지 않은 최대 노드의 수. 값이 낮을수록 CA 응답성은 향상되지만 스케일 다운 지연 시간은 더 느릴 수 있습니다. 값이 높을수록 대규모 클러스터 (수백 개 노드) 의 CA 성능에 영향을 미칠 수 있습니다.이 휴리스틱을 끄려면 양수가 아닌 값으로 설정하십시오. CA는 고려하는 노드 수를 제한하지 않습니다.30
scale-down-candidates-pool-ratio이전 반복의 일부 후보가 더 이상 유효하지 않을 때 축소할 수 있는 비어 있지 않은 추가 후보로 간주되는 노드의 비율입니다.값이 낮을수록 CA 응답성은 향상되지만 스케일 다운 지연 시간은 더 느릴 수 있습니다.값이 높을수록 대규모 클러스터 (수백 개 노드) 의 CA 성능에 영향을 미칠 수 있습니다.이 휴리스틱을 끄려면 1.0으로 설정합니다. CA는 모든 노드를 추가 후보로 사용합니다.0.1
scale-down-candidates-pool-min-count이전 반복의 일부 후보가 더 이상 유효하지 않을 경우 축소할 수 있는 비어 있지 않은 추가 후보로 간주되는 최소 노드 수. 추가 후보의 풀 크기를 계산할 때는 최대값 (노드수 * scale-down-candidates-pool-ratio, scale-down-candidates-pool-min-count)으로 계산합니다.50
+

추가 리소스

+

이 페이지에는 Cluster Autoscaler 프레젠테이션 및 데모 목록이 들어 있습니다. 여기에 프레젠테이션이나 데모를 추가하려면 풀 리퀘스트를 보내주세요.

+ + + + + + + + + + + + + + + + + +
프레젠테이션 데모발표자
Autoscaling and Cost Optimization on Kubernetes: From 0 to 100Guy Templeton, Skyscanner & Jiaxin Shan, Amazon
SIG-Autoscaling Deep DiveMaciek Pytel & Marcin Wielgus
+

참고 자료

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cost_optimization/awareness/index.html b/ko/cost_optimization/awareness/index.html new file mode 100644 index 000000000..9aba640ee --- /dev/null +++ b/ko/cost_optimization/awareness/index.html @@ -0,0 +1,2636 @@ + + + + + + + + + + + + + + + + + + + + + + + Awareness - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Expenditure awareness

+

Expenditure awareness is understanding who, where and what is causing expenditures in your EKS cluster. Getting an accurate picture of this data will help raise awareness of your spend and highlight areas to remediate.

+

Recommendations

+

Use Cost Explorer

+

AWS Cost Explorer has an easy-to-use interface that lets you visualize, understand, and manage your AWS costs and usage over time. You can analyze cost and usage data, at various levels using the filters available in Cost Explorer.

+

EKS Control Plane and EKS Fargate costs

+

Using the filters, we can query the costs incurred for the EKS costs at the Control Plane and Fargate Pod as shown in the diagram below:

+

Cost Explorer - EKS Control Plane

+

Using the filters, we can query the aggregate costs incurred for the Fargate Pods across regions in EKS - which includes both vCPU-Hours per CPU and GB Hrs as shown in the diagram below:

+

Cost Explorer - EKS Fargate

+

Tagging of Resources

+

Amazon EKS supports adding AWS tags to your Amazon EKS clusters. This makes it easy to control access to the EKS API for managing your clusters. Tags added to an EKS cluster are specific to the AWS EKS cluster resource, they do not propagate to other AWS resources used by the cluster such as EC2 instances or load balancers. Today, cluster tagging is supported for all new and existing EKS clusters via the AWS API, Console, and SDKs.

+

AWS Fargate is a technology that provides on-demand, right-sized compute capacity for containers. Before you can schedule pods on Fargate in your cluster, you must define at least one Fargate profile that specifies which pods should use Fargate when they are launched.

+

Adding and Listing tags to an EKS cluster: +

$ aws eks tag-resource --resource-arn arn:aws:eks:us-west-2:xxx:cluster/ekscluster1 --tags team=devops,env=staging,bu=cio,costcenter=1234
+$ aws eks list-tags-for-resource --resource-arn arn:aws:eks:us-west-2:xxx:cluster/ekscluster1
+{
+    "tags": {
+        "bu": "cio",
+        "env": "staging",
+        "costcenter": "1234",
+        "team": "devops"
+    }
+}
+
+After you activate cost allocation tags in the AWS Cost Explorer, AWS uses the cost allocation tags to organize your resource costs on your cost allocation report, to make it easier for you to categorize and track your AWS costs.

+

Tags don't have any semantic meaning to Amazon EKS and are interpreted strictly as a string of characters. For example, you can define a set of tags for your Amazon EKS clusters to help you track each cluster's owner and stack level.

+

Use AWS Trusted Advisor

+

AWS Trusted Advisor offers a rich set of best practice checks and recommendations across five categories: cost optimization; security; fault tolerance; performance; and service limits.

+

For Cost Optimization, Trusted Advisor helps eliminate unused and idle resources and recommends making commitments to reserved capacity. The key action items that will help Amazon EKS will be around low utilsed EC2 instances, unassociated Elastic IP addresses, Idle Load Balancers, underutilized EBS volumes among other things. The complete list of checks are provided at https://aws.amazon.com/premiumsupport/technology/trusted-advisor/best-practice-checklist/.

+

The Trusted Advisor also provides Savings Plans and Reserved Instances recommendations for EC2 instances and Fargate which allows you to commit to a consistent usage amount in exchange for discounted rates.

+
+

Note

+

The recommendations from Trusted Advisor are generic recommendations and not specific to EKS.

+
+

Use the Kubernetes dashboard

+

Kubernetes dashboard

+

Kubernetes Dashboard is a general purpose, web-based UI for Kubernetes clusters, which provides information about the Kubernetes cluster including the resource usage at a cluster, node and pod level. The deployment of the Kubernetes dashboard on an Amazon EKS cluster is described in the Amazon EKS documentation.

+

Dashboard provides resource usage breakdowns for each node and pod, as well as detailed metadata about pods, services, Deployments, and other Kubernetes objects. This consolidated information provides visibility into your Kubernetes environment.

+

Kubernetes Dashboard

+

kubectl top and describe commands

+

Viewing resource usage metrics with kubectl top and kubectl describe commands. kubectl top will show current CPU and memory usage for the pods or nodes across your cluster, or for a specific pod or node. The kubectl describe command will give more detailed information about a specific node or a pod. +

$ kubectl top pods
+$ kubectl top nodes
+$ kubectl top pod pod-name --namespace mynamespace --containers
+

+

Using the top command, the output will displays the total amount of CPU (in cores) and memory (in MiB) that the node is using, and the percentages of the node’s allocatable capacity those numbers represent. You can then drill-down to the next level, container level within pods by adding a --containers flag.

+
$ kubectl describe node <node>
+$ kubectl describe pod <pod>
+
+

kubectl describe returns the percent of total available capacity that each resource request or limit represents.

+

kubectl top and describe, track the utilization and availability of critical resources such as CPU, memory, and storage across kubernetes pods, nodes and containers. This awareness will help in understanding resource usage and help in controlling costs.

+

Use CloudWatch Container Insights

+

Use CloudWatch Container Insights to collect, aggregate, and summarize metrics and logs from your containerized applications and microservices. Container Insights is available for Amazon Elastic Kubernetes Service on EC2, and Kubernetes platforms on Amazon EC2. The metrics include utilization for resources such as CPU, memory, disk, and network.

+

The installation of insights is given in the documentation.

+

CloudWatch creates aggregated metrics at the cluster, node, pod, task, and service level as CloudWatch metrics.

+

The following query shows a list of nodes, sorted by average node CPU utilization +

STATS avg(node_cpu_utilization) as avg_node_cpu_utilization by NodeName
+| SORT avg_node_cpu_utilization DESC 
+

+

CPU usage by Container name +

stats pct(container_cpu_usage_total, 50) as CPUPercMedian by kubernetes.container_name 
+| filter Type="Container"
+
+Disk usage by Container name +
stats floor(avg(container_filesystem_usage/1024)) as container_filesystem_usage_avg_kb by InstanceId, kubernetes.container_name, device 
+| filter Type="ContainerFS" 
+| sort container_filesystem_usage_avg_kb desc
+

+

More sample queries are given in the Container Insights documention

+

This awareness will help in understanding resource usage and help in controlling costs.

+

Using Kubecost for expenditure awareness and guidance

+

Third party tools like kubecost can also be deployed on Amazon EKS to get visibility into cost of running your Kubernetes cluster. Please refer to this AWS blog for tracking costs using Kubecost

+

Deploying kubecost using Helm 3: +

$ curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
+$ helm version --short
+v3.2.1+gfe51cd1
+$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/
+$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/c^C
+$ kubectl create namespace kubecost 
+namespace/kubecost created
+$ helm repo add kubecost https://kubecost.github.io/cost-analyzer/ 
+"kubecost" has been added to your repositories
+
+$ helm install kubecost kubecost/cost-analyzer --namespace kubecost --set kubecostToken="aGRoZEBqc2pzLmNvbQ==xm343yadf98"
+NAME: kubecost
+LAST DEPLOYED: Mon May 18 08:49:05 2020
+NAMESPACE: kubecost
+STATUS: deployed
+REVISION: 1
+TEST SUITE: None
+NOTES:
+--------------------------------------------------Kubecost has been successfully installed. When pods are Ready, you can enable port-forwarding with the following command:
+
+    kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090
+
+Next, navigate to http://localhost:9090 in a web browser.
+$ kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090
+
+Note: If you are using Cloud 9 or have a need to forward it to a different port like 8080, issue the following command
+$ kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 8080:9090
+
+Kubecost Dashboard - +Kubernetes Cluster Auto Scaler logs

+

Use Kubernetes Cost Allocation and Capacity Planning Analytics Tool

+

Kubernetes Opex Analytics is a tool to help organizations track the resources being consumed by their Kubernetes clusters to prevent overpaying. To do so it generates, short- (7 days), mid- (14 days) and long-term (12 months) usage reports showing relevant insights on what amount of resources each project is spending over time.

+

Kubernetes Opex Analytics

+

Magalix Kubeadvisor

+

KubeAdvisor continuously scans your Kubernetes clusters and reports how you can fix issues, apply best practices, and optimize your cluster (with recommendations of resources like CPU/Memory around cost-efficiency).

+

Spot.io, previously called Spotinst

+

Spotinst Ocean is an application scaling service. Similar to Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups, Spotinst Ocean is designed to optimize performance and costs by leveraging Spot Instances combined with On-Demand and Reserved Instances. Using a combination of automated Spot Instance management and the variety of instance sizes, the Ocean cluster autoscaler scales according to the pod resource requirements. Spotinst Ocean also includes a prediction algorithm to predict Spot Instance interruption 15 minutes ahead of time and spin up a new node in a different Spot capacity pool.

+

This is available as an AWS Quickstart developed by Spotinst, Inc. in collaboration with AWS.

+

The EKS workshop also has a module on Optimized Worker Node on Amazon EKS Management with Ocean by Spot.io which includes sections on cost allocation, right sizing and scaling strategies.

+

Yotascale

+

Yotascale helps with accurately allocating Kubernetes costs. Yotascale Kubernetes Cost Allocation feature utilizes actual cost data, which is inclusive of Reserved Instance discounts and spot instance pricing instead of generic market-rate estimations, to inform the total Kubernetes cost footprint

+

More details can be found at their website.

+

Alcide Advisor

+

Alcide is an AWS Partner Network (APN) Advanced Technology Partner. Alcide Advisor helps ensure your Amazon EKS cluster, nodes, and pods configuration are tuned to run according to security best practices and internal guidelines. Alcide Advisor is an agentless service for Kubernetes audit and compliance that’s built to ensure a frictionless and secured DevSecOps flow by hardening the development stage before moving to production.

+

More details can be found in this blog post.

+

Other tools

+

Kubernetes Garbage Collection

+

The role of the Kubernetes garbage collector is to delete certain objects that once had an owner, but no longer have an owner.

+

Fargate count

+

Fargatecount is an useful tool, which allows AWS customers to track, with a custom CloudWatch metric, the total number of EKS pods that have been deployed on Fargate in a specific region of a specific account. This helps in keeping track of all the Fargate pods running across an EKS cluster.

+

Popeye - A Kubernetes Cluster Sanitizer

+

Popeye - A Kubernetes Cluster Sanitizer is a utility that scans live Kubernetes cluster and reports potential issues with deployed resources and configurations. It sanitizes your cluster based on what's deployed and not what's sitting on disk. By scanning your cluster, it detects misconfigurations and helps you to ensure that best practices are in place

+

Resources

+

Refer to the following resources to learn more about best practices for cost optimization.

+

Documentation and Blogs

+ +

Tools

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cost_optimization/cfm_framework/index.html b/ko/cost_optimization/cfm_framework/index.html new file mode 100644 index 000000000..b7568507f --- /dev/null +++ b/ko/cost_optimization/cfm_framework/index.html @@ -0,0 +1,2241 @@ + + + + + + + + + + + + + + + + + + + + + + + 클라우드 재무 관리 프레임워크 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

비용 최적화 - 소개

+

AWS 클라우드 이코노믹스(Economics)는 고객이 Amazon EKS와 같은 최신 컴퓨팅 기술을 채택하여 효율성을 높이고 비용을 절감할 수 있도록 지원하는 분야입니다. 이 분야에서는 4가지 기반원칙(Pillar)으로 구성된 "클라우드 재무 관리(CFM) 프레임워크"라는 방법론을 따르는 것을 권장합니다.

+

CFM 프레임워크

+

See 원칙: 측정 및 책임

+

See 원칙는 클라우드 지출을 측정, 모니터링 및 결정하는 방법을 정의하는 기본 활동 및 기술 세트입니다. 이를 흔히 "옵저버빌리티", "계측" 또는 "텔레메트리"라고 합니다. "옵저버빌리티" 인프라의 기능 및 한계에 따라 최적화할 수 있는 항목이 결정됩니다. 어디서부터 시작해야 하는지를 알아야 하므로 비용을 명확하게 파악하는 것이 비용 최적화의 중요한 첫 단계입니다. 이러한 유형의 가시성은 환경을 더욱 최적화하기 위해 수행해야 하는 활동 유형을 안내하기도 합니다.

+

다음은 See 기둥의 모범 사례에 대한 간략한 개요입니다.

+
    +
  • 워크로드에 대한 태깅 전략을 정의하고 유지 관리합니다.
      +
    • 인스턴스 태깅 을 사용하면 EKS 클러스터에 태그를 지정하여 개별 클러스터 비용을 확인하고 비용 및 사용 보고서에 비용을 할당할 수 있습니다.
    • +
    +
  • +
  • Kubecost 와 같은 기술을 사용하여 EKS 사용에 대한 보고 및 모니터링을 설정합니다. +
  • +
  • 애플리케이션, LOB(Line of Business) 및 수입원 워크로드 등에 클라우드 비용을 할당합니다.
  • +
  • 효율성/가치 KPI를 정의, 측정하고 비즈니스 이해 관계자와 함께 공유합니다. 예를 들어 거래당 비용을 측정하는 "단위 지표" KPI를 만들 수 있습니다. 예를 들어, 차량 공유 서비스에는 "승차 당 비용"에 대한 KPI가 있을 수 있습니다.
  • +
+

이 기반원칙과 관련된 권장 기술 및 활동에 대한 자세한 내용은 이 가이드의 비용 최적화 - 옵저버빌리티 섹션을 참조하십시오.

+

Save 원칙: 비용 최적화

+

이 기반원칙은 "See" 기반원칙에서 개발된 기술과 역량을 기반으로 합니다.일반적으로 다음과 같은 활동이 이 기반원칙에 속합니다.

+
    +
  • 환경에서 불필요한 부분을 식별하고 제거하세요.
  • +
  • 비용 효율성을 위한 아키텍쳐를 구성하고 디자인하세요.
  • +
  • 최적의 구매 옵션(예: 온디맨드 인스턴스와 스팟 인스턴스)을 선택하세요.
  • +
  • 서비스가 발전함에 따라 (또는 AWS 서비스가 발전함에 따라) 서비스를 효율적으로 사용하는 방법도 달라질 수 있습니다. 이러한 변화에 맞춰 적극적으로 적용하세요.
  • +
+

이러한 활동은 운영되므로 환경의 특성에 따라 크게 달라집니다. 비용의 주요 동인은 무엇인지 스스로 자문해 보세요. 다양한 환경이 제공하는 비즈니스 가치는 무엇입니까? 각 환경에 가장 적합한 구매 옵션과 인프라(예: 인스턴스 패밀리 유형)는 무엇입니까?

+

다음은 EKS 클러스터에서 가장 일반적으로 비용을 유발하는 목록 순위입니다.

+
    +
  1. 컴퓨팅 비용: 여러 유형의 인스턴스 패밀리, 구매 옵션을 결합하고 확장성과 가용성의 균형을 맞추려면 신중한 고려가 필요합니다. 자세한 내용은 이 가이드의 비용 최적화 - 컴퓨팅 섹션을 참조합니다.
  2. +
  3. 네트워킹 비용: EKS 클러스터에 3개의 AZ를 사용하면 잠재적으로 AZ 간 트래픽 비용이 증가할 수 있습니다. HA 요구 사항과 네트워크 트래픽 비용 절감의 균형을 맞추는 방법에 대한 권장 사항은 이 가이드의 비용 최적화 - 네트워킹 섹션을 참조합니다.
  4. +
  5. 스토리지 비용: EKS 클러스터의 워크로드 스테이트풀/스테이트리스(Stateless) 워크로드 특성과 다양한 스토리지 유형이 사용되는 방식에 따라 스토리지는 워크로드의 일부로 간주될 수 있습니다. EKS 스토리지 비용과 관련된 고려 사항은 이 가이드의 비용 최적화 - 스토리지 섹션을 참조합니다.
  6. +
+

Plan 원칙: 플래닝 및 예측

+

See 기반원칙의 권장 사항이 구현되면 클러스터는 지속적으로 최적화됩니다. 클러스터를 효율적으로 운영해 본 경험이 쌓이면 계획 및 예측 활동은 다음 사항에 집중할 수 있습니다.

+
    +
  • 동적으로 클라우드 비용의 예산을 책정하고 예측합니다.
  • +
  • EKS 컨테이너 서비스가 제공하는 비즈니스 가치를 정량화합니다.
  • +
  • EKS 클러스터 비용 관리를 IT 재무 관리 계획과 통합합니다.
  • +
+

Run 원칙

+

비용 최적화는 지속적인 프로세스이며 점진적인 개선이 수반됩니다.

+

Cost optimization flywheel

+

이러한 유형의 활동에 대한 경영진 후원을 확보하는 것은 EKS 클러스터 최적화를 조직의 "FinOps" 노력에 통합하는 데 매우 중요합니다. 이를 통해 EKS 클러스터 비용에 대한 이해를 공유하고, EKS 클러스터 비용 가드레일을 구현하고, 도구, 자동화 및 활동이 조직의 요구에 맞게 발전하도록 함으로써 이해 관계자를 조정할 수 있습니다.

+

참고 자료

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cost_optimization/cost_opt_compute/index.html b/ko/cost_optimization/cost_opt_compute/index.html new file mode 100644 index 000000000..54983136a --- /dev/null +++ b/ko/cost_optimization/cost_opt_compute/index.html @@ -0,0 +1,2631 @@ + + + + + + + + + + + + + + + + + + + + + + + 컴퓨팅 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

비용 최적화 - 컴퓨팅 및 오토스케일링

+

개발자는 애플리케이션의 리소스 요구 사항(예: CPU 및 메모리)을 초기 예상하지만 이런 리소스 스펙을 지속적으로 조정하지 않으면 비용이 증가하고 성능 및 안정성이 저하될 수 있습니다. 초기에 정확한 예측값을 얻는 것보다 애플리케이션의 리소스 요구 사항을 지속적으로 조정하는 것이 더 중요합니다.

+

아래에 언급된 모범 사례는 비용을 최소화하고 조직이 투자 수익을 극대화할 수 있도록 하는 동시에 비즈니스 성과를 달성하는 비용 인지형 워크로드를 구축하고 운영하는 데 도움이 됩니다. 클러스터 컴퓨팅 비용을 최적화하는 데 있어 가장 중요한 순서는 다음과 같습니다:

+
    +
  1. 워크로드 규모 조정(Right-sizing)
  2. +
  3. 사용되지 않는 용량 줄이기
  4. +
  5. 컴퓨팅 유형(예: 스팟) 및 가속기(예: GPU) 최적화
  6. +
+

워크로드 규모 조정(Right-sizing)

+

대부분의 EKS 클러스터에서 대부분의 비용은 컨테이너식 워크로드를 실행하는 데 사용되는 EC2 인스턴스에서 발생합니다. 워크로드 요구 사항을 이해하지 않고는 컴퓨팅 리소스의 크기를 적절하게 조정할 수 없습니다. 따라서 적절한 요청(request) 및 제한(limit)을 사용하고 필요에 따라 해당 설정을 조정해야 합니다. 또한 인스턴스 크기 및 스토리지 선택과 같은 종속성이 워크로드 성능에 영향을 미쳐 비용과 안정성에 의도하지 않은 다양한 결과를 초래할 수 있습니다.

+

request는 실제 사용률과 일치해야 합니다. 컨테이너의 request가 너무 크면 사용되지 않은 용량이 발생하여 총 클러스터 비용의 큰 부분을 차지합니다. 파드 내 각 컨테이너 (예: 애플리케이션 및 사이드카) 에는 총 파드 한도가 최대한 정확하도록 자체 request 및 limit을 설정해야 합니다.

+

컨테이너에 대한 리소스 요청 및 한도를 추정하는 Goldilocks, KRR, Kubecost와 같은 도구를 활용하세요. 애플리케이션의 특성, 성능/비용 요구 사항 및 복잡성에 따라 어떤 메트릭을 확장하는 것이 가장 좋은지, 애플리케이션 성능이 저하되는 시점 (포화 시점), 그리고 그에 따라 요청 및 제한을 조정하는 방법을 평가해야 합니다. 이 주제에 대한 자세한 지침은 애플리케이션 적정 크기 조정을 참조하십시오.

+

Horizontal Pod Autoscaler(HPA)를 사용하여 실행해야 하는 애플리케이션 복제본 수를 제어하고, Vertical Pod Autoscaler(VPA)를 사용하여 복제본 당 애플리케이션에 필요한 요청 수와 제한을 조정하고, Karpenter 또는 Cluster Autoscaler와 같은 노드 오토스케일링을 사용하여 클러스터의 총 노드 수를 지속적으로 조정하는 것이 좋습니다. Karpenter 및 Cluster Autoscaler를 사용한 비용 최적화 기법은 이 문서의 뒷부분에 설명되어 있습니다.

+

VPA는 워크로드가 최적으로 실행되도록 컨테이너에 할당된 요청 및 제한을 조정할 수 있습니다. VPA를 감사 모드에서 실행하여 자동으로 변경한 후 파드를 다시 시작하지 않도록 해야 합니다. 관찰된 메트릭을 기반으로 변경 사항을 제안합니다. 프로덕션 워크로드에 영향을 미치는 변경 사항은 애플리케이션의 안정성과 성능에 영향을 미칠 수 있으므로 비프로덕션 환경에서 먼저 해당 변경 사항을 검토하고 테스트해야 합니다.

+

소비 감소

+

비용을 절감하는 가장 좋은 방법은 리소스를 적게 프로비저닝하는 것입니다.이를 위한 한 가지 방법은 현재 요구 사항에 따라 워크로드를 조정하는 것입니다. 워크로드가 요구 사항을 정의하고 동적으로 확장되도록 하는 것부터 비용 최적화 노력을 시작해야 합니다. 이를 위해서는 애플리케이션에서 메트릭을 가져오고 PodDisruptionBudgetsPod Readiness Gates와 같은 구성을 설정하여 애플리케이션이 안전하게 동적으로 확장 및 축소할 수 있는지 확인해야 합니다.

+

HPA는 애플리케이션의 성능 및 안정성 요구 사항을 충족하는 데 필요한 복제본 수를 조정할 수 있는 유연한 워크로드 오토스케일러입니다. CPU, 메모리 또는 사용자 지정 메트릭 (예: 큐 깊이, 파드에 대한 연결 수 등) 과 같은 다양한 메트릭을 기반으로 확장 및 축소 시기를 정의할 수 있는 유연한 모델을 제공합니다.

+

쿠버네티스 메트릭 서버는 CPU 및 메모리 사용량과 같은 내장된 지표에 따라 크기를 조정할 수 있지만 Amazon CloudWatch 또는 SQS 대기열 깊이와 같은 다른 지표를 기반으로 확장하려면 KEDA와 같은 이벤트 기반 자동 크기 조정 프로젝트를 고려해야 합니다. KEDA를 CloudWatch 지표와 함께 사용하는 방법에 대해서는 이 블로그 게시물을 참조하십시오. 어떤 지표를 모니터링하고 규모를 조정해야 할지 잘 모르겠다면 중요한 지표 모니터링에 대한 모범 사례를 확인하십시오.

+

워크로드 소비를 줄이면 클러스터에 초과 용량이 생성되며 적절한 자동 크기 조정 구성을 통해 노드를 자동으로 축소하여 총 지출을 줄일 수 있습니다. 컴퓨팅 파워를 수동으로 최적화하지 않는 것이 좋습니다. 쿠버네티스 스케줄러와 노드 오토스케일러는 이 프로세스를 처리하도록 설계되었습니다.

+

미사용 용량 줄이기

+

애플리케이션의 크기를 올바르게 결정하고 초과 요청을 줄인 후 프로비저닝된 컴퓨팅 파워를 줄일 수 있습니다. 위 섹션에서 시간을 들여 워크로드 크기를 올바르게 조정했다면 이 작업을 동적으로 수행할 수 있을 것입니다.AWS의 쿠버네티스와 함께 사용되는 기본 노드 자동 확장 프로그램은 두 가지입니다.

+

Karpenter와 Cluster Autoscaler

+

Karpenter와 쿠버네티스 Cluster Autoscaler는 모두 파드가 생성되거나 제거되고 컴퓨팅 요구 사항이 변경됨에 따라 클러스터의 노드 수를 확장합니다. 둘 다 기본 목표는 같지만 Karpenter는 비용을 줄이고 클러스터 전체 사용을 최적화하는 데 도움이 되는 노드 관리 프로비저닝과 디프로비저닝에 대해 다른 접근 방식을 취합니다.

+

클러스터의 규모가 커지고 워크로드의 다양성이 증가함에 따라 노드 그룹과 인스턴스를 미리 구성하기가 더욱 어려워지고 있습니다.워크로드 요청과 마찬가지로 초기 기준을 설정하고 필요에 따라 지속적으로 조정하는 것이 중요합니다.

+

Cluster Autoscaler Priority Expander

+

쿠버네티스 Cluster Autoscaler는 노드 그룹을 확장하거나 축소하는 방식으로 작동합니다. 워크로드를 동적으로 확장하지 않는 경우 클러스터 오토스케일러는 비용 절감에 도움이 되지 않습니다. Cluster Autoscaler를 사용하려면 클러스터 관리자가 워크로드 사용에 대비하여 노드 그룹을 미리 생성해야 합니다. 노드 그룹은 "프로파일"이 동일한 인스턴스, 즉 CPU와 메모리 양이 거의 같은 인스턴스를 사용하도록 구성해야 합니다.

+

노드 그룹을 여러 개 가질 수 있으며 우선 순위 조정 수준을 설정하도록 클러스터 오토스케일러를 구성할 수 있으며 각 노드 그룹은 서로 다른 크기의 노드를 포함할 수 있습니다.노드 그룹은 다양한 용량 유형을 가질 수 있으며 우선 순위 확장기를 사용하여 비용이 저렴한 그룹을 먼저 확장할 수 있습니다.

+

다음은 온디맨드 인스턴스를 사용하기 전에 '컨피그맵'을 사용하여 예약 용량의 우선 순위를 지정하는 클러스터 구성 스니펫의 예입니다. 동일한 기법을 사용하여 다른 유형보다 Graviton 또는 스팟 인스턴스의 우선 순위를 지정할 수 있습니다.

+
apiVersion: eksctl.io/v1alpha5
+kind: ClusterConfig
+metadata:
+  name: my-cluster
+managedNodeGroups:
+  - name: managed-ondemand
+    minSize: 1
+    maxSize: 7
+    instanceType: m5.xlarge
+  - name: managed-reserved
+    minSize: 2
+    maxSize: 10
+    instanceType: c5.2xlarge
+
+
apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cluster-autoscaler-priority-expander
+  namespace: kube-system
+data:
+  priorities: |-
+    10:
+      - .*ondemand.*
+    50:
+      - .*reserved.*
+
+

노드 그룹을 사용하면 기본 컴퓨팅 리소스가 기본적으로 예상한 작업을 수행하는 데 도움이 될 수 있습니다(예: AZ 전체에 노드를 분산시키는 경우). 그러나 모든 워크로드의 요구 사항이나 기대치가 동일하지는 않으므로 애플리케이션이 요구 사항을 명시적으로 선언하도록 하는 것이 좋습니다. Cluster Autoscaler에 대한 자세한 내용은 모범 사례 섹션을 참조하십시오.

+

스케줄 조정자

+

Cluster Autoscaler는 스케줄이 필요한 새 파드 또는 사용률이 낮은 노드를 기반으로 클러스터에서 노드 용량을 추가하고 제거할 수 있습니다. 노드에 스케줄링된 후에는 파드 배치를 전체적으로 살펴볼 수 없습니다. 클러스터 오토스케일러를 사용하는 경우 클러스터의 용량 낭비를 방지하기 위해 Kubernetes descheduler도 살펴봐야 합니다.

+

클러스터에 10개의 노드가 있고 각 노드의 사용률이 60%라면 클러스터에서 프로비저닝된 용량의 40% 를 사용하지 않는 것입니다. 클러스터 오토스케일러를 사용하면 노드당 사용률 임계값을 60% 로 설정할 수 있지만, 이렇게 하면 사용률이 60% 미만으로 떨어진 후 단일 노드만 축소하려고 합니다.

+

Descheduler를 사용하면 파드가 스케줄링되거나 클러스터에 노드가 추가된 후 클러스터 용량 및 사용률을 확인할 수 있습니다. 클러스터의 총 용량을 지정된 임계값 이상으로 유지하려고 시도합니다. 또한 노드 테인트나 클러스터에 합류하는 새 노드를 기반으로 파드를 제거하여 파드가 최적의 컴퓨팅 환경에서 실행되도록 할 수 있습니다. 참고로 Descheduler는 제거된 파드의 교체를 스케줄링하지 않고 기본 스케줄러를 사용한다.

+

Karpenter 통합 기능

+

Karpenter는 노드 관리에 대해 "그룹과 무관한(groupless)" 접근 방식을 취합니다. 이 접근 방식은 다양한 워크로드 유형에 더 유연하며 클러스터 관리자의 사전 구성이 덜 필요합니다. 그룹을 미리 정의하고 워크로드 필요에 따라 각 그룹을 조정하는 대신 Karpenter는 프로비저너와 노드 템플릿을 사용하여 생성할 수 있는 EC2 인스턴스 유형과 생성 시 인스턴스에 대한 설정을 광범위하게 정의합니다.

+

빈패킹(Bin Packing)은 더 적은 수의 최적 크기의 인스턴스에 더 많은 워크로드를 패킹하여 인스턴스의 리소스를 더 많이 활용하는 방법입니다. 이렇게 하면 워크로드에서 사용하는 리소스만 프로비저닝하여 컴퓨팅 비용을 줄이는 데 도움이 되지만 절충점이 있습니다. 특히 대규모 확장 이벤트의 경우 클러스터에 용량을 추가해야 하므로 새 워크로드를 시작하는 데 시간이 더 오래 걸릴 수 있습니다. 빈패킹을 설정할 때는 비용 최적화, 성능 및 가용성 간의 균형을 고려하십시오.

+

Karpenter는 지속적으로 모니터링하고 빈패킹하여 인스턴스 리소스 사용률을 높이고 컴퓨팅 비용을 낮출 수 있습니다. 또한 Karpenter는 워크로드에 대해 더 비용 효율적인 워커 노드를 선택할 수 있습니다. 프로비저닝 도구에서 "consolidation" 플래그를 true로 설정하면 이를 달성할 수 있습니다 (아래 샘플 코드 스니펫 참조). 아래 예제는 통합을 지원하는 프로비저닝 도구의 예를 보여줍니다. 이 안내서를 작성하는 시점에서 Karpenter는 실행 중인 스팟 인스턴스를 더 저렴한 스팟 인스턴스로 대체하지 않을 것입니다. Karpenter 통합에 대한 자세한 내용은 이 블로그를 참조하십시오.

+
apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+  name: enable-binpacking
+spec:
+  consolidation:
+    enabled: true
+
+

중단이 불가능할 수 있는 워크로드(예: 체크포인트 없이 장기간 실행되는 일괄 작업)의 경우, 파드에 do-not-evict 어노테이션을 달아 보세요. 파드를 제거에서 제외시키는 것은 Karpenter가 이 파드를 포함하는 노드를 자발적으로 제거해서는 안 된다고 말하는 것입니다. 하지만 노드가 드레이닝되는 동안 노드에 do-not-evict 파드가 추가되면 나머지 파드는 여전히 제거되지만 해당 파드는 제거될 때까지 종료를 차단합니다. 어느 경우든 노드에 추가 작업이 스케줄링되는 것을 방지하기 위해 노드는 cordon됩니다. 다음은 어노테이션을 설정하는 방법을 보여주는 예시입니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: label-demo
+  labels:
+    environment: production
+  annotations:  
+    "karpenter.sh/do-not-evict": "true"
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+
+

Cluster Autoscaler 파라미터를 조정하여 사용률이 낮은 노드를 제거합니다.

+

노드 사용률은 요청된 리소스의 합계를 용량으로 나눈 값으로 정의됩니다. 기본적으로 'scale-down-utilization-threshold'은 50% 로 설정됩니다. 이 파라미터는 'scale-down-unneeded-time'과 함께 사용할 수 있습니다. 이 시간은 노드를 축소할 수 있을 때까지 필요하지 않게 되는 기간을 결정합니다. 기본값은 10분입니다.축소된 노드에서 여전히 실행 중인 파드는 kube-scheduler에 의해 다른 노드에 스케줄링됩니다. 이러한 설정을 조정하면 활용도가 낮은 노드를 제거하는 데 도움이 될 수 있지만, 클러스터를 조기에 강제로 축소하지 않도록 먼저 이 값을 테스트하는 것이 중요합니다.

+

제거하는 데 비용이 많이 드는 파드를 클러스터 오토스케일러에서 인식하는 레이블로 보호함으로써 스케일 다운이 발생하지 않도록 할 수 있습니다. 이렇게 하려면 제거 비용이 많이 드는 파드에 cluster-autoscaler.kubernetes.io/safe-to-evict=false라는 주석을 달아야 한다. 다음은 어노테이션을 설정하는 yaml의 예시입니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: label-demo
+  labels:
+    environment: production
+  annotations:  
+    "cluster-autoscaler.kubernetes.io/safe-to-evict": "false"
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    ports:
+    - containerPort: 80
+
+

노드에 Cluster Autoscaler 및 Karpenter 태그 지정

+

AWS 태그는 리소스를 구성하고 세부 수준에서 AWS 비용을 추적하는 데 사용됩니다. 비용 추적을 위한 Kubernetes 레이블과 직접적인 상관 관계를 맺지는 않습니다. 먼저 쿠버네티스 리소스 레이블링으로 시작하고 Kubecost와 같은 도구를 활용하여 파드, 네임스페이스 등의 쿠버네티스 레이블을 기반으로 인프라 비용을 보고하는 것이 좋습니다.

+

AWS Cost Explorer에서 결제 정보를 표시하려면 워커 노드에 태그가 있어야 합니다. Cluster Autoscaler를 사용하면 시작 템플릿을 사용하여 관리형 노드 그룹 내의 워커 노드에 태그를 지정합니다. 자체 관리형 노드 그룹의 경우 EC2 Auto Scaling 그룹 을 사용하여 인스턴스에 태그를 지정합니다. Karpenter에서 프로비저닝한 인스턴스의 경우 노드 템플릿의 spec.tags를 사용하여 태그를 지정하십시오.

+

멀티 테넌트 클러스터

+

다른 팀이 공유하는 클러스터에서 작업하는 경우 동일한 노드에서 실행되는 다른 워크로드를 파악하지 못할 수 있습니다. 리소스 요청은 CPU 공유와 같은 일부 "시끄러운 이웃(noisy neighbor)" 문제를 격리하는 데 도움이 될 수 있지만 디스크 I/O 병목과 같은 모든 리소스 경계를 분리하지는 못할 수도 있습니다. 워크로드에 의해 사용 가능한 모든 리소스를 분리하거나 제한할 수는 없습니다. 다른 워크로드보다 높은 비율로 공유 리소스를 사용하는 워크로드는 노드 taint와 toleration을 통해 격리해야 합니다. 이러한 워크로드를 위한 또 다른 고급 기법은 컨테이너의 공유 CPU 대신 전용 CPU를 보장하는 CPU pinning을 적용할 수 있습니다.

+

워크로드를 노드 수준에서 분리하는 것은 비용이 더 많이 들 수 있지만 예약 인스턴스, Graviton 프로세서 또는 스팟 인스턴스을 사용하여 BestEffort 작업을 예약하거나 추가 비용 절감을 활용할 수 있습니다.

+

공유 클러스터에는 IP 고갈, 쿠버네티스 서비스 제한 또는 API 확장 요청과 같은 클러스터 수준 리소스 제약이 있을 수도 있습니다. 확장성 모범 사례 가이드를 검토하여 클러스터가 이러한 제한이 없는지 확인해야 합니다.

+

네임스페이스 또는 Karpenter 프로비저너 수준에서 리소스를 격리할 수 있습니다. 리소스 할당량(Quota)은 네임스페이스의 워크로드가 소비할 수 있는 리소스 수를 제한하는 방법을 제공합니다. 이는 초기 보호 수단으로 유용할 수 있지만, 워크로드 확장을 인위적으로 제한하지 않도록 지속적으로 평가해야 합니다.

+

Karpenter 프로비저너는 클러스터에서 일부 사용 가능한 리소스(예: CPU, GPU)에 제한을 설정할 수 있지만 적절한 프로비저너를 사용하도록 테넌트 애플리케이션을 구성해야 합니다. 이렇게 하면 단일 제공자가 클러스터에 너무 많은 노드를 생성하는 것을 방지할 수 있지만, 제한을 너무 낮게 설정하지 않도록 지속적으로 평가하여 워크로드가 확장되지 않도록 해야 합니다.

+

오토스케일링 스케줄링

+

주말 또는 휴일에는 클러스터를 축소해야 할 수도 있습니다. 이는 사용하지 않을 때 0으로 축소하려는 테스트 및 비프로덕션 클러스터에 특히 적합합니다. cluster-turndown 와 같은 솔루션은 크론 스케줄에 따라 복제본을 0으로 축소할 수 있습니다. 다음 AWS 블로그에 설명된 대로 Karpenter를 사용하여 동일한 작업을 수행할 수도 있습니다.

+

컴퓨팅 용량 유형 최적화

+

클러스터의 총 컴퓨팅 용량을 최적화하고 빈 패킹을 사용한 후에는 클러스터에 프로비저닝한 컴퓨팅 유형과 해당 리소스에 대한 비용을 확인해야 합니다. AWS는 컴퓨팅 비용을 절감할 수 있는 컴퓨팅 절감형 플랜(Savings Plan)을 운영하고 있으며, 이를 다음과 같은 용량 유형으로 분류해 보겠습니다.

+
    +
  • 스팟
  • +
  • 절감형 플랜(Savings Plan)
  • +
  • 온디맨드
  • +
  • Fargate
  • +
+

각 용량 유형에는 관리 오버헤드, 가용성 및 장기 약정 측면에서 서로 다른 절충점이 있으므로 환경에 적합한 것을 결정해야 합니다. 어떤 환경도 단일 용량 유형에 의존해서는 안 되며 단일 클러스터에 여러 실행 유형을 혼합하여 특정 워크로드 요구 사항 및 비용을 최적화할 수 있습니다.

+

스팟 인스턴스

+

스팟 용량 유형은 가용영역의 예비 용량에서 EC2 인스턴스를 프로비저닝합니다. 스팟은 최대 90% 까지 할인을 제공하지만, 다른 곳에서 필요할 경우 해당 인스턴스가 중단될 수 있습니다. 또한 새 스팟 인스턴스를 프로비저닝할 용량이 항상 있는 것은 아니며 2분 중단 알림을 통해 기존 스팟 인스턴스를 회수할 수 있습니다. 애플리케이션의 시작 또는 종료 프로세스가 오래 걸리는 경우 스팟 인스턴스가 최선의 옵션이 아닐 수 있습니다.

+

스팟 컴퓨팅은 다양한 인스턴스 유형을 사용하여 사용 가능한 스팟 용량이 없을 가능성을 줄여야 합니다. 노드를 안전하게 종료하려면 인스턴스 중단을 처리해야 합니다. Karpenter 또는 관리형 노드 그룹의 일부로 프로비저닝된 노드는 인스턴스 중단 알림을 자동으로 지원합니다. 자체 관리형 노드를 사용하는 경우 노드 종료 핸들러를 별도로 실행하여 스팟 인스턴스를 정상적으로 종료해야 합니다.

+

단일 클러스터에서 스팟 인스턴스와 온디맨드 인스턴스의 균형을 맞출 수 있습니다. Karpenter를 사용하면 가중치 프로비저너를 생성하여 다양한 용량 유형의 균형을 맞출 수 있습니다. Cluster Autoscaler를 사용하면 스팟 및 온디맨드 또는 예약 인스턴스가 포함된 혼합 노드 그룹을 생성할 수 있습니다.

+

다음은 Karpenter를 사용하여 온디맨드 인스턴스보다 스팟 **** 인스턴스 우선 순위를 높게 정하는 예입니다. 프로비저너를 생성할 때 스팟, 온디맨드 또는 둘 다 지정할 수 있습니다(아래 그림 참조). 둘 다 지정하고 파드가 스팟 또는 온디맨드를 사용해야 하는지 명시적으로 지정하지 않는 경우 Karpenter는 price-capacity-optimization 할당 전략으로 노드를 프로비저닝할 때 스팟의 우선 순위를 지정합니다.

+
apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+  name: spot-prioritized
+spec:
+  requirements:
+    - key: "karpenter.sh/capacity-type" 
+      operator: In
+        values: ["spot", "on-demand"]
+
+

Savings Plans, 예약 인스턴스 및 AWS 엔터프라이즈 할인 프로그램(EDP)

+

Compute Savings Plan을 사용하면 컴퓨팅 비용을 줄일 수 있습니다. Savings Plan은 1년 또는 3년 컴퓨팅 사용 약정 시 할인된 가격을 제공합니다. 사용량은 EKS 클러스터의 EC2 인스턴스에 적용할 수 있지만 Lambda 및 Fargate와 같은 모든 컴퓨팅 사용에도 적용됩니다. Savings Plan을 사용하면 비용을 절감하면서도 약정 기간 동안 모든 EC2 인스턴스 유형을 선택할 수 있습니다.

+

Compute Savings Plan을 사용하면 사용하려는 인스턴스 유형, 제품군 또는 리전에 대한 약정 없이 EC2 비용을 최대 66% 절감할 수 있습니다. 절감액은 인스턴스를 사용할 때 인스턴스에 자동으로 적용됩니다.

+

EC2 인스턴스 Savings Plan은 특정 리전 및 EC2 제품군(예: C 제품군 인스턴스)의 사용량을 약정하여 컴퓨팅 비용을 최대 72% 절감합니다. 리전 내 모든 AZ로 사용량을 전환하고, c5 또는 c6와 같은 모든 세대의 인스턴스 패밀리를 사용하고, 패밀리 내에서 원하는 크기의 인스턴스를 사용할 수 있습니다. 할인은 Savings Plan 기준과 일치하는 계정 내 모든 인스턴스에 자동으로 적용됩니다.

+

예약 인스턴스는 EC2 인스턴스 Savings Plan과 비슷하지만 가용영역 또는 지역의 용량을 보장하고 온디맨드 인스턴스에 비해 비용을 최대 72% 절감합니다 .필요한 예약 용량을 계산한 후 예약 기간(1년 또는 3년)을 선택할 수 있습니다. 어카운트에서 해당 EC2 인스턴스를 실행하면 할인이 자동으로 적용됩니다.

+

또한 고객은 AWS와 기업 계약을 체결할 수 있습니다.기업 계약은 고객에게 요구 사항에 가장 적합한 계약을 조정할 수 있는 옵션을 제공합니다.고객은 AWS 엔터프라이즈 할인 프로그램(EDP, Enterprise Discount Program)를 기반으로 가격 할인을 받을 수 있습니다. 기업 계약에 대한 추가 정보는 AWS 영업 담당자에게 문의하세요.

+

온디맨드

+

온디맨드 EC2 인스턴스는 (스팟에 비해) 중단 없이 사용할 수 있고 (Savings Plan에 비해) 장기 약정이 없다는 이점이 있습니다. 클러스터에서 비용을 절감하려면 온디맨드 EC2 인스턴스의 사용량을 줄여야 합니다.

+

워크로드 요구 사항을 최적화한 후에는 클러스터의 최소 및 최대 용량을 계산하여야 합니다. 이 수치는 시간이 지남에 따라 변경될 수 있지만 감소하는 경우는 거의 없습니다. 최소 금액 미만의 모든 항목에는 Savings Plan을 사용하고 애플리케이션 가용성에 영향을 미치지 않는 용량은 확보해 두는 것이 좋습니다. 지속적으로 사용되지 않거나 가용성이 필요한 다른 모든 항목은 온디맨드로 사용할 수 있습니다.

+

이 섹션에서 언급한 것처럼 사용량을 줄이는 가장 좋은 방법은 리소스를 적게 사용하고 프로비저닝한 리소스를 최대한 활용하는 것입니다. Cluster Autoscaler를 사용하면 scale-down-utilization-threshold 설정으로 사용률이 낮은 노드를 제거할 수 있습니다.Karpenter의 경우 통합을 활성화하는 것이 좋습니다.

+

워크로드에 사용할 수 있는 EC2 인스턴스 유형을 수동으로 식별하려면 ec2-instance-selector를 사용할 수 있습니다. 그러면 각 러전에서 사용 가능한 인스턴스는 물론 EKS와 호환되는 인스턴스도 표시할 수 있습니다. 다음 예는 x86 프로세스 아키텍처, 4Gb 메모리, vCPU 2개를 갖추고 us-east-1 지역에서 사용 가능한 인스턴스를 보여줍니다.

+
ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 \
+  -r us-east-1 --service eks
+c5.large
+c5a.large
+c5ad.large
+c5d.large
+c6a.large
+c6i.large
+t2.medium
+t3.medium
+t3a.medium
+
+

운영 환경이 아닌 경우 야간 및 주말과 같이 사용하지 않는 시간에는 클러스터를 자동으로 축소할 수 있습니다. kubecost 프로젝트 cluster-turndown은 설정된 일정에 따라 클러스터를 자동으로 축소할 수 있는 컨트롤러의 예입니다.

+

Fargate 컴퓨팅

+

Fargate 컴퓨팅은 EKS 클러스터를 위한 완전 관리형 컴퓨팅 옵션입니다. 쿠버네티스 클러스터의 노드당 파드 하나를 스케줄링하여 파드 격리를 제공합니다. 이를 통해 워크로드의 CPU 및 RAM 메모리 요구 사항에 맞게 컴퓨팅 노드의 크기를 조정하여 클러스터의 워크로드 사용을 엄격하게 제어할 수 있습니다.

+

Fargate는 최소 0.25vCPU, 0.5GB 메모리에서부터 최대 16vCPU, 120GB 메모리까지 워크로드를 확장할 수 있습니다. 사용할 수 있는 파드 크기 변형에 제한이 있으므로 Fargate 설정이 워크로드에 적합한지 확인해야 합니다. 예를 들어 워크로드가 vCPU 1개, 0.5GB 메모리가 필요한 경우 이를 위한 가장 작은 Fargate 파드는 vCPU 1개, 2GB 메모리입니다.

+

Fargate는 EC2 인스턴스 또는 운영 체제 관리가 필요 없는 등 많은 이점을 제공하지만 배포된 모든 파드가 클러스터의 개별 노드로 격리되어 있기 때문에 기존 EC2 인스턴스보다 더 많은 컴퓨팅 파워가 필요할 수 있습니다. 이를 위해서는 Kubelet, 로깅 에이전트, 일반적으로 노드에 배포하는 데몬셋 등의 항목을 더 많이 복제해야 합니다. 데몬셋은 Fargate에서 지원되지 않으므로 파드 "사이드카"로 변환하여 애플리케이션과 함께 실행해야 합니다.

+

Fargate는 노드별로 각 워크로드간 분리되기 때문에 버스팅이 불가능하고 공유될 수 없기 때문에 빈패킹이나 CPU 오버프로비저닝의 이점을 누릴 수 없습니다. Fargate를 사용하면 비용이 드는 EC2 인스턴스 관리 시간을 절약할 수 있지만 CPU 및 메모리 비용은 다른 EC2 용량 유형보다 비쌀 수 있습니다. Fargate 파드는 컴퓨팅 Savings Plan을 활용하여 온디맨드 비용을 절감할 수 있습니다.

+

컴퓨팅 사용 최적화

+

컴퓨팅 인프라 비용을 절감하는 또 다른 방법은 워크로드에 더 효율적인 컴퓨팅을 사용하는 것입니다. 이는 x86보다 최대 20% 저렴하고 에너지 효율이 60% 더 높은 Graviton 프로세서와 같이 성능이 더 뛰어난 범용 컴퓨팅이나 GPU 및 FPGA 와 같은 워크로드별 가속기를 통해 얻을 수 있습니다. 워크로드에 맞게 ARM 아키텍처에서 실행하고 적절한 가속기로 노드를 설정할 수 있는 컨테이너를 구축해야 합니다.

+

EKS는 혼합 아키텍처(예: amd64 및 arm64)로 클러스터를 실행할 수 있으며 컨테이너가 멀티 아키텍처용으로 컴파일된 경우 프로비저너에서 두 아키텍처를 모두 허용하여 Karpenter와 함께 Graviton 프로세서를 활용할 수 있습니다. 하지만 성능을 일관되게 유지하려면 각 워크로드를 단일 컴퓨팅 아키텍처에 두고 추가 용량이 없는 경우에만 다른 아키텍처를 사용하는 것이 좋습니다.

+

프로비저너는 여러 아키텍처로 구성할 수 있으며 워크로드는 워크로드 사양에서 특정 아키텍처를 요청할 수도 있습니다.

+
apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+  name: default
+spec:
+  requirements:
+  - key: "kubernetes.io/arch"
+    operator: In
+    values: ["arm64", "amd64"]
+
+

Cluster Autoscaler를 사용하면 Graviton 인스턴스용 노드 그룹을 생성하고 새 용량을 활용하려면 워크로드에 대한 노드 허용 범위를 설정해야 합니다.

+

GPU와 FPGA는 워크로드의 성능을 크게 향상시킬 수 있지만 가속기를 사용하려면 워크로드를 최적화해야 합니다. 머신러닝 및 인공지능을 위한 다양한 워크로드 유형은 컴퓨팅에 GPU를 사용할 수 있으며, 리소스 요청을 사용하여 클러스터에 인스턴스를 추가하고 워크로드에 마운트할 수 있습니다.

+
spec:
+  template:
+    spec:
+    - containers:
+      ...
+      resources:
+          limits:
+            nvidia.com/gpu: "1"
+
+

일부 GPU 하드웨어는 여러 워크로드에서 공유할 수 있으므로 단일 GPU를 프로비저닝하고 사용할 수 있습니다. 워크로드 GPU 공유를 구성하는 방법을 보려면 자세한 내용은 가상 GPU 장치 플러그인을 참조하십시오. 다음 블로그를 참조할 수도 있습니다.

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cost_optimization/cost_opt_networking/index.html b/ko/cost_optimization/cost_opt_networking/index.html new file mode 100644 index 000000000..b3ed78e53 --- /dev/null +++ b/ko/cost_optimization/cost_opt_networking/index.html @@ -0,0 +1,2799 @@ + + + + + + + + + + + + + + + + + + + + + + + 네트워크 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

비용 최적화 - 네트워킹

+

고가용성(HA)을 위한 시스템 아키텍처는 복원력과 내결함성을 달성하기 위한 모범 사례를 통해 구현됩니다. 이는 특정 AWS 리전의 여러 가용영역(AZ)에 워크로드와 기본 인프라를 분산시키는 것을 의미합니다. Amazon EKS 환경에 이러한 특성을 적용하면 시스템의 전반적인 안정성이 향상됩니다. 이와 함께 EKS 환경은 다양한 구조(예: VPC), 구성 요소(예: ELB) 및 통합(예: ECR 및 기타 컨테이너 레지스트리)으로 구성될 가능성이 높습니다.

+

고가용성 시스템과 기타 사용 사례별 구성 요소의 조합은 데이터 전송 및 처리 방식에 중요한 역할을 할 수 있습니다.이는 결국 데이터 전송 및 처리로 인해 발생하는 비용에도 영향을 미칩니다.

+

아래에 자세히 설명된 실천사항은 다양한 도메인 및 사용 사례에서 비용 효율성을 달성하기 위해 EKS 환경을 설계하고 최적화하는 데 도움이 됩니다.

+

파드 간 통신

+

설정에 따라 파드 간 네트워크 통신 및 데이터 전송은 Amazon EKS 워크로드 실행의 전체 비용에 상당한 영향을 미칠 수 있습니다.이 섹션에서는 고가용성 (HA) 아키텍처, 애플리케이션 성능 및 복원력을 고려하면서 파드 간 통신과 관련된 비용을 줄이기 위한 다양한 개념과 접근 방식을 다룹니다.

+

가용영역으로의 트래픽 제한

+

잦은 이그레스 크로스존 트래픽(AZ 간에 분산되는 트래픽)은 네트워크 관련 비용에 큰 영향을 미칠 수 있습니다. 다음은 EKS 클러스터의 파드 간 크로스 존 트래픽 양을 제어하는 방법에 대한 몇 가지 전략입니다.

+

클러스터 내 파드 간 크로스-존 트래픽 양(예: 전송된 데이터 양 또는 바이트 단위 전송)을 세밀하게 파악하려면 이 게시물 참조하세요.

+

Topology Aware Routing (이전 명칭은 Topology Aware Hint) 활용

+

Topology aware routing

+

Topology Aware Routing을 사용할 때는 트래픽을 라우팅할 때 서비스, EndpointSlices 및 kube-proxy가 함께 작동하는 방식을 이해하는 것이 중요합니다. 위 다이어그램에서 볼 수 있듯이 서비스는 파드로 향하는 트래픽을 수신하는 안정적인 네트워크 추상화 계층입니다. 서비스가 생성되면 여러 EndpointSlices가 생성됩니다. 각 EndpointSlice에는 실행 중인 노드 및 추가 토폴로지 정보와 함께 파드 주소의 하위 집합이 포함된 엔드포인트 목록이 있습니다. kube-proxy는 클러스터의 모든 노드에서 실행되고 내부 라우팅 역할도 수행하는 데몬셋이지만, 생성된 EndpointSlices에서 소비하는 양을 기반으로 합니다.

+

Topology aware routing을 활성화하고 쿠버네티스 서비스에 구현하면, EndpointSlices 컨트롤러는 클러스터가 분산되어 있는 여러 영역에 비례적으로 엔드포인트를 할당합니다. EndpointSlices 컨트롤러는 각 엔드포인트에 대해 영역에 대한 힌트 도 설정합니다. 힌트 는 엔드포인트가 트래픽을 처리해야 하는 영역을 설명합니다.그러면 kube-proxy가 적용된 힌트 를 기반으로 영역에서 엔드포인트로 트래픽을 라우팅합니다.

+

아래 다이어그램은 'kube-proxy'가 영역 출발지를 기반으로 가야 할 목적지를 알 수 있도록 힌트가 있는 EndpointSlice를 구성하는 방법을 보여줍니다. 힌트가 없으면 이러한 할당이나 구성이 없으며 트래픽이 어디에서 오는지에 관계없이 서로 다른 지역 목적지로 프록시됩니다.

+

Endpoint Slice

+

경우에 따라 EndPointSlice 컨트롤러는 다른 영역에 대해 힌트 를 적용할 수 있습니다. 즉, 엔드포인트가 다른 영역에서 발생하는 트래픽을 처리하게 될 수 있습니다. 이렇게 하는 이유는 서로 다른 영역의 엔드포인트 간에 트래픽을 균일하게 분배하기 위함입니다.

+

다음은 서비스에 대해 토폴로지 인식 라우팅 을 활성화하는 방법에 대한 코드 스니펫입니다.

+
apiVersion: v1
+kind: Service
+metadata:
+  name: orders-service
+  namespace: ecommerce
+    annotations:
+      service.kubernetes.io/topology-mode: Auto
+spec:
+  selector:
+    app: orders
+  type: ClusterIP
+  ports:
+  - protocol: TCP
+    port: 3003
+    targetPort: 3003
+
+

아래 스크린샷은 EndpointSlices 컨트롤러가 eu-west-1a가용영역에서 실행되는 파드 복제본의 엔드포인트에 힌트를 성공적으로 적용한 결과를 보여준다.

+

Slice shell

+
+

Note

+

Topology aware routing이 아직 베타라는 것 인지해야 합니다. 또한 워크로드가 클러스터 토폴로지 전체에 광범위하고 균등하게 분산되어 있을 때 이 기능을 더 잘 예측할 수 있습니다. 따라서 파드 토폴로지 확산 제약과 같이 애플리케이션의 가용성을 높이는 일정 제약과 함께 사용하는 것이 좋습니다.

+
+

오토스케일러 사용: 특정 가용영역에 노드 프로비저닝

+

여러 가용영역의 고가용성 환경에서 워크로드를 실행하는 것을 강력히 권장 합니다. 이렇게 하면 애플리케이션의 안정성이 향상되며, 특히 가용영역에 문제가 발생한 경우 더욱 그렇습니다. 네트워크 관련 비용을 줄이기 위해 안정성을 희생하려는 경우 노드를 단일 가용영역로 제한할 수 있습니다.

+

동일한 가용영역에서 모든 파드를 실행하려면 동일한 가용영역에 워커 노드를 프로비저닝하거나 동일한 가용영역에서 실행되는 워커 노드에 파드를 스케줄링해야 합니다. 단일 가용영역 내에서 노드를 프로비저닝하려면 Cluster Autoscaler (CA)를 사용하여 동일한 가용영역에 속하는 서브넷으로 노드 그룹을 정의하십시오. Karpenter의 경우 "topology.kubernetes.io/zone"을 사용하고 워커 노드를 만들려는 가용영역를 지정합니다. 예를 들어 아래 카펜터 프로비저닝 스니펫은 us-west-2a 가용영역의 노드를 프로비저닝합니다.

+

Karpenter

+
apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+name: single-az
+spec:
+  requirements:
+  - key: "topology.kubernetes.io/zone"
+    operator: In
+    values: ["us-west-2a"]
+
+

Cluster Autoscaler (CA)

+
apiVersion: eksctl.io/v1alpha5
+kind: ClusterConfig
+metadata:
+  name: my-ca-cluster
+  region: us-east-1
+  version: "1.21"
+availabilityZones:
+- us-east-1a
+managedNodeGroups:
+- name: managed-nodes
+  labels:
+    role: managed-nodes
+  instanceType: t3.medium
+  minSize: 1
+  maxSize: 10
+  desiredCapacity: 1
+...
+
+

파드 할당 및 노드 어피니티 사용

+

또는 여러 가용영역에서 실행되는 워커 노드가 있는 경우 각 노드에는 가용영역 값(예: us-west-2a 또는 us-west-2b)과 함께 topology.kubernetes.io/zone 레이블이 붙습니다.nodeSelector 또는 nodeAffinity를 활용하여 단일 가용영역의 노드에 파드를 스케줄링할 수 있습니다. 예를 들어, 다음 매니페스트 파일은 가용영역 us-west-2a에서 실행되는 노드 내에서 파드를 스케줄링한다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: nginx
+  labels:
+    env: test
+spec:
+  nodeSelector:
+    topology.kubernetes.io/zone: us-west-2a
+  containers:
+  - name: nginx
+    image: nginx 
+    imagePullPolicy: IfNotPresent
+
+

노드로의 트래픽 제한

+

존 레벨에서 트래픽을 제한하는 것만으로는 충분하지 않은 경우가 있습니다. 비용 절감 외에도 상호 통신이 빈번한 특정 애플리케이션 간의 네트워크 지연 시간을 줄여야 하는 추가 요구 사항이 있을 수 있습니다. 최적의 네트워크 성능을 달성하고 비용을 절감하려면 트래픽을 특정 노드로 제한하는 방법이 필요합니다. 예를 들어 마이크로서비스 A는 고가용성(HA)설정에서도 항상 노드 1의 마이크로서비스 B와 통신해야 합니다. 노드 1의 마이크로서비스 A가 노드 2의 마이크로서비스 B와 통신하도록 하면 특히 노드 2가 완전히 별도의 AZ에 있는 경우 이러한 성격의 애플리케이션에 필요한 성능에 부정적인 영향을 미칠 수 있습니다.

+

서비스 내부 트래픽 정책 사용

+

파드 네트워크 트래픽을 노드로 제한하려면 서비스 내부 트래픽 정책 을 사용할 수 있습니다. 기본적으로 워크로드 서비스로 전송되는 트래픽은 생성된 여러 엔드포인트에 무작위로 분산됩니다. 따라서 HA 아키텍처에서는 마이크로서비스 A의 트래픽이 여러 AZ의 특정 노드에 있는 마이크로서비스 B의 모든 복제본으로 이동할 수 있습니다. 하지만 서비스의 내부 트래픽 정책을 local로 설정하면 트래픽이 발생한 노드의 엔드포인트로 트래픽이 제한됩니다. 이 정책은 노드-로컬 엔드포인트를 독점적으로 사용하도록 규정합니다. 암시적으로 보면 해당 워크로드에 대한 네트워크 트래픽 관련 비용이 클러스터 전체에 분산되는 경우보다 낮아질 것입니다.또한 지연 시간이 짧아져 애플리케이션의 성능이 향상됩니다.

+
+

Note

+

이 기능을 쿠버네티스의 토폴로지 인식 라우팅과 결합할 수 없다는 점에 유의해야 합니다.

+
+

Local internal traffic

+

다음은 서비스의 내부 트래픽 정책 을 설정하는 방법에 대한 코드 스니펫입니다.

+
apiVersion: v1
+kind: Service
+metadata:
+  name: orders-service
+  namespace: ecommerce
+spec:
+  selector:
+    app: orders
+  type: ClusterIP
+  ports:
+  - protocol: TCP
+    port: 3003
+    targetPort: 3003
+  internalTrafficPolicy: Local
+
+

트래픽 감소로 인한 예상치 못한 애플리케이션 동작을 방지하려면 다음과 같은 접근 방식을 고려해야 합니다:

+ +

이 예에서는 마이크로서비스 A의 복제본 2개와 마이크로서비스 B의 복제본 3개가 있습니다. 마이크로서비스 A의 복제본이 노드 1과 2 사이에 분산되어 있고 마이크로서비스 B의 복제본이 노드 3에 있는 경우 local 내부 트래픽 정책 때문에 통신할 수 없습니다. 사용 가능한 노드-로컬 엔드포인트가 없으면 트래픽이 삭제됩니다.

+

node-local_no_peer

+

마이크로서비스 B의 노드 1과 2에 복제본 3개 중 2개가 있는 경우 피어 애플리케이션 간에 통신이 이루어집니다.하지만 통신할 피어 복제본이 없는 마이크로서비스 B의 격리된 복제본은 여전히 남아 있을 것입니다.

+

node-local_with_peer

+
+

Note

+

일부 시나리오에서는 위 다이어그램에 표시된 것과 같은 격리된 복제본이 여전히 목적(예: 외부 수신 트래픽의 요청 처리)에 부합한다면 걱정할 필요가 없을 수도 있습니다.

+
+

토폴로지 분산 제약이 있는 서비스 내부 트래픽 정책 사용

+

내부 트래픽 정책토폴로지 확산 제약 과 함께 사용하면 서로 다른 노드의 마이크로서비스와 통신하기 위한 적절한 수의 복제본을 확보하는 데 유용할 수 있습니다.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: express-test
+spec:
+  replicas: 6
+  selector:
+    matchLabels:
+      app: express-test
+  template:
+    metadata:
+      labels:
+        app: express-test
+        tier: backend
+    spec:
+      topologySpreadConstraints:
+      - maxSkew: 1
+        topologyKey: "topology.kubernetes.io/zone"
+        whenUnsatisfiable: ScheduleAnyway
+        labelSelector:
+          matchLabels:
+            app: express-test
+
+

파드 어피니티 규칙과 함께 서비스 내부 트래픽 정책 사용

+

또 다른 접근 방식은 서비스 내부 트래픽 정책을 사용할 때 파드 어피니티 규칙을 사용하는 것입니다. 파드 어피니티를 사용하면 잦은 통신으로 인해 스케줄러가 특정 파드를 같은 위치에 배치하도록 영향을 줄 수 있다. 특정 파드에 엄격한 스케줄링 제약 조건(RequiredDuringSchedulingExecutionDuringExecutionDuringIgnored)을 적용하면, 스케줄러가 파드를 노드에 배치할 때 파드 코로케이션에 대해 더 나은 결과를 얻을 수 있다.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: graphql
+  namespace: ecommerce
+  labels:
+    app.kubernetes.io/version: "0.1.6"
+    ...
+    spec:
+      serviceAccountName: graphql-service-account
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+              - key: app
+                operator: In
+                values:
+                - orders
+            topologyKey: "kubernetes.io/hostname"
+
+

로드밸런서와 파드 통신

+

EKS 워크로드는 일반적으로 트래픽을 EKS 클러스터의 관련 파드로 분산하는 로드밸런서에 의해 선행됩니다. 아키텍처는 내부 또는 외부 로드밸런서로 구성될 수 있습니다. 아키텍처 및 네트워크 트래픽 구성에 따라 로드밸런서와 파드 간의 통신으로 인해 데이터 전송 요금이 크게 증가할 수 있습니다.

+

AWS Load Balancer Controller를 사용하여 ELB 리소스 (ALB 및 NLB) 생성을 자동으로 관리할 수 있습니다. 이러한 설정에서 발생하는 데이터 전송 요금은 네트워크 트래픽이 사용한 경로에 따라 달라집니다. AWS Load Balancer Controller는 인스턴스 모드IP 모드 라는 두 가지 네트워크 트래픽 모드를 지원합니다.

+

인스턴스 모드 를 사용하면 EKS 클러스터의 각 노드에서 NodePort가 열립니다. 그러면 로드밸런서가 노드 전체에서 트래픽을 균등하게 프록시합니다. 노드에 대상 파드가 실행되고 있는 경우 데이터 전송 비용이 발생하지 않습니다. 하지만 대상 파드가 별도의 노드에 있고 트래픽을 수신하는 NodePort와 다른 AZ에 있는 경우 kube-proxy에서 대상 파드로 추가 네트워크 홉이 발생하게 된다. 이러한 시나리오에서는 AZ 간 데이터 전송 요금이 부과됩니다. 노드 전체에 트래픽이 고르게 분산되기 때문에 kube-proxy에서 관련 대상 파드로의 교차 영역 네트워크 트래픽 홉과 관련된 추가 데이터 전송 요금이 부과될 가능성이 높습니다.

+

아래 다이어그램은 로드밸런서에서 NodePort로, 이후에 kube-proxy에서 다른 AZ의 별도 노드에 있는 대상 파드로 이동하는 트래픽의 네트워크 경로를 보여줍니다. 다음은 인스턴스 모드 설정의 예시이다.

+

LB to Pod

+

IP 모드 를 사용하면 네트워크 트래픽이 로드밸런서에서 대상 파드로 직접 프록시됩니다. 따라서 이 접근 방식에는 데이터 전송 요금이 부과되지 않습니다.

+
+

Tip

+

데이터 전송 요금을 줄이려면 로드밸런서를 IP 트래픽 모드 로 설정하는 것이 좋습니다. 이 설정에서는 로드밸런서가 VPC의 모든 서브넷에 배포되었는지 확인하는 것도 중요합니다.

+
+

아래 다이어그램은 네트워크 IP 모드 에서 로드밸런서에서 파드로 이동하는 트래픽의 네트워크 경로를 보여줍니다.

+

IP mode

+

컨테이너 레지스트리에서의 데이터 전송

+

Amazon ECR

+

Amazon ECR 프라이빗 레지스트리로의 데이터 전송은 무료입니다. 지역 내 데이터 전송에는 비용이 들지 않습니다. 하지만 인터넷으로 데이터를 전송하거나 지역 간에 데이터를 전송할 때는 양쪽에 인터넷 데이터 전송 요금이 부과됩니다.

+

ECR에 내장된 이미지 복제 기능을 활용하여 관련 컨테이너 이미지를 워크로드와 동일한 지역에 복제해야 합니다. 이렇게 하면 복제 비용이 한 번만 청구되고 동일 리전 (리전 내) 이미지를 모두 무료로 가져올 수 있습니다.

+

인터페이스 VPC 엔드포인트 를 사용하여 지역 내 ECR 저장소에 연결하면 ECR에서 이미지를 가져오는 작업 (데이터 전송) 과 관련된 데이터 전송 비용을 더욱 줄일 수 있습니다. NAT 게이트웨이와 인터넷 게이트웨이를 통해 ECR의 퍼블릭 AWS 엔드포인트에 연결하는 대안적인 접근 방식은 더 높은 데이터 처리 및 전송 비용을 발생시킵니다. 다음 섹션에서는 워크로드와 AWS 서비스 간의 데이터 전송 비용 절감에 대해 더 자세히 다루겠습니다.

+

특히 큰 이미지로 워크로드를 실행하는 경우, 미리 캐시된 컨테이너 이미지로 사용자 지정 Amazon 머신 이미지 (AMI) 를 구축할 수 있습니다. 이를 통해 초기 이미지 가져오기 시간과 컨테이너 레지스트리에서 EKS 워커 노드로의 잠재적 데이터 전송 비용을 줄일 수 있습니다.

+

인터넷 및 AWS 서비스로의 데이터 전송

+

인터넷을 통해 쿠버네티스 워크로드를 다른 AWS 서비스 또는 타사 도구 및 플랫폼과 통합하는 것은 일반적인 관행입니다. 관련 목적지를 오가는 트래픽을 라우팅하는 데 사용되는 기본 네트워크 인프라는 데이터 전송 프로세스에서 발생하는 비용에 영향을 미칠 수 있습니다.

+

NAT 게이트웨이 사용

+

NAT 게이트웨이는 네트워크 주소 변환 (NAT) 을 수행하는 네트워크 구성 요소입니다. 아래 다이어그램은 다른 AWS 서비스 (Amazon ECR, DynamoDB, S3) 및 타사 플랫폼과 통신하는 EKS 클러스터의 파드를 보여줍니다. 이 예시에서는 파드가 별도의 가용영역에 있는 프라이빗 서브넷에서 실행된다. 인터넷에서 트래픽을 보내고 받기 위해 NAT 게이트웨이가 한 가용영역의 퍼블릭 서브넷에 배포되어 프라이빗 IP 주소를 가진 모든 리소스가 단일 퍼블릭 IP 주소를 공유하여 인터넷에 액세스할 수 있도록 합니다. 이 NAT 게이트웨이는 인터넷 게이트웨이 구성 요소와 통신하여 패킷을 최종 목적지로 전송할 수 있도록 합니다.

+

NAT Gateway

+

이러한 사용 사례에 NAT 게이트웨이를 사용하면 각 AZ에 NAT 게이트웨이를 배포하여 데이터 전송 비용을 최소화할 수 있습니다. 이렇게 하면 인터넷으로 라우팅되는 트래픽이 동일한 AZ의 NAT 게이트웨이를 통과하므로 AZ 간 데이터 전송이 방지됩니다.그러나 AZ 간 데이터 전송 비용을 절감할 수는 있지만 이 설정의 의미는 아키텍처에 추가 NAT 게이트웨이를 설치하는 데 드는 비용이 발생한다는 것입니다.

+

이 권장 접근 방식은 아래 다이어그램에 나와 있습니다.

+

Recommended approach

+

VPC 엔드포인트 사용

+

이러한 아키텍처에서 비용을 추가로 절감하려면 VPC 엔드포인트를 사용하여 워크로드와 AWS 서비스 간의 연결을 설정해야 합니다. VPC 엔드포인트를 사용하면 인터넷을 통과하는 데이터/네트워크 패킷 없이 VPC 내에서 AWS 서비스에 액세스할 수 있습니다. 모든 트래픽은 내부적이며 AWS 네트워크 내에 머물러 있습니다. VPC 엔드포인트에는 인터페이스 VPC 엔드포인트(많은 AWS 서비스에서 지원)와 게이트웨이 VPC 엔드포인트 (S3 및 DynamoDB에서만 지원)의 두 가지 유형이 있습니다.

+

게이트웨이 VPC 엔드포인트

+

게이트웨이 VPC 엔드포인트와 관련된 시간당 또는 데이터 전송 비용은 없습니다. 게이트웨이 VPC 엔드포인트를 사용할 때는 VPC 경계를 넘어 확장할 수 없다는 점에 유의해야 합니다. VPC 피어링, VPN 네트워킹 또는 Direct Connect를 통해서는 사용할 수 없습니다.

+

인터페이스 VPC 엔드포인트

+

VPC 엔드포인트에는 시간당 요금이 부과되며, AWS 서비스에 따라 기본 ENI를 통한 데이터 처리와 관련된 추가 요금이 부과되거나 부과되지 않을 수 있습니다. 인터페이스 VPC 엔드포인트와 관련된 가용영역 간 데이터 전송 비용을 줄이려면 각 가용영역에 VPC 엔드포인트를 만들 수 있습니다. 동일한 AWS 서비스를 가리키더라도 동일한 VPC에 여러 VPC 엔드포인트를 생성할 수 있습니다.

+

아래 다이어그램은 VPC 엔드포인트를 통해 AWS 서비스와 통신하는 파드를 보여줍니다.

+

VPC Endpoints

+

VPC 간 데이터 전송

+

경우에 따라 서로 통신해야 하는 서로 다른 VPC(동일한 AWS 지역 내)에 워크로드가 있을 수 있습니다. 이는 각 VPC에 연결된 인터넷 게이트웨이를 통해 트래픽이 퍼블릭 인터넷을 통과하도록 허용함으로써 달성할 수 있습니다. 이러한 통신은 EC2 인스턴스, NAT 게이트웨이 또는 NAT 인스턴스와 같은 인프라 구성 요소를 퍼블릭 서브넷에 배포하여 활성화할 수 있습니다. 하지만 이러한 구성 요소가 포함된 설정에서는 VPC 내/외부로 데이터를 처리/전송하는 데 비용이 발생합니다. 개별 VPC와 주고받는 트래픽이 AZ 간에 이동하는 경우 데이터 전송 시 추가 요금이 부과됩니다. 아래 다이어그램은 NAT 게이트웨이와 인터넷 게이트웨이를 사용하여 서로 다른 VPC의 워크로드 간에 통신을 설정하는 설정을 보여줍니다.

+

Between VPCs

+

VPC 피어링 연결

+

이러한 사용 사례에서 비용을 절감하려면 VPC 피어링을 사용할 수 있습니다. VPC 피어링 연결을 사용하면 동일한 AZ 내에 있는 네트워크 트래픽에 대한 데이터 전송 요금이 부과되지 않습니다. 트래픽이 AZ를 통과하는 경우 비용이 발생합니다. 하지만 동일한 AWS 지역 내의 개별 VPC에 있는 워크로드 간의 비용 효율적인 통신을 위해서는 VPC 피어링 접근 방식을 사용하는 것이 좋습니다. 하지만 VPC 피어링은 전이적 네트워킹을 허용하지 않기 때문에 주로 1:1 VPC 연결에 효과적이라는 점에 유의해야 합니다.

+

아래 다이어그램은 VPC 피어링 연결을 통한 워크로드 통신을 개괄적으로 나타낸 것입니다.

+

Peering

+

트랜지티브(Transitive) 네트워킹 연결

+

이전 섹션에서 설명한 것처럼 VPC 피어링 연결은 트랜지티브 네트워킹 연결을 허용하지 않습니다. 트랜지티브 네트워킹 요구 사항이 있는 VPC를 3개 이상 연결하려면 Transit Gateway(TGW)를 사용해야 합니다. 이를 통해 VPC 피어링의 한계 또는 여러 VPC 간의 다중 VPC 피어링 연결과 관련된 운영 오버헤드를 극복할 수 있습니다. TGW로 전송된 데이터에 대해서는 시간당 요금 이 청구됩니다. TGW를 통해 이동하는 가용영역 간 트래픽과 관련된 목적지 비용은 없습니다.

+

아래 다이어그램은 동일한 AWS 지역 내에 있는 서로 다른 VPC에 있는 워크로드 간에 TGW를 통해 이동하는 가용영역 간 트래픽을 보여줍니다.

+

Transitive

+

서비스 메시 사용

+

서비스 메시는 EKS 클러스터 환경에서 네트워크 관련 비용을 줄이는 데 사용할 수 있는 강력한 네트워킹 기능을 제공합니다. 그러나 서비스 메시를 채택할 경우 서비스 메시로 인해 환경에 발생할 수 있는 운영 작업과 복잡성을 신중하게 고려해야 합니다.

+

가용영역으로의 트래픽 제한

+

Istio의 지역성 가중 분포 사용

+

Istio를 사용하면 라우팅이 발생한 이후에 트래픽에 네트워크 정책을 적용할 수 있습니다. 이 작업은 지역 가중 분포와 같은 데스티네이션룰(Destination Rules)을 사용하여 수행됩니다. 이 기능을 사용하면 출발지를 기준으로 특정 목적지로 이동할 수 있는 트래픽의 가중치 (백분율로 표시) 를 제어할 수 있습니다. 이 트래픽의 소스는 외부 (또는 공용) 로드밸런서 또는 클러스터 자체 내의 파드에서 발생할 수 있습니다. 모든 파드 엔드포인트를 사용할 수 있게 되면 가중치 기반 라운드로빈 로드 밸런싱 알고리즘을 기반으로 지역이 선택됩니다. 특정 엔드포인트가 비정상이거나 사용할 수 없는 경우, 사용 가능한 엔드포인트에 이러한 변경 사항을 반영하도록 지역성 가중치가 자동으로 조정됩니다.

+
+

Note

+

지역성 가중 배포를 구현하기 전에 먼저 네트워크 트래픽 패턴과 대상 규칙 정책이 애플리케이션 동작에 미칠 수 있는 영향을 이해해야 합니다.따라서 AWS X-Ray 또는 Jaeger와 같은 도구를 사용하여 분산 추적(트레이싱) 메커니즘을 마련하는 것이 중요합니다.

+
+

위에서 설명한 Istio 대상 규칙을 적용하여 EKS 클러스터의 로드밸런서에서 파드로 이동하는 트래픽을 관리할 수도 있습니다. 가용성이 높은 로드밸런서 (특히 Ingress 게이트웨이) 로부터 트래픽을 수신하는 서비스에 지역성 가중치 배포 규칙을 적용할 수 있습니다. 이러한 규칙을 사용하면 영역 출처 (이 경우에는 로드밸런서) 를 기반으로 트래픽이 어디로 이동하는지 제어할 수 있습니다. 올바르게 구성하면 트래픽을 여러 가용영역의 파드 복제본에 균등하게 또는 무작위로 분배하는 로드밸런서에 비해 이그레스 교차 영역 트래픽이 덜 발생합니다.

+

다음은 Istio에 있는 데스티네이션룰 리소스의 코드 블록 예시입니다. 아래에서 볼 수 있듯이 이 리소스는 eu-west-1 지역의 서로 다른 3개 가용영역에서 들어오는 트래픽에 대한 가중치 기반 구성을 지정합니다. 이러한 구성은 특정 가용영역에서 들어오는 트래픽의 대부분 (이 경우 70%) 을 해당 트래픽이 발생한 동일한 가용영역의 대상으로 프록시해야 한다고 선언합니다.

+
apiVersion: networking.istio.io/v1beta1
+kind: DestinationRule
+metadata:
+  name: express-test-dr
+spec:
+  host: express-test.default.svc.cluster.local
+  trafficPolicy:
+    loadBalancer:                        
+      localityLbSetting:
+        distribute:
+        - from: eu-west-1/eu-west-1a/    
+          to:
+            "eu-west-1/eu-west-1a/*": 70 
+            "eu-west-1/eu-west-1b/*": 20
+            "eu-west-1/eu-west-1c/*": 10
+        - from: eu-west-1/eu-west-1b/*    
+          to:
+            "eu-west-1/eu-west-1a/*": 20 
+            "eu-west-1/eu-west-1b/*": 70
+            "eu-west-1/eu-west-1c/*": 10
+        - from: eu-west-1/eu-west-1c/*    
+          to:
+            "eu-west-1/eu-west-1a/*": 20 
+            "eu-west-1/eu-west-1b/*": 10
+            "eu-west-1/eu-west-1c/*": 70**
+    connectionPool:
+      http:
+        http2MaxRequests: 10
+        maxRequestsPerConnection: 10
+    outlierDetection:
+      consecutiveGatewayErrors: 1
+      interval: 1m
+      baseEjectionTime: 30s
+
+
+

Note

+

목적지에 분배할 수 있는 최소 가중치는 1% 입니다. 그 이유는 주 대상의 엔드포인트가 정상이 아니거나 사용할 수 없게 되는 경우에 대비하여 장애 조치 리전 및 가용영역을 유지하기 위함입니다.

+
+

아래 다이어그램은 eu-west-1 지역에 고가용성 로드 밸런서가 있고 지역 가중 분배가 적용되는 시나리오를 보여줍니다. 이 다이어그램의 대상 규칙 정책은 eu-west-1a 에서 들어오는 트래픽의 60% 를 동일한 AZ의 파드로 전송하도록 구성되어 있는 반면, eu-west-1a 의 트래픽 중 40% 는 eu-west-1b의 파드로 이동해야 한다.

+

Istio Traffic Control

+

가용 영역 및 노드로의 트래픽 제한

+

Istio에서 서비스 내부 트래픽 정책 사용

+

파드 간의 외부 수신 트래픽 및 내부 트래픽과 관련된 네트워크 비용을 줄이기 위해 Istio의 대상 규칙과 쿠버네티스 서비스의 내부 트래픽 정책 을 결합할 수 있습니다. Istio 목적지 규칙을 서비스 내부 트래픽 정책과 결합하는 방법은 크게 다음 세 가지 요소에 따라 달라집니다.

+
    +
  • 마이크로서비스의 역할
  • +
  • 마이크로서비스 전반의 네트워크 트래픽 패턴
  • +
  • 쿠버네티스 클러스터 토폴로지 전반에 마이크로서비스를 배포하는 방법
  • +
+

아래 다이어그램은 중첩된 요청의 경우 네트워크 흐름이 어떻게 표시되는지와 앞서 언급한 정책이 트래픽을 제어하는 방식을 보여줍니다.

+

External and Internal traffic policy

+
    +
  1. 최종 사용자가 앱 A에 요청을 보내고, 이 요청은 다시 앱 C에 중첩된 요청을 보냅니다. 이 요청은 먼저 가용성이 뛰어난 로드 밸런서로 전송됩니다. 이 로드 밸런서는 위 다이어그램에서 볼 수 있듯이 AZ 1과 AZ 2에 인스턴스가 있습니다.
  2. +
  3. 그런 다음 외부 수신 요청은 Istio 가상 서비스에 의해 올바른 대상으로 라우팅됩니다.
  4. +
  5. 요청이 라우팅된 후 Istio 대상 규칙은 트래픽이 시작된 위치 (AZ 1 또는 AZ 2) 를 기반으로 각 AZ로 이동하는 트래픽 양을 제어합니다.
  6. +
  7. 그런 다음 트래픽은앱 A용 서비스로 이동한 다음 각 Pod 엔드포인트로 프록시됩니다. 다이어그램에서 볼 수 있듯이 수신 트래픽의 80% 는 AZ 1의 파드 엔드포인트로 전송되고, 수신 트래픽의 20% 는 AZ 2로 전송됩니다.
  8. +
  9. 그런 다음 앱 A가 내부적으로 앱 C에 요청을 보냅니다. 앱 C의 서비스에는 내부 트래픽 정책이 활성화되어 있습니다(내부 트래픽 정책``: 로컬).
  10. +
  11. 앱 C에 사용할 수 있는 노드-로컬 엔드포인트가 있기 때문에 앱 A (노드 1)에서 앱 C로의 내부 요청이 성공했습니다.
  12. +
  13. 앱 C에 사용할 수 있는 노드-로컬 엔드포인트 가 없기 때문에 앱 A (노드 3)에서 앱 C에 대한 내부 요청이 실패합니다. 다이어그램에서 볼 수 있듯이 앱 C의 노드 3에는 복제본이 없습니다.***
  14. +
+

아래 스크린샷은 이 접근법의 실제 예에서 캡처한 것입니다. 첫 번째 스크린샷 세트는 'graphql'에 대한 성공적인 외부 요청과 'graphql'에서 노드 ip-10-0-151.af-south-1.compute.internal 노드에 같은 위치에 있는 orders 복제본으로의 성공적인 중첩 요청을 보여줍니다.

+

Before +Before results

+

Istio를 사용하면 프록시가 인식하는 모든 업스트림 클러스터 및 엔드포인트의 통계를 확인하고 내보낼 수 있습니다. 이를 통해 네트워크 흐름과 워크로드 서비스 간의 분산 점유율을 파악할 수 있습니다. 같은 예제를 계속하면, 다음 명령을 사용하여 graphql 프록시가 인식하는 orders 엔드포인트를 가져올 수 있습니다.

+
kubectl exec -it deploy/graphql -n ecommerce -c istio-proxy -- curl localhost:15000/clusters | grep orders 
+
+
...
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_error::0**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_success::119**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_timeout::0**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_total::119**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**health_flags::healthy**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**region::af-south-1**
+orders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**zone::af-south-1b**
+...
+
+

이 경우, graphql 프록시는 노드를 공유하는 복제본의 orders 엔드포인트만 인식합니다. 주문 서비스에서 InternalTrafficPolicy: Local 설정을 제거하고 위와 같은 명령을 다시 실행하면 결과는 서로 다른 노드에 분산된 복제본의 모든 엔드포인트를 반환합니다. 또한 각 엔드포인트의 rq_total을 살펴보면 네트워크 분배에서 비교적 균일한 점유율을 확인할 수 있습니다. 따라서 엔드포인트가 서로 다른 가용영역에서 실행되는 업스트림 서비스와 연결된 경우 여러 영역에 네트워크를 분산하면 비용이 더 많이 듭니다.

+

위의 이전 섹션에서 언급한 바와 같이, 파드 어피니티를 활용하여 자주 통신하는 파드를 같은 위치에 배치할 수 있다.

+
...
+spec:
+...
+  template:
+    metadata:
+      labels:
+        app: graphql
+        role: api
+        workload: ecommerce
+    spec:
+      affinity:
+        podAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+          - labelSelector:
+              matchExpressions:
+              - key: app
+                operator: In
+                values:
+                - orders
+            topologyKey: "kubernetes.io/hostname"
+      nodeSelector:
+        managedBy: karpenter
+        billing-team: ecommerce
+...
+
+

graphqlorders 복제본이 동일한 노드에 공존하지 않는 경우 (ip-10-0-0-151.af-south-1.compute.internal), 아래 포스트맨 스크린샷의 200 응답 코드에서 알 수 있듯이 graphql에 대한 첫 번째 요청은 성공하지만, graphql에서 orders로의 두 번째 중첩 요청은 503 응답 코드로 실패합니다.

+

After +After results

+

추가 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cost_optimization/cost_opt_observability/index.html b/ko/cost_optimization/cost_opt_observability/index.html new file mode 100644 index 000000000..177445c00 --- /dev/null +++ b/ko/cost_optimization/cost_opt_observability/index.html @@ -0,0 +1,2820 @@ + + + + + + + + + + + + + + + + + + + + + + + 옵저버빌리티 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

비용 최적화 - 옵저버빌리티

+

소개

+

옵저버빌리티 도구를 사용하면 워크로드를 효율적으로 감지, 수정 및 조사할 수 있습니다. EKS 사용이 늘어남에 따라 원격 측정 데이터 비용은 자연스럽게 증가합니다. 때로는 운영 요구 사항의 균형을 맞추고 비즈니스에 중요한 요소를 측정하고 옵저버빌리티 비용을 관리하는 것이 어려울 수 있습니다. 이 가이드에서는 옵저버빌리티의 세 가지 요소인 로그, 지표 및 트레이스에 대한 비용 최적화 전략에 중점을 둡니다. 이러한 각 모범 사례는 조직의 최적화 목표에 맞게 독립적으로 적용할 수 있습니다.

+

로깅

+

로깅은 클러스터의 애플리케이션을 모니터링하고 문제를 해결하는 데 중요한 역할을 합니다. 로깅 비용을 최적화하기 위해 사용할 수 있는 몇 가지 전략이 있습니다. 아래 나열된 모범 사례 전략에는 로그 보존 정책을 검토하여 로그 데이터 보관 기간에 대한 세분화된 제어를 구현하고, 중요도에 따라 로그 데이터를 다양한 스토리지 옵션으로 전송하고, 로그 필터링을 활용하여 저장되는 로그 메시지 유형의 범위를 좁히는 것이 포함됩니다. 로그 원격 분석을 효율적으로 관리하면 환경 비용을 절감할 수 있습니다.

+

EKS 컨트롤 플레인

+

컨트롤 플레인 로그 최적화

+

쿠버네티스 컨트롤 플레인은 클러스터를 관리하는 구성 요소 집합이며, 이러한 구성 요소는 다양한 유형의 정보를 로그 스트림으로 Amazon CloudWatch의 로그 그룹에 보냅니다. 모든 컨트롤 플레인 로그 유형을 활성화하면 이점이 있지만, 각 로그의 정보와 모든 로그 원격 분석을 저장하는 데 드는 관련 비용을 알고 있어야 합니다. 클러스터에서 Amazon CloudWatch 로그로 전송된 표준 CloudWatch Logs 데이터 수집 및 로그 스토리지 비용에 대한 요금이 부과됩니다. 활성화하기 전에 각 로그 스트림이 필요한지 평가하십시오.

+

예를 들어 비프로덕션 클러스터에서는 API 서버 로그와 같은 특정 로그 유형을 분석용으로만 선택적으로 활성화하고 이후에는 비활성화합니다. 하지만 이벤트를 재현할 수 없고 문제를 해결하려면 추가 로그 정보가 필요한 프로덕션 클러스터의 경우 모든 로그 유형을 활성화할 수 있습니다. 컨트롤 플레인 비용 최적화 구현에 대한 자세한 내용은 이 블로그 게시물에 있습니다.

+

S3로 로그 스트리밍

+

또 다른 비용 최적화 모범 사례는 CloudWatch Logs 구독을 통해 컨트롤 플레인 로그를 S3로 스트리밍하는 것입니다. CloudWatch 로그 구독을 활용하면 로그를 선택적으로 S3로 전달할 수 있어 CloudWatch에 로그를 무기한으로 보관하는 것보다 비용 효율적인 장기 스토리지를 제공합니다. 예를 들어 프로덕션 클러스터의 경우 중요 로그 그룹을 생성하고 구독을 활용하여 15일 후에 이러한 로그를 S3로 스트리밍할 수 있습니다. 이렇게 하면 분석을 위해 로그에 빠르게 액세스할 수 있을 뿐만 아니라 로그를 보다 비용 효율적인 스토리지로 이동하여 비용을 절약할 수 있습니다.

+
+

Attention

+

2023년 9월 5일부터 EKS 로그는 아마존 클라우드워치 로그에서 Vended 로그로 분류됩니다.Vended 로그는 고객을 대신하여 AWS 서비스에서 기본적으로 게시하는 특정 AWS 서비스 로그로, 대량 구매 할인 요금으로 제공됩니다. Vended 로그 요금에 대해 자세히 알아보려면 Amazon CloudWatch 요금 페이지를 방문하십시오.

+
+

EKS 데이터 플레인

+

로그 보존

+

Amazon CloudWatch의 기본 보존 정책은 로그를 무기한으로 보관하고 만료되지 않도록 하는 것이므로 AWS 지역에 해당하는 스토리지 비용이 발생합니다. 스토리지 비용을 줄이려면 워크로드 요구 사항에 따라 각 로그 그룹의 보존 정책을 사용자 지정할 수 있습니다.

+

개발 환경에서는 긴 보존 기간이 필요하지 않을 수 있습니다. 하지만 프로덕션 환경에서는 문제 해결, 규정 준수 및 용량 계획 요구 사항을 충족하기 위해 더 긴 보존 정책을 설정할 수 있습니다. 예를 들어, 연휴 성수기에 전자 상거래 애플리케이션을 실행하는 경우 시스템의 부하가 가중되어 즉시 눈에 띄지 않을 수 있는 문제가 발생할 수 있습니다. 자세한 문제 해결 및 사후 이벤트 분석을 위해 로그 보존 기간을 더 길게 설정하는 것이 좋습니다.

+

AWS CloudWatch 콘솔에서 보존 기간 구성 또는 AWS API에서 각 로그 그룹을 기준으로 1일에서 10년까지 보존 기간을 설정할 수 있습니다. 보존 기간을 유연하게 설정하면 중요한 로그를 관리하는 동시에 로그 스토리지 비용을 절감할 수 있습니다.

+

로그 스토리지 옵션

+

스토리지는 옵저버빌리티 비용의 큰 요인이므로 로그 스토리지 전략을 최적화하는 것이 중요합니다. 전략은 성능과 확장성을 유지하면서 워크로드 요구 사항에 맞게 조정해야 합니다. 로그 저장 비용을 줄이기 위한 한 가지 전략은 AWS S3 버킷과 다양한 스토리지 티어를 활용하는 것입니다.

+

로그를 S3로 직접 전달

+

개발 환경과 같이 덜 중요한 로그를 Cloudwatch 대신 S3에 직접 전달하는 것을 고려해 보세요. 이는 로그 스토리지 비용에 즉각적인 영향을 미칠 수 있습니다. 한 가지 옵션은 FluentBit을 사용하여 로그를 S3로 바로 전달하는 것입니다. FluentBit이 보존을 위해 컨테이너 로그를 전송하는 목적지인 [OUTPUT] 섹션에서 이를 정의합니다. 추가 구성 파라미터 여기를 검토하십시오.

+
[OUTPUT]
+        Name eks_to_s3
+        Match application.* 
+        bucket $S3_BUCKET name
+        region us-east-2
+        store_dir /var/log/fluentbit
+        total_file_size 30M
+        upload_timeout 3m
+
+

단기 분석을 위해서만 CloudWatch로 로그를 전달합니다.

+

데이터에 대한 즉각적인 분석을 수행해야 하는 프로덕션 환경과 같이 더 중요한 로그의 경우 로그를 CloudWatch로 전달하는 것을 고려해 보십시오. FluentBit이 보존을 위해 컨테이너 로그를 전송하는 목적지인 [OUTPUT] 섹션에서 이를 정의합니다. 추가 구성 파라미터 여기를 검토하십시오.

+
[OUTPUT]
+        Name eks_to_cloudwatch_logs
+        Match application.*
+        region us-east-2
+        log_group_name fluent-bit-cloudwatch
+        log_stream_prefix from-fluent-bit-
+        auto_create_group On
+
+

그러나 이것이 비용 절감에 즉각적인 영향을 미치지는 않습니다. 추가 비용 절감을 위해 이러한 로그를 Amazon S3로 내보내야 합니다.

+

CloudWatch에서 Amazon S3로 내보내기

+

Amazon CloudWatch 로그를 장기간 저장하려면 Amazon EKS CloudWatch 로그를 Amazon Simple Storage(S3) 서비스로 내보내는 것이 좋습니다.콘솔 또는 API를 통해 내보내기 작업을 생성하여 Amazon S3 버킷으로 로그를 전달할 수 있습니다. 이렇게 하면 Amazon S3는 비용을 추가로 절감할 수 있는 다양한 옵션을 제공합니다. 자체 Amazon S3 수명 주기 규칙을 정의하여 필요에 맞는 스토리지 클래스로 로그를 이동하거나, Amazon S3 Intelligent-Tiering 스토리지 클래스를 활용하여 AWS가 사용 패턴에 따라 데이터를 장기 스토리지로 자동 이동하도록 할 수 있습니다. 자세한 내용은 이 블로그를 참조하십시오. 예를 들어 프로덕션 환경의 경우 로그가 30일 이상 CloudWatch에 보관된 후 Amazon S3 버킷으로 내보내집니다. 그런 다음 나중에 로그를 다시 참조해야 하는 경우 Amazon Athena를 사용하여 Amazon S3 버킷의 데이터를 쿼리할 수 있습니다.

+

로그 레벨 조정

+

애플리케이션 작성 시 로깅 레벨을 조정하여 선별적으로 로깅을 남깁니다. 기본적으로 애플리케이션과 노드 모두 로그를 출력합니다. 애플리케이션 로그의 경우 워크로드 및 환경의 중요도에 맞게 로그 수준을 조정하십시오. 예를 들어, 아래의 Java 애플리케이션은 일반적인 기본 애플리케이션 구성인 INFO 로그를 출력하므로 코드에 따라 로그 데이터의 양이 많아질 수 있습니다.

+
import org.apache.log4j.*;
+
+public class LogClass {
+   private static org.apache.log4j.Logger log = Logger.getLogger(LogClass.class);
+
+   public static void main(String[] args) {
+      log.setLevel(Level.INFO);
+
+      log.debug("This is a DEBUG message, check this out!");
+      log.info("This is an INFO message, nothing to see here!");
+      log.warn("This is a WARN message, investigate this!");
+      log.error("This is an ERROR message, check this out!");
+      log.fatal("This is a FATAL message, investigate this!");
+   }
+}
+
+

개발 환경에서는 로그 수준을 'DEBUG'로 변경하세요. 이렇게 하면 문제를 디버깅하거나 잠재적인 문제를 프로덕션에 적용하기 전에 찾아내는 데 도움이 될 수 있습니다.

+
      log.setLevel(Level.DEBUG);
+
+

프로덕션 환경에서는 로그 수준을 ERROR 또는 FATAL로 수정하는 것을 고려해 보십시오. 이렇게 하면 애플리케이션에 오류가 있는 경우에만 로그가 출력되므로 로그 출력이 줄어들고 애플리케이션 상태와 관련된 중요한 데이터에 집중할 수 있습니다.

+
      log.setLevel(Level.ERROR);
+
+

다양한 쿠버네티스 구성 요소의 로그 수준을 미세 조정할 수 있습니다.예를 들어, EKS 노드 운영 체제로 Bottlerocket 을 사용하는 경우, kubelet 프로세스 로그 수준을 조정할 수 있는 구성 설정이 있습니다.이 구성 설정의 스니펫은 다음과 같다. 'kubelet' 프로세스의 로깅 상세도를 조정하는 2의 기본 로그 레벨을 참고하세요.

+
[settings.kubernetes]
+log-level = "2"
+image-gc-high-threshold-percent = "85"
+image-gc-low-threshold-percent = "80"
+
+

개발 환경의 경우 추가 이벤트를 보려면 로그 수준을2 이상으로 설정할 수 있습니다. 이는 디버깅에 유용합니다. 프로덕션 환경의 경우 중요 이벤트만 보려면 레벨을 0로 설정할 수 있습니다.

+

필터 활용하기

+

기본 EKS FluentBit 구성을 사용하여 컨테이너 로그를 Cloudwatch로 보내는 경우, FluentBit은 아래의 [INPUT] 구성 블록에 표시된 대로 쿠버네티스 메타데이터가 보강된ALL 애플리케이션 컨테이너 로그를 캡처하여 CloudWatch에 전송합니다.

+
    [INPUT]
+        Name                tail
+        Tag                 application.*
+        Exclude_Path        /var/log/containers/cloudwatch-agent*, /var/log/containers/fluent-bit*, /var/log/containers/aws-node*, /var/log/containers/kube-proxy*
+        Path                /var/log/containers/*.log
+        Docker_Mode         On
+        Docker_Mode_Flush   5
+        Docker_Mode_Parser  container_firstline
+        Parser              docker
+        DB                  /var/fluent-bit/state/flb_container.db
+        Mem_Buf_Limit       50MB
+        Skip_Long_Lines     On
+        Refresh_Interval    10
+        Rotate_Wait         30
+        storage.type        filesystem
+        Read_from_Head      ${READ_FROM_HEAD}
+
+

위의 [INPUT] 섹션은 모든 컨테이너 로그를 수집하고 있습니다. 이로 인해 필요하지 않을 수도 있는 대량의 데이터가 생성될 수 있습니다. 이 데이터를 필터링하면 CloudWatch로 전송되는 로그 데이터의 양이 줄어들어 비용을 절감할 수 있습니다. 로그가 CloudWatch로 출력되기 전에 로그에 필터를 적용할 수 있습니다. Fluentbit은 [필터] 섹션에서 이를 정의합니다. 예를 들어 Kubernetes 메타데이터가 로그 이벤트에 추가되지 않도록 필터링하면 로그 볼륨을 줄일 수 있습니다.

+
    [FILTER]
+        Name                nest
+        Match               application.*
+        Operation           lift
+        Nested_under        kubernetes
+        Add_prefix          Kube.
+
+    [FILTER]
+        Name                modify
+        Match               application.*
+        Remove              Kube.<Metadata_1>
+        Remove              Kube.<Metadata_2>
+        Remove              Kube.<Metadata_3>
+
+    [FILTER]
+        Name                nest
+        Match               application.*
+        Operation           nest
+        Wildcard            Kube.*
+        Nested_under        kubernetes
+        Remove_prefix       Kube.
+
+

메트릭 지표

+

메트릭 지표는 시스템 성능과 관련된 중요한 정보를 제공합니다. 시스템 관련 또는 사용 가능한 모든 리소스 메트릭을 중앙 집중식으로 통합하면 성능 데이터를 비교하고 분석할 수 있습니다. 이러한 중앙 집중식 접근 방식을 사용하면 리소스를 확장하거나 축소하는 등 정보에 입각한 전략적 결정을 내릴 수 있습니다. 또한 메트릭 지표는 리소스 상태를 평가하는 데 중요한 역할을 하므로 필요한 경우 사전 조치를 취할 수 있습니다.일반적으로 옵저버빌리티 비용은 원격 측정 데이터 수집 및 보존에 따라 조정됩니다. 다음은 메트릭 원격 측정 비용을 줄이기 위해 구현할 수 있는 몇 가지 전략입니다. 중요한 메트릭 지표만 수집하고, 원격 측정 데이터의 카디널리티를 줄이고, 원격 측정 데이터 수집의 세부 수준을 조정하는 것입니다.

+

중요한 사항을 모니터링하고 필요한 항목만 수집하세요

+

첫 번째 비용 절감 전략은 수집하는 지표의 수를 줄이고 결과적으로 유지 비용을 줄이는 것입니다.

+
    +
  1. 먼저 이해관계자의 요구 사항을 거꾸로 검토하여 가장 중요한 지표를 결정하세요. 성공 지표는 사람마다 다릅니다! 좋은이 어떤 모습인지 알고 측정하세요.
  2. +
  3. 지원 중인 워크로드를 자세히 살펴보고 '골든 시그널'이라고도 하는 핵심 성과 지표(KPI) 를 파악해 보세요. 이는 비즈니스 및 이해관계자의 요구 사항에 맞게 조정되어야 합니다. 서비스 안정성을 관리하려면 Amazon CloudWatch와 Metric Math를 사용하여 SLI, SLO 및 SLA를 계산하는 것이 중요합니다. 이 가이드에 설명된 모범 사례를 따라 EKS 환경의 성능을 효과적으로 모니터링하고 유지 관리하십시오.
  4. +
  5. 그런 다음 여러 인프라 계층을 계속 검토하여 워크로드 KPI와 연결 및 상호 연관 EKS 클러스터, 노드 및 추가 인프라 메트릭을 수행하십시오. 비즈니스 지표와 운영 지표를 서로 연관시키고 관찰된 영향을 기반으로 결론을 도출할 수 있는 시스템에 저장하세요. +4.EKS는 컨트롤 플레인, 클러스터 kube-state-metrics, 파드 및 노드의 메트릭을 노출합니다. 이러한 모든 지표의 관련성은 요구 사항에 따라 다르지만, 여러 계층에 걸쳐 모든 단일 지표가 필요하지는 않을 수도 있습니다. 이 EKS 필수 지표 가이드를 EKS 클러스터 및 워크로드의 전반적인 상태를 모니터링하기 위한 기준으로 사용할 수 있습니다.
  6. +
+

다음은 프로메테우스 스크랩 구성의 예시로, kubelet 메트릭만 유지하기 위해 'relabel_config'를 사용하고 모든 컨테이너 메트릭을 삭제하기 위해 'metric_relabel_config'를 사용합니다.

+
  kubernetes_sd_configs:
+  - role: endpoints
+    namespaces:
+      names:
+      - kube-system
+  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+  tls_config:
+    insecure_skip_verify: true
+  relabel_configs:
+  - source_labels: [__meta_kubernetes_service_label_k8s_app]
+    regex: kubelet
+    action: keep
+
+  metric_relabel_configs:
+  - source_labels: [__name__]
+    regex: container_(network_tcp_usage_total|network_udp_usage_total|tasks_state|cpu_load_average_10s)
+    action: drop
+
+

해당하는 경우 카디널리티 줄이기

+

카디널리티란 특정 지표 집합에 대한 데이터 값 (예: 프로메테우스 레이블) 과 데이터 값의 고유성을 나타냅니다. 카디널리티가 높은 지표에는 여러 차원이 있으며 각 차원 지표 조합은 고유성이 더 높습니다. 카디널리티가 높을수록 메트릭 원격 측정 데이터 크기 및 스토리지 요구 사항이 커져 비용이 증가합니다.

+

아래와 같이 카디널리티가 높은 예제에서는 메트릭 지표인 Latency는 RequestID, CustomerID 및 Service와 같은 여러 차원(Dimension)을 가지고 있고, 각 차원에는 고유한 값이 많이 존재하는 것을 볼 수 있습니다. 카디널리티는 차원당 가능한 값 수의 조합을 측정한 값입니다. Prometheus에서는 각각의 고유한 차원/레이블 세트를 새로운 지표로 간주하므로 카디널리티가 높으면 지표가 더 많아집니다.

+

high cardinality

+

메트릭 지표(클러스터, 네임스페이스, 서비스, 파드, 컨테이너 등)가 많고 지표 당 차원/레이블이 많은 EKS 환경에서는 카디널리티가 커지는 경향이 있습니다. 비용을 최적화하려면 수집하는 지표의 카디널리티를 신중하게 고려해야 합니다. 예를 들어 클러스터 수준에서 시각화를 위해 특정 지표를 집계하는 경우 네임스페이스 레이블과 같이 하위 계층에 있는 추가 레이블을 삭제할 수 있습니다.

+

prometheus에서 카디널리티가 높은 메트릭을 식별하려면 다음 PROMQL 쿼리를 실행하여 메트릭 수가 가장 많은 스크랩 대상(카디널리티) 을 확인할 수 있습니다.

+
topk_max(5, max_over_time(scrape_samples_scraped[1h]))
+
+

다음 PROMQL 쿼리는 지표 이탈률이 가장 높은 스크랩 대상(특정 스크랩에서 생성된 새 지표 시리즈 수)을 결정하는 데 도움이 될 수 있습니다.

+
topk_max(5, max_over_time(scrape_series_added[1h]))
+
+

grafana를 사용하는 경우 Grafana Lab의 Mimirtool을 사용하여 그라파나 대시보드와 프로메테우스 규칙을 분석하여 사용하지 않는 하이 카디널리티 메트릭을 식별할 수 있습니다. 이 가이드를 참조하여 mimirtool analyzemimirtool analyze prometheus 명령을 사용하여 대시보드에서 참조되지 않는 활성 메트릭을 식별하는 방법에 대해 설명합니다.

+

메트릭 주기 고려

+

매 분보다 매 초와 같이 더 높은 단위로 지표를 수집하면 원격 분석을 수집하고 저장하는 양에 큰 영향을 주어 비용이 증가할 수 있습니다. 일시적인 문제를 확인할 수 있을 만큼 충분히 세분화된 수준과 비용 효율적일 만큼 충분히 낮은 수준 사이의 균형을 맞추는 합리적인 스크랩 또는 지표 수집 간격을 결정하세요. 용량 계획 및 더 긴 기간 분석에 사용되는 지표의 세분성을 줄이십시오.

+

아래는 기본 AWS Distro for Opentelemetry(ADOT) EKS 애드온 컬렉터 구성의 스니펫입니다.

+
+

Attention

+

글로벌 프로메테우스 수집 간격은 15초로 설정되어 있습니다. 이 수집 간격을 늘리면 프로메테우스에서 수집되는 메트릭 데이터의 양이 줄어들 수 있습니다.

+
+
apiVersion: opentelemetry.io/v1alpha1
+kind: OpenTelemetryCollector
+metadata:
+  name: my-collector-amp
+
+...
+
+  config: |
+    extensions:
+      sigv4auth:
+        region: "<YOUR_AWS_REGION>"
+        service: "aps"
+
+    receivers:
+      #
+      # Scrape configuration for the Prometheus Receiver
+      # This is the same configuration used when Prometheus is installed using the community Helm chart
+      # 
+      prometheus:
+        config:
+          global:
+  scrape_interval: 15s
+            scrape_timeout: 10s
+
+

추적(트레이싱)

+

추적(트레이싱)과 관련된 주요 비용은 추적(트레이싱) 저장소 생성에서 비롯됩니다. 추적(트레이싱)의 목표는 성능 측면을 진단하고 이해하는 데 필요한 충분한 데이터를 수집하는 것입니다. 하지만 X-Ray 추적(트레이싱) 비용은 X-Ray로 전송된 데이터를 기반으로 하므로 전송된 흔적을 지워도 비용이 절감되지는 않습니다. 적절한 분석을 수행할 수 있도록 데이터를 유지하면서 추적(트레이싱) 비용을 절감할 수 있는 방법을 검토해 보겠습니다.

+

샘플링 규칙 적용

+

X-Ray 샘플링 속도는 기본적으로 보수적입니다. 수집하는 데이터의 양을 제어할 수 있는 샘플링 규칙을 정의하십시오. 이렇게 하면 비용을 절감하는 동시에 성능 효율성이 향상됩니다. 샘플링 비율을 낮추면 더 낮은 비용 구조를 유지하면서 워크로드에 필요한 것만 요청에서 트레이스를 수집할 수 있습니다.

+

예를 들어, 하나의 문제가 있는 경로에 대한 모든 요청의 추적(트레이싱)을 디버깅하려는 Java 애플리케이션이 있습니다.

+

Configure via the SDK to load sampling rules from a JSON document

+
{
+  "version": 2,
+  "rules": [
+    {
+      "description": "debug-eks",
+      "host": "*",
+      "http_method": "PUT",
+      "url_path": "/history/*",
+      "fixed_target": 0,
+      "rate": 1,
+      "service_type": "debug-eks"
+    }
+  ],
+  "default": {
+    "fixed_target": 1,
+    "rate": 0.1
+  }
+}
+
+

콘솔을 통해

+

console

+

AWS Distro for OpenTelemetry (ADOT)으로 테일 샘플링 적용 (ADOT)

+

ADOT 테일 샘플링을 사용하면 서비스에서 수집되는 트레이스의 양을 제어할 수 있습니다. 하지만 테일 샘플링을 사용하면 요청의 모든 범위가 처음에 완료되는 대신 완료된 후에 샘플링 정책을 정의할 수 있습니다. 이로 인해 CloudWatch로 전송되는 원시 데이터의 양이 더욱 제한되어 비용이 절감됩니다.

+

예를 들어 랜딩 페이지 트래픽의 1% 와 결제 페이지 요청의 10% 를 샘플링하는 경우 30분 동안 300개의 트레이스가 남을 수 있습니다. 특정 오류를 필터링하는 ADOT 테일 샘플링 규칙을 사용하면 트레이스가 200개 남게 되어 저장되는 트레이스 수가 줄어들 수 있습니다.

+
processors:
+  groupbytrace:
+    wait_duration: 10s
+    num_traces: 300 
+    tail_sampling:
+    decision_wait: 1s # This value should be smaller than wait_duration
+    policies:
+      - ..... # Applicable policies**
+  batch/tracesampling:
+    timeout: 0s # No need to wait more since this will happen in previous processors
+    send_batch_max_size: 8196 # This will still allow us to limit the size of the batches sent to subsequent exporters
+
+service:
+  pipelines:
+    traces/tailsampling:
+      receivers: [otlp]
+      processors: [groupbytrace, tail_sampling, batch/tracesampling]
+      exporters: [awsxray]
+
+

Amazon S3 스토리지 옵션 활용

+

트레이스를 저장하려면 AWS S3 버킷과 다양한 스토리지 클래스를 활용해야 합니다.보존 기간이 만료되기 전에 트레이스를 S3로 익스포트하십시오.Amazon S3 수명 주기 규칙을 사용하여 트레이스 데이터를 요구 사항에 맞는 스토리지 클래스로 이동합니다.

+

예를 들어 90일이 지난 트레이스가 있는 경우 Amazon S3 인텔리전트 티어링 은 사용 패턴에 따라 데이터를 장기 스토리지로 자동으로 이동할 수 있습니다. 트레이스를 나중에 다시 참조해야 하는 경우 Amazon Athena를 사용하여 Amazon S3에 있는 데이터를 쿼리할 수 있습니다. 이를 통해 분산 트레이스 비용을 더욱 절감할 수 있습니다.

+

추가 리소스:

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cost_optimization/cost_opt_storage/index.html b/ko/cost_optimization/cost_opt_storage/index.html new file mode 100644 index 000000000..912f59c5d --- /dev/null +++ b/ko/cost_optimization/cost_opt_storage/index.html @@ -0,0 +1,2597 @@ + + + + + + + + + + + + + + + + + + + + + + + 스토리지 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

비용 최적화 - 스토리지

+

개요

+

데이터를 단기 또는 장기적으로 보존해야 하는 애플리케이션을 실행해야 하는 시나리오가 있을 수 있습니다. 이러한 사용 사례의 경우, 컨테이너가 다양한 스토리지 메커니즘을 활용할 수 있도록 파드에서 볼륨을 정의하고 마운트할 수 있습니다. 쿠버네티스는 임시 및 영구 스토리지를 위해 다양한 유형의 볼륨을 지원합니다. 스토리지 선택은 주로 애플리케이션 요구 사항에 따라 달라집니다. 각 접근 방식마다 비용에 미치는 영향이 있으며, 아래에 자세히 설명된 사례는 EKS 환경에서 특정 형태의 스토리지가 필요한 워크로드의 비용 효율성을 달성하는 데 도움이 됩니다.

+

임시(Ephemeral) 볼륨

+

임시 볼륨은 일시적인 로컬 볼륨이 필요하지만 재시작 후에도 데이터를 유지할 필요가 없는 애플리케이션에 적합합니다. 이러한 예로는 스크래치 공간, 캐싱, 읽기 전용 입력 데이터(예: 구성 데이터 및 암호)에 대한 요구 사항이 포함됩니다. 쿠버네티스 임시 볼륨에 대한 자세한 내용은 여기에서 확인할 수 있다. 대부분의 임시 볼륨 (예: emptyDir, ConfigMap, DownwardAPI, secret, hostpath)은 로컬로 연결된 쓰기 가능 디바이스 (일반적으로 루트 디스크) 또는 RAM으로 백업되므로 가장 비용 효율적이고 성능이 뛰어난 호스트 볼륨을 선택하는 것이 중요합니다.

+

EBS 볼륨 사용

+

호스트 루트 볼륨은 gp3로 시작하는 것이 좋습니다. Amazon EBS에서 제공하는 최신 범용 SSD 볼륨이며 gp2 볼륨에 비해 GB당 가격(최대 20%)도 저렴합니다.

+

Amazon EC2 인스턴스 스토어(Instance Stores) 사용

+

Amazon EC2 인스턴스 스토어는 EC2 인스턴스를 위한 임시 블록 레벨 스토리지를 제공합니다. EC2 인스턴스 스토어가 제공하는 스토리지는 호스트에 물리적으로 연결된 디스크를 통해 액세스할 수 있습니다. Amazon EBS와 달리 인스턴스 스토어 볼륨은 인스턴스가 시작될 때만 연결할 수 있으며, 이러한 볼륨은 인스턴스의 수명 기간 동안에만 존재합니다. 분리한 후 다른 인스턴스에 다시 연결할 수는 없습니다.Amazon EC2 인스턴스 스토어에 대한 자세한 내용은 여기에서 확인할 수 있습니다. 인스턴스 스토어 볼륨과 관련된 추가 요금은 없습니다. 따라서 인스턴스 스토어 볼륨은 EBS 볼륨이 큰 일반 EC2 인스턴스보다 _비용 효율적_이 뛰어납니다.

+

쿠버네티스에서 로컬 스토어 볼륨을 사용하려면 Amazon EC2 사용자 데이터를 사용하여 디스크를 파티셔닝, 구성 및 포맷해야 합니다. 그래야 볼륨이 파드 사양에서 HostPath로 마운트될 수 있습니다. 또는 로컬 퍼시스턴트 볼륨 정적 프로비저너(Local Persistent Volume Static Provisioner)를 활용하여 로컬 스토리지 관리를 간소화할 수 있습니다. 로컬 퍼시스턴트 볼륨 정적 프로비저너를 사용하면 표준 쿠버네티스 퍼시스턴트 볼륨 클레임(PVC) 인터페이스를 통해 로컬 인스턴스 스토어 볼륨에 액세스할 수 있습니다. 또한 노드 어피니티 정보가 포함된 퍼시스턴트 볼륨 (PV) 을 프로비저닝하여 파드를 올바른 노드에 스케줄링한다.쿠버네티스 퍼시스턴트 볼륨을 사용하긴 하지만 EC2 인스턴스 스토어 볼륨은 사실상 일시적입니다. 임시 디스크에 기록된 데이터는 인스턴스의 수명 기간 동안만 사용할 수 있습니다. 인스턴스가 종료되면 데이터도 종료됩니다. 자세한 내용은 이 블로그를 참조하십시오.

+

Amazon EC2 인스턴스 스토어 볼륨을 사용할 때는 총 IOPS 한도가 호스트와 공유되며 이는 파드를 특정 호스트에 바인딩한다는 점에 유의하세요. Amazon EC2 인스턴스 스토어 볼륨을 채택하기 전에 워크로드 요구 사항을 철저히 검토해야 합니다.

+

퍼시스턴트 볼륨

+

쿠버네티스는 일반적으로 스테이트리스 (Stateless) 애플리케이션을 실행하는 것에 적합합니다. 하지만 한 요청부터 다음 요청까지 영구 데이터나 정보를 보존해야 하는 마이크로서비스를 실행해야 하는 시나리오가 있을 수 있습니다. 데이터베이스는 이러한 사용 사례의 일반적인 예입니다. 하지만 파드와 그 안에 있는 컨테이너 또는 프로세스는 사실상 일시적이다. 파드의 수명 이후에도 데이터를 유지하려면 PV를 사용하여 파드와 독립적인 특정 위치의 스토리지에 대한 액세스를 정의할 수 있습니다. PV와 관련된 비용은 사용 중인 스토리지의 유형과 애플리케이션이 스토리지를 사용하는 방식에 따라 크게 달라집니다.

+

여기에는 Amazon EKS에서 쿠버네티스 PV를 지원하는 다양한 유형의 스토리지 옵션이 나열되어 있습니다. 아래에서 다루는 스토리지 옵션은 Amazon EBS, Amazon EFS, Amazon FSx for Lustre, Amazon FSx for NetApp ONTAP입니다.

+

Amazon Elastic Block Store (EBS) 볼륨

+

Amazon EBS 볼륨은 쿠버네티스 PV로 사용하여 블록 레벨 스토리지 볼륨을 제공할 수 있습니다. 이는 무작위 읽기 및 쓰기에 의존하는 데이터베이스와 길고 지속적인 읽기 및 쓰기를 수행하는 처리량 집약적인 애플리케이션에 적합합니다. Amazon EBS 컨테이너 스토리지 인터페이스 (CSI) 드라이버를 사용하면 Amazon EKS 클러스터가 퍼시스턴트 볼륨에 대한 Amazon EBS 볼륨의 수명 주기를 관리할 수 있습니다. 컨테이너 스토리지 인터페이스는 쿠버네티스와 스토리지 시스템 간의 상호 작용을 지원하고 촉진합니다. CSI 드라이버가 EKS 클러스터에 배포되면 퍼시스턴트 볼륨 (PV), 퍼시스턴트 볼륨 클레임 (PVC) 및 스토리지 클래스 (SC) 와 같은 네이티브 쿠버네티스 스토리지 리소스를 통해 해당 기능에 액세스할 수 있습니다. 이 링크는 Amazon EBS CSI 드라이버를 사용하여 Amazon EBS 볼륨과 상호 작용하는 방법에 대한 실제 예를 제공합니다.

+

적절한 볼륨 선택

+

가격과 성능 간의 적절한 균형을 제공하는 최신 블록 스토리지 (gp3)를 사용하는 것이 좋습니다. 또한 추가 블록 스토리지 용량을 프로비저닝할 필요 없이 볼륨 크기와 독립적으로 볼륨 IOPS와 처리량을 확장할 수 있습니다. 현재 gp2 볼륨을 사용하고 있다면 gp3 볼륨으로 마이그레이션하는 것이 좋습니다. 이 블로그 에서는 아마존 EKS 클러스터에서 gp2에서 gp3로 마이그레이션하는 방법을 설명합니다.

+

더 높은 성능이 필요하고 단일 gp3 볼륨이 지원할 수 있는 용량보다 큰 볼륨이 필요한 애플리케이션이 있는 경우 io2 block express사용을 고려해야 합니다. 이 유형의 스토리지는 지연 시간이 짧은 기타 대규모 데이터베이스 또는 SAP HANA와 같이 규모가 크고 I/O 집약적이며 미션 크리티컬 배포에 적합합니다. 단, 인스턴스의 EBS 성능은 인스턴스의 성능 제한에 의해 제한되므로 모든 인스턴스가 io2 블록 익스프레스 볼륨을 지원하는 것은 아닙니다. 지원되는 인스턴스 유형 및 기타 고려 사항은 이 문서에서 확인할 수 있습니다.

+

단일 gp3 볼륨은 최대 16,000 IOPS, 최대 처리량 1,000MiB/초, 최대 16TiB를 지원할 수 있습니다.최대 256,000 IOPS, 4,000MiB/s, 처리량 및 64TiB를 제공하는 최신 세대의 프로비저닝된 IOPS SSD 볼륨입니다.

+

이러한 옵션 중에서 애플리케이션 요구 사항에 맞게 스토리지 성능과 비용을 조정하는 것이 가장 좋습니다.

+

시간 경과에 따른 모니터링 및 최적화

+

애플리케이션의 기준 성능을 이해하고 선택한 볼륨에 대해 모니터링하여 요구 사항/기대치를 충족하는지 또는 과다 프로비저닝되었는지 확인하는 것이 중요합니다 (예: 프로비저닝된 IOPS가 완전히 활용되지 않는 시나리오).

+

처음부터 큰 볼륨을 할당하는 대신 데이터가 누적되면서 점차 볼륨 크기를 늘릴 수 있습니다. Amazon EBS CSI 드라이버 (aws-ebs-csi-driver) 의 볼륨 크기 조정기능을 사용하여 볼륨 크기를 동적으로 조정할 수 있습니다. EBS 볼륨 크기만 늘릴 수 있다는 점에 유의하세요.

+

매달려 있는 EBS 볼륨을 식별하고 제거하려면 AWS Trusted Advisor의 비용 최적화 카테고리를 사용할 수 있습니다. 이 기능을 사용하면 연결되지 않은 볼륨이나 일정 기간 쓰기 작업이 매우 적은 볼륨을 식별할 수 있습니다. Popeye라는 클라우드 네이티브 오픈 소스 읽기 전용 도구가 있습니다. 이 도구는 쿠버네티스 클러스터를 스캔하고 배포된 리소스 및 구성과 관련된 잠재적 문제를 보고합니다. 예를 들어, 사용하지 않는 PV와 PVC를 스캔하여 바인딩되었는지 또는 볼륨 마운트 오류가 있는지 확인할 수 있습니다.

+

모니터링에 대한 자세한 내용은 EKS 비용 최적화 옵저버빌리티 가이드를 참조하십시오.

+

고려할 수 있는 또 다른 옵션은 AWS Compute Optimizer EBS 볼륨 권장 사항입니다. 이 도구는 최적의 볼륨 구성과 필요한 정확한 성능 수준을 자동으로 식별합니다. 예를 들어, 지난 14일 동안의 최대 사용률을 기준으로 프로비저닝된 IOPS, 볼륨 크기 및 EBS 볼륨 유형과 관련된 최적의 설정에 사용할 수 있습니다. 또한 권장 사항을 바탕으로 얻을 수 있는 잠재적인 월별 비용 절감 효과를 수치화합니다. 자세한 내용은 이 블로그 에서 확인할 수 있습니다.

+

백업 보존 정책

+

특정 시점 스냅샷을 생성하여 Amazon EBS 볼륨의 데이터를 백업할 수 있습니다.Amazon EBS CSI 드라이버는 볼륨 스냅샷을 지원합니다. 여기에 설명된 단계를 사용하여 스냅샷을 생성하고 EBS PV를 복원하는 방법을 배울 수 있습니다.

+

이후 스냅샷은 증분 백업으로 진행됩니다. 즉, 가장 최근 스냅샷 이후에 변경된 디바이스의 블록만 저장됩니다. 이렇게 하면 스냅샷을 만드는 데 필요한 시간이 최소화되고 데이터를 복제하지 않아 스토리지 비용이 절약됩니다. 하지만 적절한 보존 정책 없이 오래된 EBS 스냅샷의 수를 늘리면 대규모 운영 시 예상치 못한 비용이 발생할 수 있습니다. AWS API를 통해 아마존 EBS 볼륨을 직접 백업하는 경우, 아마존 EBS 스냅샷과 EBS 기반 AMI를 위한 자동화된 정책 기반 수명 주기 관리 솔루션을 제공하는 Amazon Data Lifecycle Manager(DLM)를 활용할 수 있습니다. 콘솔을 사용하면 EBS 스냅샷과 AMI의 생성, 보존 및 삭제를 더 쉽게 자동화할 수 있습니다.

+
+

Note

+

현재로서는 Amazon EBS CSI 드라이버를 통해 Amazon DLM을 사용할 수 있는 방법이 없습니다.

+
+

쿠버네티스 환경에서는 Velero라는 오픈 소스 도구를 활용하여 EBS 퍼시스턴트 볼륨을 백업할 수 있습니다. 백업이 만료되도록 작업을 예약할 때 TTL 플래그를 설정할 수 있습니다. 다음은 Velero의 예제는 이 가이드를 참고합니다.

+

Amazon Elastic File System (EFS)

+

Amazon Elastic File System (EFS) 는 서버리스 방식의 완전 탄력적 파일 시스템으로, 광범위한 워크로드 및 애플리케이션에 대해 표준 파일 시스템 인터페이스 및 파일 시스템 시맨틱스를 사용하여 파일 데이터를 공유할 수 있습니다. 워크로드 및 애플리케이션의 예로는 Wordpress와 Drupal, JIRA와 Git과 같은 개발자 도구, Jupyter와 같은 공유 노트북 시스템, 홈 디렉터리가 있습니다.

+

Amazon EFS의 주요 이점 중 하나는 여러 노드와 여러 가용 영역에 분산된 여러 컨테이너에 마운트할 수 있다는 것입니다. 또 다른 이점은 사용한 스토리지에 대해서만 비용을 지불한다는 것입니다. EFS 파일 시스템은 파일을 추가하고 제거함에 따라 자동으로 확장 및 축소되므로 용량 계획이 필요 없습니다.

+

쿠버네티스에서 Amazon EFS를 사용하려면, Amazon EFS 컨테이너 스토리지 인터페이스 (CSI) 드라이버 인, aws-efs-csi-driver를 사용해야 합니다. 현재 드라이버는 동적으로 액세스 포인트를 생성할 수 있습니다. 하지만 Amazon EFS 파일 시스템을 먼저 프로비저닝하고 Kubernetes 스토리지 클래스 파라미터의 입력으로 제공해야 합니다.

+

올바른 EFS 스토리지 클래스 선택

+

Amazon EFS는 네 가지 스토리지 클래스를 제공합니다.

+

두 가지 표준 스토리지 클래스:

+ +

두 개의 단일-존 스토리지 클래스:

+ +

Infrequent Access (IA) 스토리지 클래스는 매일 액세스하지 않는 파일에 맞게 비용 최적화되어 있습니다. Amazon EFS 수명 주기 관리를 사용하면 수명 주기 정책 기간 (7, 14, 30, 60 또는 90일) 동안 액세스하지 않은 파일을 IA 스토리지 클래스로 이동할 수 있어 EFS Standard 및 EFS One Zone 스토리지 클래스에 비해 각각 최대 92% 까지 스토리지 비용을 절감할 수 있습니다.

+

EFS Intelligent-Tiering을 사용하면 수명 주기 관리가 파일 시스템의 액세스 패턴을 모니터링하고 파일을 가장 최적의 스토리지 클래스로 자동으로 이동합니다.

+
+

Note

+

aws-efs-csi-driver는 현재 스토리지 클래스 변경, 라이프사이클 관리 또는 Intelligent-Tiering를 제어할 수 없습니다. 이러한 설정은 AWS 콘솔이나 EFS API를 통해 수동으로 설정해야 합니다.

+
+
+

Note

+

aws-efs-csi-driver는 윈도우 기반 컨테이너 이미지와 호환되지 않습니다.

+
+
+

Note

+

파일 시스템 크기에 비례하는 메모리 양을 소비하는 DiskUsage 함수로 인해 vol-metrics-opt-in (볼륨 메트릭 출력) 을 활성화하면 알려진 메모리 문제가 발생합니다. 현재는 대용량 파일 시스템에서는 --vol-metrics-opt-in 옵션을 비활성화하여 메모리를 너무 많이 사용하지 않도록 설정하는 것이 좋습니다.자세한 내용은 깃허브 이슈 링크 에서 확인하세요.

+
+

Amazon FSx for Lustre

+

Lustre는 최대 수백 GB/s의 처리량과 작업당 밀리초 미만의 지연 시간이 필요한 워크로드에 일반적으로 사용되는 고성능 병렬 파일 시스템입니다. 머신러닝 교육, 금융 모델링, HPC, 비디오 처리와 같은 시나리오에 사용됩니다. Amazon FSx for Lustre는 Amazon S3와 원활하게 통합되는 확장성과 성능을 갖춘 완전 관리형 공유 스토리지를 제공합니다.

+

FSx for Lustre CSI 드라이버를 사용하여 Amazon EKS 또는 AWS 내 자체 관리형 쿠버네티스 클러스터에서 FSx for Lustre 볼륨을 퍼시스턴트 볼륨으로 사용할 수 있습니다. 자세한 내용과 예제는 Amazon EKS 설명서를 참조하십시오.

+

Amazon S3로 연결

+

Amazon S3와 같이 내구성이 뛰어난 장기 데이터 리포지토리를 FSx for Lustre 파일 시스템과 연결하는 것이 좋습니다. 일단 연결되면 대규모 데이터 세트가 필요에 따라 Amazon S3에서 FSx for Lustre 파일 시스템으로 레이지 로드(lazy load)됩니다. 분석을 실행하고 결과를 S3로 다시 가져온 다음 [Lustre] 파일 시스템을 삭제할 수도 있습니다.

+

적절한 배포 및 스토리지 옵션 선택

+

FSx for Lustre는 다양한 배포 옵션을 제공합니다.첫 번째 옵션은 스크래치로 데이터를 복제하지 않는 반면, 두 번째 옵션은 이름에서 알 수 있듯이 데이터를 유지하는 지속적입니다.

+

첫 번째 옵션 (스크래치)을 사용하면 일시적인 단기 데이터 처리 비용을 줄일 수 있습니다. 영구 배포 옵션은 AWS 가용 영역 내에서 데이터를 자동으로 복제하는 장기 스토리지를 위해 설계되었습니다. 또한 SSD와 HDD 스토리지를 모두 지원합니다.

+

FSx for lustre 파일 시스템의 쿠버네티스 스토리지클래스에 있는 파라미터에서 원하는 배포 유형을 구성할 수 있습니다. 다음은 샘플 템플릿을 제공하는 링크입니다.

+
+

Note

+

지연 시간에 민감한 워크로드 또는 최고 수준의 IOPS/처리량이 필요한 워크로드의 경우 SSD 스토리지를 선택해야 합니다. 지연 시간에 민감하지 않은 처리량 중심 워크로드의 경우 HDD 스토리지를 선택해야 합니다.

+
+

데이터 압축 활성화

+

“LZ4"를 데이터 압축 유형으로 지정하여 파일 시스템에서 데이터 압축을 활성화할 수도 있습니다. 활성화되면 새로 작성된 모든 파일은 디스크에 기록되기 전에 FSx for Lustre에서 자동으로 압축되고 읽을 때 압축이 해제됩니다. LZ4 데이터 압축 알고리즘은 손실이 없으므로 압축된 데이터로 원본 데이터를 완전히 재구성할 수 있습니다.

+

lustre 파일 시스템의 쿠버네티스 스토리지클래스용 FSx의 파라미터에서 데이터 압축 유형을 LZ4로 구성할 수 있습니다. 값이 기본값인 NONE으로 설정되면 압축이 비활성화됩니다. 이 링크는 샘플 템플릿을 제공합니다.

+
+

Note

+

Amazon FSx for Lustre는 윈도우 기반 컨테이너 이미지와 호환되지 않습니다.

+
+

Amazon FSx for NetApp ONTAP

+

Amazon FSx for NetApp ONTAP은 NetApp의 ONTAP 파일 시스템을 기반으로 구축된 완전 관리형 공유 스토리지입니다. FSx for ONTAP은 AWS 또는 온프레미스에서 실행되는 리눅스, 윈도우 및 macOS 컴퓨팅 인스턴스에서 광범위하게 액세스할 수 있는 기능이 풍부하고 빠르며 유연한 공유 파일 스토리지를 제공합니다.

+

NetApp ONTAP용 Amazon FSx는 1/기본 계층2/용량 풀 계층이라는 두 가지 스토리지 계층을 지원합니다.

+

기본 계층은 지연 시간에 민감한 활성 데이터를 위한 프로비저닝된 고성능 SSD 기반 계층입니다. 완전히 탄력적인 용량 풀 계층은 자주 액세스하지 않는 데이터에 대해 비용 최적화되고, 데이터가 계층화됨에 따라 자동으로 확장되며, 사실상 무제한 페타바이트의 용량을 제공합니다. 용량 풀 스토리지에서 데이터 압축 및 중복 제거를 활성화하여 데이터가 소비하는 스토리지 용량을 더욱 줄일 수 있습니다. NetApp의 기본 정책 기반 FabricPool 기능은 데이터 액세스 패턴을 지속적으로 모니터링하여 스토리지 계층 간에 데이터를 양방향으로 자동 전송하여 성능과 비용을 최적화합니다.

+

NetApp의 Astra Trident는 CSI 드라이버를 사용한 동적 스토리지 오케스트레이션을 제공합니다. 이를 통해 Amazon EKS 클러스터는 NetApp ONTAP 파일 시스템용 Amazon FSX가 지원하는 퍼시스턴트 볼륨 PV의 수명 주기를 관리할 수 있습니다. 시작하려면 아스트라 트라이던트 설명서의 NetApp ONTAP용 Amazon FSx와 함께 아스트라 트라이던트 사용을 참조하십시오.

+

기타 고려 사항

+

컨테이너 이미지 크기 최소화

+

컨테이너가 배포되면 컨테이너 이미지가 호스트에 여러 레이어로 캐시됩니다. 이미지 크기를 줄이면 호스트에 필요한 스토리지 양을 줄일 수 있습니다.

+

처음부터 스크래치 이미지 또는 distroless 컨테이너 이미지 (애플리케이션 및 런타임 종속성만 포함)와 같은 간소화된 기본 이미지를 사용하면 스토리지 비용을 절감할 수 있을 뿐만 아니라 공격 노출 영역 감소 및 이미지 풀 타임 단축과 같은 기타 부수적인 이점도 줄일 수 있습니다.

+

최소한의 이미지를 만들 수 있는 쉽고 안전한 방법을 제공하는 Slim.ai와 같은 오픈 소스 도구를 사용하는 것도 고려해 보는 것이 좋습니다.

+

여러 계층의 패키지, 도구, 애플리케이션 종속성, 라이브러리는 컨테이너 이미지 크기를 쉽게 부풀릴 수 있습니다. 다단계 빌드를 사용하면 최종 이미지에서 필요하지 않은 모든 요소를 제외하고 한 스테이지에서 다른 스테이지로 아티팩트를 선택적으로 복사할 수 있습니다. 더 많은 이미지 구축 모범 사례를 여기에서 확인할 수 있습니다.

+

고려해야 할 또 다른 사항은 캐시된 이미지를 얼마나 오래 유지할 것인가입니다. 일정량의 디스크를 사용하는 경우 이미지 캐시에서 오래된 이미지를 정리하는 것이 좋습니다. 이렇게 하면 호스트 작업을 위한 충분한 공간을 확보하는 데 도움이 됩니다. 기본적으로 kubelet은 미사용 이미지에 대해 5분마다, 미사용 컨테이너에 대해서는 1분마다 가비지 수집을 수행합니다.

+

미사용 컨테이너 및 이미지 가비지 컬렉션에 대한 옵션을 구성하려면 구성 파일을 사용하여 kubelet을 조정하고 kubeletConfiguration 리소스 유형을 사용하여 가비지 컬렉션과 관련된 파라미터를 변경하십시오.

+

이에 대한 자세한 내용은 쿠버네티스 문서에서 확인할 수 있다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cost_optimization/cost_optimization_index/index.html b/ko/cost_optimization/cost_optimization_index/index.html new file mode 100644 index 000000000..9e72e2332 --- /dev/null +++ b/ko/cost_optimization/cost_optimization_index/index.html @@ -0,0 +1,2087 @@ + + + + + + + + + + + + + + + + + + + + + + + 홈 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Amazon EKS Best Practices Guide for Cost Optimization

+

Cost Optimization is achieving your business outcomes at the lowest price point. By following the documentation in this guide you will optimize your Amazon EKS workloads.

+

General Guidelines

+

In the cloud, there are a number of general guidelines that can help you achieve cost optimization of your microservices: ++ Ensure that workloads running on Amazon EKS are independent of specific infrastructure types for running your containers, this will give greater flexibility with regards to running them on the least expensive types of infrastructure. While using Amazon EKS with EC2, there can be exceptions when we have workloads that require specific type of EC2 Instance types like requiring a GPU or other instance types, due to the nature of the workload. ++ Select optimally profiled container instances — profile your production or pre-production environments and monitor critical metrics like CPU and memory, using services like Amazon CloudWatch Container Insights for Amazon EKS or third party tools that are available in the Kubernetes ecosystem. This will ensure that we can allocate the right amount of resources and avoid wastage of resources. ++ Take advantage of the different purchasing options that are available in AWS for running EKS with EC2, e.g. On-Demand, Spot and Savings Plan.

+

EKS Cost Optimization Best Practices

+

There are three general best practice areas for cost optimization in the cloud:

+
    +
  • Cost-effective resources (Auto Scaling, Down Scaling, Policies and Purchasing Options)
  • +
  • Expenditure awareness (Using AWS and third party tools)
  • +
  • Optimizing over time (Right Sizing)
  • +
+

As with any guidance there are trade-offs. Ensure you work with your organization to understand the priorities for this workload and which best practices are most important.

+

How to use this guide

+

This guide is meant for devops teams who are responsible for implementing and managing the EKS clusters and the workloads they support. The guide is organized into different best practice areas for easier consumption. Each topic has a list of recommendations, tools to use and best practices for cost optimization of your EKS clusters. The topics do not need to read in a particular order.

+

Key AWS Services and Kubernetes features

+

Cost optimization is supported by the following AWS services and features: ++ EC2 Instance types, Savings Plan (and Reserved Instances) and Spot Instances, at different prices. ++ Auto Scaling along with Kubernetes native Auto Scaling policies. Consider Savings Plan (Previously Reserved Instances) for predictable workloads. Use managed data stores like EBS and EFS, for elasticity and durability of the application data. ++ The Billing and Cost Management console dashboard along with AWS Cost Explorer provides an overview of your AWS usage. Use AWS Organizations for granular billing details. Details of several third party tools have also been shared. ++ Amazon CloudWatch Container Metrics provides metrics around usage of resources by the EKS cluster. In addition to the Kubernetes dashboard, there are several tools in the Kubernetes ecosystem that can be used to reduce wastage.

+

This guide includes a set of recommendations that you can use to improve the cost optimization of your Amazon EKS cluster.

+

Feedback

+

This guide is being released on GitHub so as to collect direct feedback and suggestions from the broader EKS/Kubernetes community. If you have a best practice that you feel we ought to include in the guide, please file an issue or submit a PR in the GitHub repository. Our intention is to update the guide periodically as new features are added to the service or when a new best practice evolves.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/cost_optimization/optimizing_WIP/index.html b/ko/cost_optimization/optimizing_WIP/index.html new file mode 100644 index 000000000..90ab398ae --- /dev/null +++ b/ko/cost_optimization/optimizing_WIP/index.html @@ -0,0 +1,2148 @@ + + + + + + + + + + + + + + + + + + + Optimizing over time (Right Sizing) - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Optimizing over time (Right Sizing)

+

Right Sizing as per the AWS Well-Architected Framework, is “… using the lowest cost resource that still meets the technical specifications of a specific workload”.

+

When you specify the resource requests for the Containers in a Pod, the scheduler uses this information to decide which node to place the Pod on. When you specify a resource limits for a Container, the kubelet enforces those limits so that the running container is not allowed to use more of that resource than the limit you set. The details of how Kubernetes manages resources for containers are given in the documentation.

+

In Kubernetes, this means setting the right compute resources (CPU and memory are collectively referred to as compute resources) - setting the resource requests that align as close as possible to the actual utilization. The tools for getting the actual resource usags of Pods are given in the section on Rexommendations below.

+

Amazon EKS on AWS Fargate: When pods are scheduled on Fargate, the vCPU and memory reservations within the pod specification determine how much CPU and memory to provision for the pod. If you do not specify a vCPU and memory combination, then the smallest available combination is used (.25 vCPU and 0.5 GB memory). The list of vCPU and memory combinations that are available for pods running on Fargate are listed in the Amazon EKS User Guide.

+

Amazon EKS on EC2: When you create a Pod, you can specify how much of each resource like CPU and Memory, a Container needs. It is important we do not over-provision (which will lead to wastage) or under-provision (will lead to throttling) the resources allocated to the containers.

+

Recommendations

+

FairwindsOps Goldilocks: The FairwindsOps Goldilocks is a tool that creates a Vertical Pod Autoscaler (VPA) for each deployment in a namespace and then queries them for information. Once the VPAs are in place, we see recommendations appear in the Goldilocks dashboard.

+

Deploy the Vertical Pod Autoscaler as per the documentation.

+

Enable Namespace - Pick an application namespace and label it like so in order to see some data, in the following example we are specifying the default namespace:

+
$ kubectl label ns default goldilocks.fairwinds.com/enabled=true
+
+

Viewing the Dashboard - The default installation creates a ClusterIP service for the dashboard. You can access via port forward:

+
$ kubectl -n goldilocks port-forward svc/goldilocks-dashboard 8080:80
+
+

Then open your browser to http://localhost:8080

+

Goldilocks recommendation Page

+

Use Application Profiling tools like CloudWatch Container Insights and Prometheus Metrics in Amazon CloudWatch

+

Use CloudWatch Container Insights to see how you can use native CloudWatch features to monitor your EKS Cluster performance. You can use CloudWatch Container Insights to collect, aggregate, and summarize metrics and logs from your containerized applications and microservices running on Amazon Elastic Kubernetes Service. The metrics include utilization for resources such as CPU, memory, disk, and network - which can help with right-sizing Pods and save costs.

+

Container Insights Prometheus Metrics Monitoring At present, support for Prometheus metrics is still in beta. CloudWatch Container Insights monitoring for Prometheus automates the discovery of Prometheus metrics from containerized systems and workloads. Prometheus is an open-source systems monitoring and alerting toolkit. All Prometheus metrics are collected in the ContainerInsights/Prometheus namespace.

+

The Metrics provided by cAdvisor and kube-state-metrics can be used for monitoring pods on Amazon EKS on AWS Fargate using Prometheus and Grafana, which can then be used to implement requests in your containers. Please refer to this blog for more details.

+

Right Size Guide: The right size guide (rsg) is a simple CLI tool that provides you with memory and CPU recommendations for your application. This tool works across container orchestrators, including Kubernetes and easy to deploy.

+

By using tools like CloudWatch Container Insights, Kube Resource Report, Goldilocks and others, applications running in the Kubernetes cluster can be right sized and potentially lower your costs.

+

Resources

+

Refer to the following resources to learn more about best practices for cost optimization.

+

Documentation and Blogs

+ +

Tools

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/index.html b/ko/index.html new file mode 100644 index 000000000..082bc129e --- /dev/null +++ b/ko/index.html @@ -0,0 +1,2162 @@ + + + + + + + + + + + + + + + + + + + + + 소개 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

소개

+

EKS 모범 사례 가이드에 오신 것을 환영합니다. 이 프로젝트의 주요 목표는 Amazon EKS의 Day 2 작업에 대한 일련의 모범 사례를 제공하는 것입니다. 우리는 이 지침을 GitHub에 게시하기로 결정하여 신속하게 반복하고, 다양한 문제에 대해 시기 적절하고 효과적인 권장 사항을 제공하고, 더 광범위한 커뮤니티의 제안을 쉽게 통합할 수 있습니다.

+

현재 다음 주제에 대한 가이드를 게시했습니다.

+ +

또한 이 가이드의 권장 사항 중 일부를 확인하기 위해 hardeneks라는 Python 기반 CLI(Command Line Interface)를 오픈 소스로 제공했습니다.

+

향후 성능, 비용 최적화 및 운영 우수성에 대한 모범 사례 지침을 게시할 예정입니다.

+

관련 가이드

+

AWS는 EKS 사용자 가이드외에도 EKS 구성에 도움이 될 수 있는 몇 가지 다른 가이드를 게시했습니다.

+ +

기여

+

이 가이드에 기여해 주시기 바랍니다. 효과가 입증된 방법을 구현했다면 이슈나 풀 리퀘스트(PR)를 열어 공유해 주세요. 마찬가지로, 이미 게시한 지침에서 오류나 결함을 발견한 경우 PR을 제출하여 수정하시기 바랍니다. PR 제출 지침은 기여 가이드라인에서 확인할 수 있습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/karpenter/index.html b/ko/karpenter/index.html new file mode 100644 index 000000000..e159ed10a --- /dev/null +++ b/ko/karpenter/index.html @@ -0,0 +1,2791 @@ + + + + + + + + + + + + + + + + + + + + + + + Karpenter - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Karpenter 모범 사례

+

Karpenter

+

Karpenter는 unschedulable 파드에 대응하여 새 노드를 자동으로 프로비저닝하는 오픈 소스 클러스터 오토스케일러입니다. Karpenter는 pending 상태의 파드의 전체 리소스 요구 사항을 평가하고 이를 실행하기 위한 최적의 인스턴스 유형을 선택합니다. 데몬셋이 아닌 파드가 없는 인스턴스를 자동으로 확장하거나 종료하여 낭비를 줄입니다. 또한 파드를 능동적으로 이동하고 노드를 삭제하거나 더 저렴한 인스턴스 유형으로 교체하여 클러스터 비용을 절감하는 통합 기능도 지원합니다.

+

Karpenter를 사용해야 하는 이유

+

Karpenter가 출시되기 전에 쿠버네티스 사용자는 주로 Amazon EC2 Auto Scaling 그룹쿠버네티스 Cluster Autoscaler(CA)를 사용하여 클러스터의 컴퓨팅 용량을 동적으로 조정했습니다. Karpenter를 사용하면 유연성과 다양성을 달성하기 위해 수십 개의 노드 그룹을 만들 필요가 없습니다. 게다가 Karpenter는 (CA처럼) 쿠버네티스 버전과 밀접하게 연결되어 있지 않기 때문에 AWS와 쿠버네티스 API 사이를 오갈 필요가 없습니다.

+

Karpenter는 단일 시스템 내 인스턴스 오케스트레이션 기능을 통합적으로 수행하며 더 간단하고 안정적이며 보다 클러스터를 잘 파악합니다. Karpenter는 다음과 같은 간소화된 방법을 제공하여 클러스터 오토스케일러가 제시하는 몇 가지 문제를 해결하도록 설계되었습니다.

+
    +
  • 워크로드 요구 사항에 따라 노드를 프로비저닝합니다.
  • +
  • 유연한 워크로드 프로비저너 옵션을 사용하여 인스턴스 유형별로 다양한 노드 구성을 생성합니다. Karpenter를 사용하면 많은 특정 사용자 지정 노드 그룹을 관리하는 대신 유연한 단일 프로비저너로 다양한 워크로드 용량을 관리할 수 있습니다.
  • +
  • 노드를 빠르게 시작하고 파드를 스케줄링하여 대규모 파드 스케줄링을 개선합니다.
  • +
+

Karpenter 사용에 대한 정보 및 설명서를 보려면 karpenter.sh 사이트를 방문하세요.

+

권장 사항

+

모범 사례는 Karpenter, 프로비저너(provisioner), 파드 스케줄링 섹션으로 구분됩니다.

+

Karpenter 모범 사례

+

다음 모범 사례는 Karpenter 자체와 관련된 주제를 다룹니다.

+

변화하는 용량 요구가 있는 워크로드에는 Karpenter를 사용하세요

+

Karpenter는 Auto Scaling 그룹(ASG) 및 관리형 노드 그룹(MNG)보다 쿠버네티스 네이티브 API에 더 가까운 스케일링 관리를 제공합니다. ASG 및 MNG는 EC2 CPU 부하와 같은 AWS 레벨 메트릭을 기반으로 스케일링이 트리거되는 AWS 네이티브 추상화입니다. Cluster Autoscaler는 쿠버네티스 추상화를 AWS 추상화로 연결하지만, 이로 인해 특정 가용영역에 대한 스케줄링과 같은 유연성이 다소 떨어집니다.

+

Karpenter는 일부 유연성을 쿠버네티스에 직접 적용하기 위해 AWS 추상화 계층을 제거합니다. Karpenter는 수요가 급증하는 시기에 직면하거나 다양한 컴퓨팅 요구 사항이 있는 워크로드가 있는 클러스터에 가장 적합합니다.MNG와 ASG는 정적이고 일관성이 높은 워크로드를 실행하는 클러스터에 적합합니다. 요구 사항에 따라 동적으로 관리되는 노드와 정적으로 관리되는 노드를 혼합하여 사용할 수 있습니다.

+

다음과 같은 경우에는 다른 Auto Scaling 프로젝트를 고려합니다.

+

Karpenter에서 아직 개발 중인 기능이 필요합니다. Karpenter는 비교적 새로운 프로젝트이므로 아직 Karpenter에 포함되지 않은 기능이 필요한 경우 당분간 다른 오토스케일링 프로젝트를 고려해 보세요.

+

EKS Fargate 또는 노드 그룹에 속한 워커 노드에서 Karpenter 컨트롤러를 실행합니다.

+

Karpenter는 헬름 차트를 사용하여 설치됩니다. 이 헬름 차트는 Karpenter 컨트롤러와 웹훅 파드를 디플로이먼트로 설치하는데, 이 디플로이먼트를 실행해야 컨트롤러를 사용하여 클러스터를 확장할 수 있습니다. 최소 하나 이상의 워커 노드가 있는 소규모 노드 그룹을 하나 이상 사용하는 것이 좋습니다. 대안으로, 'karpenter' 네임스페이스에 대한 Fargate 프로파일을 생성하여 EKS Fargate에서 이런 파드를 실행할 수 있습니다. 이렇게 하면 이 네임스페이스에 배포된 모든 파드가 EKS Fargate에서 실행됩니다. Karpenter가 관리하는 노드에서는 Karpenter를 실행하지 마십시오.

+

Karpenter에서 사용자 지정 시작 템플릿(launch template)을 사용하지 마십시오.

+

Karpenter는 사용자 지정 시작 템플릿을 사용하지 말 것을 강력히 권장합니다. 사용자 지정 시작 템플릿을 사용하면 멀티 아키텍처 지원, 노드 자동 업그레이드 기능 및 보안그룹 검색이 불가능합니다. 시작 템플릿을 사용하면 Karpenter 프로비저너 내에서 특정 필드가 중복되고 Karpenter는 다른 필드(예: 서브넷 및 인스턴스 유형)를 무시하기 때문에 혼동이 발생할 수도 있습니다.

+

사용자 지정 사용자 데이터(EC2 User Data)를 사용하거나 AWS 노드 템플릿에서 사용자 지정 AMI를 직접 지정하면 시작 템플릿 사용을 피할 수 있는 경우가 많습니다. 이 작업을 수행하는 방법에 대한 자세한 내용은 노드 템플릿에서 확인할 수 있습니다.

+

워크로드에 맞지 않는 인스턴스 유형은 제외합니다.

+

특정 인스턴스 유형이 클러스터에서 실행되는 워크로드에 필요하지 않은 경우, node.kubernetes.io/instance-type 키에서 해당 인스턴스 유형을 제외하는 것이 좋습니다.

+

다음 예제는 큰 Graviton 인스턴스의 프로비저닝을 방지하는 방법을 보여줍니다.

+
- key: node.kubernetes.io/instance-type
+    operator: NotIn
+    values:
+      'm6g.16xlarge'
+      'm6gd.16xlarge'
+      'r6g.16xlarge'
+      'r6gd.16xlarge'
+      'c6g.16xlarge'
+
+

스팟 사용 시 인터럽트 핸들링 활성화

+

Karpenter는 설정에서 aws.interruptionQueue 값을 통해 네이티브 인터럽트 처리를 지원합니다. 인터럽트 핸들링은 다음과 같이 워크로드에 장애를 일으킬 수 있는 향후 비자발적 인터럽트 이벤트를 감시합니다.

+
    +
  • 스팟 인터럽트 경고
  • +
  • 예정된 변경 상태 이벤트 (유지 관리 이벤트)
  • +
  • 인스턴스 종료 이벤트
  • +
  • 인스턴스 중지 이벤트
  • +
+

Karpenter는 노드에서 이런 이벤트 중 하나가 발생할 것을 감지하면 중단 이벤트가 발생하기 전에 노드를 자동으로 차단(cordon), 드레인 및 종료하여 중단 전에 워크로드를 정리할 수 있는 최대 시간을 제공합니다. 해당 글에서 설명한 것처럼 AWS Node Termination Handler를 Karpenter와 함께 사용하는 것은 권장되지 않습니다.

+

종료 전 2분이 소요되는 체크포인트 또는 기타 형태의 정상적인 드레인이 필요한 파드는 해당 클러스터에서 Karpenter 중단 처리가 가능해야 합니다.

+

아웃바운드 인터넷 액세스가 없는 Amazon EKS 프라이빗 클러스터

+

인터넷 연결 경로 없이 VPC에 EKS 클러스터를 프로비저닝할 때는 EKS 설명서에 나와 있는 프라이빗 클러스터 요구 사항에 따라 환경을 구성했는지 확인해야 합니다. 또한 VPC에 STS VPC 지역 엔드포인트를 생성했는지 확인해야 합니다. 그렇지 않은 경우 아래와 비슷한 오류가 표시됩니다.

+
ERROR controller.controller.metrics Reconciler error {"commit": "5047f3c", "reconciler group": "karpenter.sh", "reconciler kind": "Provisioner", "name": "default", "namespace": "", "error": "fetching instance types using ec2.DescribeInstanceTypes, WebIdentityErr: failed to retrieve credentials\ncaused by: RequestError: send request failed\ncaused by: Post \"https://sts.<region>.amazonaws.com/\": dial tcp x.x.x.x:443: i/o timeout"}
+
+

Karpenter 컨트롤러는 서비스 어카운트용 IAM 역할(IRSA)을 사용하기 때문에 프라이빗 클러스터에서는 이런 변경이 필요합니다. IRSA로 구성된 파드는 AWS 보안 토큰 서비스 (AWS STS) API를 호출하여 자격 증명을 획득합니다. 아웃바운드 인터넷 액세스가 없는 경우 VPC안에서 AWS STS VPC 엔드포인트를 생성하여 사용해야 합니다.

+

또한 프라이빗 클러스터를 사용하려면 SSM용VPC 엔드포인트를 생성해야 합니다. Karpenter는 새 노드를 프로비저닝하려고 할 때 시작 템플릿 구성과 SSM 파라미터를 쿼리합니다. VPC에 SSM VPC 엔드포인트가 없는 경우 다음과 같은 오류가 발생합니다.

+
INFO    controller.provisioning Waiting for unschedulable pods  {"commit": "5047f3c", "provisioner": "default"}
+INFO    controller.provisioning Batched 3 pods in 1.000572709s  {"commit": "5047f3c", "provisioner": "default"}
+INFO    controller.provisioning Computed packing of 1 node(s) for 3 pod(s) with instance type option(s) [c4.xlarge c6i.xlarge c5.xlarge c5d.xlarge c5a.xlarge c5n.xlarge m6i.xlarge m4.xlarge m6a.xlarge m5ad.xlarge m5d.xlarge t3.xlarge m5a.xlarge t3a.xlarge m5.xlarge r4.xlarge r3.xlarge r5ad.xlarge r6i.xlarge r5a.xlarge]        {"commit": "5047f3c", "provisioner": "default"}
+ERROR   controller.provisioning Could not launch node, launching instances, getting launch template configs, getting launch templates, getting ssm parameter, RequestError: send request failed
+caused by: Post "https://ssm.<region>.amazonaws.com/": dial tcp x.x.x.x:443: i/o timeout  {"commit": "5047f3c", "provisioner": "default"}
+
+

가격 목록 쿼리 API를 위한 VPC 엔드포인트 는 없습니다. +결과적으로 가격 데이터는 시간이 지남에 따라 부실해질 것입니다. +Karpenter는 바이너리에 온디맨드 가격 책정 데이터를 포함하여 이 문제를 해결하지만 Karpenter가 업그레이드될 때만 해당 데이터를 업데이트합니다. +가격 데이터 요청이 실패하면 다음과 같은 오류 메시지가 표시됩니다.

+
ERROR   controller.aws.pricing  updating on-demand pricing, RequestError: send request failed
+caused by: Post "https://api.pricing.us-east-1.amazonaws.com/": dial tcp 52.94.231.236:443: i/o timeout; RequestError: send request failed
+caused by: Post "https://api.pricing.us-east-1.amazonaws.com/": dial tcp 52.94.231.236:443: i/o timeout, using existing pricing data from 2022-08-17T00:19:52Z  {"commit": "4b5f953"}
+
+

요약하자면 완전한 프라이빗 EKS 클러스터에서 Karpenter를 사용하려면 다음과 같은 VPC 엔드포인트를 생성해야 합니다.

+
com.amazonaws.<region>.ec2
+com.amazonaws.<region>.ecr.api
+com.amazonaws.<region>.ecr.dkr
+com.amazonaws.<region>.s3 – For pulling container images
+com.amazonaws.<region>.sts – For IAM roles for service accounts
+com.amazonaws.<region>.ssm - If using Karpenter
+
+
+

Note

+

Karpenter (컨트롤러 및 웹훅 배포) 컨테이너 이미지는 Amazon ECR 전용 또는 VPC 내부에서 액세스할 수 있는 다른 사설 레지스트리에 있거나 복사되어야 합니다.그 이유는 Karpenter 컨트롤러와 웹훅 파드가 현재 퍼블릭 ECR 이미지를 사용하고 있기 때문입니다. VPC 내에서 또는 VPC와 피어링된 네트워크에서 이런 이미지를 사용할 수 없는 경우, 쿠버네티스가 ECR Public에서 이런 이미지를 가져오려고 할 때 이미지 가져오기 오류가 발생합니다.

+
+

자세한 내용은 이슈 988이슈 1157 을 참조하십시오.

+

프로비져너 생성

+

다음 모범 사례는 프로비져너 생성과 관련된 주제를 다룹니다.

+

다음과 같은 경우 프로비져너를 여러 개 만들 수 있습니다.

+

여러 팀이 클러스터를 공유하고 서로 다른 워커 노드에서 워크로드를 실행해야 하거나 OS 또는 인스턴스 유형 요구 사항이 다른 경우 여러 프로비저너를 생성하세요. 예를 들어 한 팀은 Bottlerocket을 사용하고 다른 팀은 Amazon Linux를 사용하려고 할 수 있습니다. 마찬가지로 한 팀은 다른 팀에는 필요하지 않은 값비싼 GPU 하드웨어를 사용할 수 있습니다. 프로비저닝 도구를 여러 개 사용하면 각 팀에서 가장 적합한 자산을 사용할 수 있습니다.

+

상호 배타적이거나 가중치가 부여되는 프로비저닝 도구 만들기

+

일관된 스케줄링 동작을 제공하려면 상호 배타적이거나 가중치가 부여되는 프로비저너를 만드는 것이 좋습니다. 일치하지 않고 여러 프로비져너가 일치하는 경우 Karpenter는 사용할 프로비져너를 임의로 선택하여 예상치 못한 결과를 초래합니다. 여러 프로비져너를 만들 때 유용한 예는 다음과 같습니다.

+

GPU를 사용하여 프로비저닝 도구를 만들고 이런 (비용이 많이 드는) 노드에서만 특수 워크로드를 실행하도록 허용:

+
# Provisioner for GPU Instances with Taints
+apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+  name: gpu
+spec:
+  requirements:
+  - key: node.kubernetes.io/instance-type
+    operator: In
+    values:
+    - p3.8xlarge
+    - p3.16xlarge
+  taints:
+  - effect: NoSchedule
+    key: nvidia.com/gpu
+    value: "true"
+  ttlSecondsAfterEmpty: 60
+
+

태인트(Taint)를 위한 톨러레이션(Toleration)을 갖고 있는 디플로이먼트:

+
# Deployment of GPU Workload will have tolerations defined
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: inflate-gpu
+spec:
+  ...
+    spec:
+      tolerations:
+      - key: "nvidia.com/gpu"
+        operator: "Exists"
+        effect: "NoSchedule"
+
+

다른 팀을 위한 일반 디플로이먼트의 경우 프로비저너 사양에 NodeAffinify가 포함될 수 있습니다. 그러면 디플로이먼트는 노드 셀렉터 용어를 사용하여 billing-team 과 일치시킬 수 있습니다.

+
# Provisioner for regular EC2 instances
+apiVersion: karpenter.sh/v1alpha5
+kind: Provisioner
+metadata:
+  name: generalcompute
+spec:
+  labels:
+    billing-team: my-team
+  requirements:
+  - key: node.kubernetes.io/instance-type
+    operator: In
+    values:
+    - m5.large
+    - m5.xlarge
+    - m5.2xlarge
+    - c5.large
+    - c5.xlarge
+    - c5a.large
+    - c5a.xlarge
+    - r5.large
+    - r5.xlarge
+
+

노드 어피니티를 사용하는 디플로이먼트:

+
# Deployment will have spec.affinity.nodeAffinity defined
+kind: Deployment
+metadata:
+  name: workload-my-team
+spec:
+  replicas: 200
+  ...
+    spec:
+      affinity:
+        nodeAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+            nodeSelectorTerms:
+              - matchExpressions:
+                - key: "billing-team"
+                  operator: "In"
+                  values: ["my-team"]
+
+

타이머(TTL)를 사용하여 클러스터에서 노드를 자동으로 삭제합니다.

+

프로비저닝된 노드의 타이머를 사용하여 워크로드 파드가 없거나 만료 시간에 도달한 노드를 삭제할 시기를 설정할 수 있습니다. 노드 만료를 업그레이드 수단으로 사용하여 노드를 폐기하고 업데이트된 버전으로 교체할 수 있습니다. ttlSecondsUntilExpiredttlSecondsAfterEmpty를 사용하여 노드를 프로비저닝 해제하는 방법에 대한 자세한 내용은 Karpenter 설명서의 Karpenter 노드 디프로비저닝 방법을 참조하십시오.

+

특히 스팟을 사용할 때는 Karpenter가 프로비저닝할 수 있는 인스턴스 유형을 지나치게 제한하지 마십시오.

+

스팟을 사용할 때 Karpenter는 가격 및 용량 최적화 할당 전략을 사용하여 EC2 인스턴스를 프로비저닝합니다. 이 전략은 EC2가 시작 중인 인스턴스 수만큼 가장 깊은 풀의 인스턴스를 프로비저닝하고 중단 위험이 가장 적은 인스턴스 수에 맞게 인스턴스를 프로비저닝하도록 지시합니다. 그런 다음 EC2 플릿은 이런 풀 중 가장 저렴한 가격의 스팟 인스턴스를 요청합니다. Karpenter에 사용할 수 있는 인스턴스 유형이 많을수록 EC2는 스팟 인스턴스의 런타임을 더 잘 최적화할 수 있습니다. 기본적으로 Karpenter는 클러스터가 배포된 지역 및 가용영역에서 EC2가 제공하는 모든 인스턴스 유형을 사용합니다. Karpenter는 보류 중인 파드를 기반으로 모든 인스턴스 유형 세트 중에서 지능적으로 선택하여 파드가 적절한 크기와 장비를 갖춘 인스턴스로 스케줄링되도록 합니다. 예를 들어, 파드에 GPU가 필요하지 않은 경우 Karpenter는 GPU를 지원하는 EC2 인스턴스 유형으로 파드를 예약하지 않습니다. 어떤 인스턴스 유형을 사용해야 할지 확실하지 않은 경우 Amazon ec2-instance-selector를 실행하여 컴퓨팅 요구 사항에 맞는 인스턴스 유형 목록을 생성할 수 있습니다. 예를 들어 CLI는 메모리 vCPU,아키텍처 및 지역을 입력 파라미터로 사용하고 이런 제약 조건을 충족하는 EC2 인스턴스 목록을 제공합니다.

+
$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r ap-southeast-1
+c5.large
+c5a.large
+c5ad.large
+c5d.large
+c6i.large
+t2.medium
+t3.medium
+t3a.medium
+
+

스팟 인스턴스를 사용할 때 Karpenter에 너무 많은 제약을 두어서는 안 됩니다. 그렇게 하면 애플리케이션의 가용성에 영향을 미칠 수 있기 때문입니다. 예를 들어 특정 유형의 모든 인스턴스가 회수되고 이를 대체할 적절한 대안이 없다고 가정해 보겠습니다. 구성된 인스턴스 유형의 스팟 용량이 보충될 때까지 파드는 보류 상태로 유지됩니다. 스팟 풀은 AZ마다 다르기 때문에 여러 가용영역에 인스턴스를 분산하여 용량 부족 오류가 발생할 위험을 줄일 수 있습니다. 하지만 일반적인 모범 사례는 Karpenter가 스팟을 사용할 때 다양한 인스턴스 유형 세트를 사용할 수 있도록 하는 것입니다.

+

스케줄링 파드

+

다음 모범 사례는 노드 프로비저닝을 위해 Karpenter를 사용하여 클러스터에 파드를 배포하는 것과 관련이 있습니다.

+

고가용성을 위한 EKS 모범 사례를 따르십시오.

+

고가용성 애플리케이션을 실행해야 하는 경우 일반적인 EKS 모범 사례 권장 사항을 따르십시오. 여러 노드와 영역에 파드를 분산하는 방법에 대한 자세한 내용은 Karpenter 설명서의 토폴로지 확산을 참조하십시오. 파드를 제거하거나 삭제하려는 시도가 있는 경우 중단 예산(Disruption Budgets)을 사용하여 유지 관리가 필요한 최소 가용 파드를 설정하세요.

+

계층화된 제약 조건을 사용하여 클라우드 공급자가 제공하는 컴퓨팅 기능을 제한하십시오.

+

Karpenter의 계층형 제약 조건 모델을 사용하면 복잡한 프로비저너 및 파드 배포 제약 조건 세트를 생성하여 파드 스케줄링에 가장 적합한 조건을 얻을 수 있습니다. 파드 사양이 요청할 수 있는 제약 조건의 예는 다음과 같습니다.

+
    +
  • 특정 애플리케이션만 사용할 수 있는 가용영역에서 실행해야 합니다. 예를 들어 특정 가용영역에 있는 EC2 인스턴스에서 실행되는 다른 애플리케이션과 통신해야 하는 파드가 있다고 가정해 보겠습니다. VPC의 AZ 간 트래픽을 줄이는 것이 목표라면 EC2 인스턴스가 위치한 AZ에 파드를 같은 위치에 배치하는 것이 좋습니다. 이런 종류의 타겟팅은 대개 노드 셀렉터를 사용하여 수행됩니다. 노드 셀렉터에 대한 추가 정보는 쿠버네티스 설명서를 참조하십시오.
  • +
  • 특정 종류의 프로세서 또는 기타 하드웨어가 필요합니다. GPU에서 파드를 실행해야 하는 팟스펙 예제는 Karpenter 문서의 액셀러레이터섹션을 참조하십시오.
  • +
+

결제 경보를 생성하여 컴퓨팅 지출을 모니터링하세요

+

클러스터를 자동으로 확장하도록 구성할 때는 지출이 임계값을 초과했을 때 경고하는 청구 알람를 생성하고 Karpenter 구성에 리소스 제한을 추가해야 합니다. Karpenter로 리소스 제한을 설정하는 것은 Karpenter 프로비저너가 인스턴스화할 수 있는 컴퓨팅 리소스의 최대량을 나타낸다는 점에서 AWS Autoscaling 그룹의 최대 용량을 설정하는 것과 비슷합니다.

+
+

Note

+

전체 클러스터에 대해 글로벌 제한을 설정할 수는 없습니다. 한도는 특정 프로비저너에 적용됩니다.

+
+

아래 스니펫은 Karpenter에게 최대 1000개의 CPU 코어와 1000Gi의 메모리만 프로비저닝하도록 지시합니다. Karpenter는 한도에 도달하거나 초과할 때만 용량 추가를 중단합니다. 한도를 초과하면 Karpenter 컨트롤러는 '1001의 메모리 리소스 사용량이 한도 1000을 초과합니다' 또는 이와 비슷한 모양의 메시지를 컨트롤러 로그에 기록합니다. 컨테이너 로그를 CloudWatch 로그로 라우팅하는 경우 지표 필터를 생성하여 로그에서 특정 패턴이나 용어를 찾은 다음 CloudWatch 알람을 생성하여 구성된 지표 임계값을 위반했을 때 경고를 보낼 수 있습니다.

+

Karpenter에서 제한을 사용하는 자세한 내용은 Karpenter 설명서의 리소스 제한 설정을 참조하십시오.

+
spec:
+  limits:
+    resources:
+      cpu: 1000
+      memory: 1000Gi
+
+

Karpenter가 프로비저닝할 수 있는 인스턴스 유형을 제한하거나 제한하지 않는 경우 Karpenter는 필요에 따라 클러스터에 컴퓨팅 파워를 계속 추가합니다. Karpenter를 이런 방식으로 구성하면 클러스터를 자유롭게 확장할 수 있지만 비용에도 상당한 영향을 미칠 수 있습니다. 이런 이유로 결제 경보를 구성하는 것이 좋습니다. 청구 경보를 사용하면 계정에서 계산된 예상 요금이 정의된 임계값을 초과할 경우 알림을 받고 사전에 알림을 받을 수 있습니다. 자세한 내용은 예상 요금을 사전에 모니터링하기 위한 Amazon CloudWatch 청구 경보 설정을 참조하십시오.

+

기계 학습을 사용하여 비용과 사용량을 지속적으로 모니터링하여 비정상적인 지출을 감지하는 AWS 비용 관리 기능인 비용 예외 탐지를 활성화할 수도 있습니다. 자세한 내용은 AWS 비용 이상 탐지 시작 가이드에서 확인할 수 있습니다. AWS Budgets에서 예산을 편성한 경우, 특정 임계값 위반 시 알림을 받도록 조치를 구성할 수도 있습니다. 예산 활동을 통해 이메일을 보내거나, SNS 주제에 메시지를 게시하거나, Slack과 같은 챗봇에 메시지를 보낼 수 있습니다. 자세한 내용은 AWS 예산 작업 구성을 참조하십시오.

+

제거 금지(do-not-evict) 어노테이션 사용하여 Karpenter가 노드 프로비저닝을 취소하지 못하도록 하세요.

+

Karpenter가 프로비저닝한 노드에서 중요한 애플리케이션(예: 장기 실행 배치 작업 또는 스테이트풀 애플리케이션)을 실행 중이고 노드의 TTL이 만료되었으면* 인스턴스가 종료되면 애플리케이션이 중단됩니다. 파드에 karpenter.sh/do-not-evict 어노테이션을 추가하면 파드가 종료되거나 do-not-evict 어노테이션이 제거될 때까지 Karpenter가 노드를 보존하도록 지시하는 것입니다. 자세한 내용은 디프로비저닝 설명서를 참조하십시오.

+

노드에 데몬셋이 아닌 파드가 작업과 관련된 파드만 남아 있는 경우, Karpenter는 작업 상태가 성공 또는 실패인 한 해당 노드를 대상으로 지정하고 종료할 수 있습니다.

+

통합(Consolidation)을 사용할 때 CPU가 아닌 모든 리소스에 대해 요청=제한(requests=limits)을 구성합니다.

+

일반적으로 파드 리소스 요청과 노드의 할당 가능한 리소스 양을 비교하여 통합 및 스케줄링을 수행합니다. 리소스 제한은 고려되지 않습니다. 예를 들어 메모리 한도가 메모리 요청량보다 큰 파드는 요청을 초과할 수 있습니다. 동일한 노드의 여러 파드가 동시에 버스트되면 메모리 부족(OOM) 상태로 인해 일부 파드가 종료될 수 있습니다.통합은 요청만 고려하여 파드를 노드에 패킹하는 방식으로 작동하기 때문에 이런 일이 발생할 가능성을 높일 수 있다.

+

LimitRanges 를 사용하여 리소스 요청 및 제한에 대한 기본값을 구성합니다.

+

쿠버네티스는 기본 요청이나 제한을 설정하지 않기 때문에 컨테이너는 기본 호스트, CPU 및 메모리의 리소스 사용량을 제한하지 않습니다. 쿠버네티스 스케줄러는 파드의 총 요청(파드 컨테이너의 총 요청 또는 파드 Init 컨테이너의 총 리소스 중 더 높은 요청)을 검토하여 파드를 스케줄링할 워커 노드를 결정합니다. 마찬가지로 Karpenter는 파드의 요청을 고려하여 프로비저닝하는 인스턴스 유형을 결정합니다. 일부 파드에서 리소스 요청을 지정하지 않는 경우 제한 범위를 사용하여 네임스페이스에 적절한 기본값을 적용할 수 있습니다.

+

네임스페이스에 대한 기본 메모리 요청 및 제한 구성을 참조하십시오.

+

정확한 리소스 요청을 모든 워크로드에 적용

+

Karpenter는 워크로드 요구 사항에 대한 정보가 정확할 때 워크로드에 가장 적합한 노드를 시작할 수 있습니다.이는 Karpenter의 통합 기능을 사용하는 경우 특히 중요합니다.

+

모든 워크로드에 대한 리소스 요청/제한 구성 및 크기 조정을 참조하십시오.

+

추가 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/custom-networking/index.html b/ko/networking/custom-networking/index.html new file mode 100644 index 000000000..43421e578 --- /dev/null +++ b/ko/networking/custom-networking/index.html @@ -0,0 +1,2415 @@ + + + + + + + + + + + + + + + + + + + + + + + Custom 네트워킹 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

사용자 지정 네트워킹

+

기본적으로 Amazon VPC CNI는 기본 서브넷에서 선택한 IP 주소를 파드에 할당합니다. 기본 서브넷은 기본 ENI가 연결된 서브넷 CIDR이며, 일반적으로 노드/호스트의 서브넷입니다.

+

서브넷 CIDR이 너무 작으면 CNI가 파드에 할당하기에 충분한 보조 IP 주소를 확보하지 못할 수 있습니다. 이는 EKS IPv4 클러스터의 일반적인 문제입니다.

+

사용자 지정 네트워킹은 이 문제에 대한 한 가지 해결책입니다.

+

사용자 지정 네트워킹은 보조 VPC 주소 공간 (CIDR) 에서 노드 및 파드 IP를 할당하여 IP 고갈 문제를 해결합니다. 사용자 지정 네트워킹 지원은 Eniconfig 사용자 지정 리소스를 지원합니다. ENIConfig에는 파드가 속하게 될 보안 그룹과 함께 대체 서브넷 CIDR 범위 (보조 VPC CIDR에서 파밍) 가 포함되어 있습니다. 사용자 지정 네트워킹이 활성화되면 VPC CNI는 eniconfig에 정의된 서브넷에 보조 ENI를 생성합니다. CNI는 ENIConfig CRD에 정의된 CIDR 범위의 IP 주소를 파드에 할당합니다.

+

사용자 지정 네트워킹에서는 기본 ENI를 사용하지 않으므로, 노드에서 실행할 수 있는 최대 파드 수는 더 적다. 호스트 네트워크 파드는 기본 ENI에 할당된 IP 주소를 계속 사용합니다. 또한 기본 ENI는 소스 네트워크 변환을 처리하고 파드 트래픽을 노드 외부로 라우팅하는 데 사용됩니다.

+

예제 구성

+

사용자 지정 네트워킹은 보조 CIDR 범위에 유효한 VPC 범위를 허용하지만 CG-NAT 공간 (예: 100.64.0.0/10 또는 198.19.0.0/16) 의 CIDR (/16) 은 다른 RFC1918 범위보다 기업 환경에서 사용될 가능성이 적기 때문에 사용하는 것이 좋습니다. VPC에서 사용할 수 있는 허용 및 제한된 CIDR 블록 연결에 대한 자세한 내용은 VPC 설명서의 VPC 및 서브넷 크기 조정 섹션에서 IPv4 CIDR 블록 연결 제한을 참조하십시오.

+

아래 다이어그램에서 볼 수 있듯이 워커 노드의 기본 엘라스틱 네트워크 인터페이스 (ENI) 는 여전히 기본 VPC CIDR 범위 (이 경우 10.0.0.0/16) 를 사용하지만 보조 ENI는 보조 VPC CIDR 범위 (이 경우 100.64.0.0/16) 를 사용합니다. 이제 파드가 100.64.0.0/16 CIDR 범위를 사용하도록 하려면 사용자 지정 네트워킹을 사용하도록 CNI 플러그인을 구성해야 합니다. 여기에 설명된 대로 단계를 수행하면 됩니다.

+

illustration of pods on secondary subnet

+

CNI에서 사용자 지정 네트워킹을 사용하도록 하려면 AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG 환경 변수를 true로 설정하십시오.

+
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true
+
+

AWS_VPC_K8S_CNI_CUSTOM_Network_CFG=true인 경우, CNI는 ENIConfig에 정의된 서브넷의 파드 IP 주소를 할당한다. ENIConfig 사용자 지정 리소스는 파드가 스케줄링될 서브넷을 정의하는 데 사용됩니다.

+
apiVersion : crd.k8s.amazonaws.com/v1alpha1
+kind : ENIConfig
+metadata:
+  name: us-west-2a
+spec: 
+  securityGroups:
+    - sg-0dff111a1d11c1c11
+  subnet: subnet-011b111c1f11fdf11
+
+

ENIconfig 사용자 지정 리소스를 생성할 때 새 워커 노드를 생성하고 기존 노드를 비워야 합니다. 기존 워커 노드와 파드는 영향을 받지 않습니다.

+

권장 사항

+

사용자 지정 네트워킹 이용을 권장하는 경우

+

IPv4가 고갈되고 있고 아직 IPv6를 사용할 수 없는 경우 사용자 지정 네트워킹을 고려하는 것이 좋습니다. Amazon EKS는 RFC6598 공간을 지원하므로 RFC1918 문제 소모 문제 이상으로 파드를 확장할 수 있습니다.사용자 지정 네트워킹과 함께 Prefix 위임을 사용하여 노드의 파드 밀도를 높이는 것을 고려해 보십시오.

+

보안 그룹 요구 사항이 다른 다른 네트워크에서 Pod를 실행해야 하는 보안 요구 사항이 있는 경우 사용자 지정 네트워킹을 고려할 수 있습니다. 사용자 지정 네트워킹이 활성화되면 파드는 Eniconfig에 정의된 대로 노드의 기본 네트워크 인터페이스와 다른 서브넷 또는 보안 그룹을 사용합니다.

+

사용자 지정 네트워킹은 여러 EKS 클러스터 및 애플리케이션을 배포하여 온프레미스 데이터 센터 서비스를 연결하는 데 가장 적합한 옵션입니다. Amazon Elastic Load Balancing 및 NAT-GW와 같은 서비스를 위해 VPC에서 EKS로 액세스할 수 있는 프라이빗 주소 (RFC1918) 의 수를 늘리는 동시에 여러 클러스터에서 파드에 라우팅할 수 없는 CG-NAT 공간을 사용할 수 있습니다. 트랜짓 게이트웨이 및 공유 서비스 VPC (고가용성을 위한 여러 가용영역에 걸친 NAT 게이트웨이 포함) 를 사용한 사용자 지정 네트워킹을 통해 확장 가능하고 예측 가능한 트래픽 흐름을 제공할 수 있습니다. 이 블로그 게시물 에서는 사용자 지정 네트워킹을 사용하여 EKS Pod를 데이터 센터 네트워크에 연결하는 데 가장 권장되는 방법 중 하나인 아키텍처 패턴을 설명합니다.

+

사용자 지정 네트워킹 이용을 권장하지 않는 경우

+

IPv6 구현 준비 완료

+

사용자 지정 네트워킹은 IP 고갈 문제를 완화할 수 있지만 추가 운영 오버헤드가 필요합니다. 현재 이중 스택 (IPv4/IPv6) VPC를 배포 중이거나 계획에 IPv6 지원이 포함된 경우 IPv6 클러스터를 대신 구현하는 것이 좋습니다. IPv6 EKS 클러스터를 설정하고 앱을 마이그레이션할 수 있습니다. IPv6 EKS 클러스터에서는 쿠버네티스와 파드 모두 IPv6 주소를 얻고 IPv4 및 IPv6 엔드포인트 모두와 송수신할 수 있습니다. IPv6 EKS 클러스터 실행에 대한 모범 사례를 검토하십시오.

+

고갈된 CG-NAT 공간

+

또한 현재 CG-NAT 공간의 CIDR을 사용하고 있거나 보조 CIDR을 클러스터 VPC와 연결할 수 없는 경우 대체 CNI 사용과 같은 다른 옵션을 탐색해야 할 수도 있습니다. 상용 지원을 받거나 사내 지식을 보유하여 오픈소스 CNI 플러그인 프로젝트를 디버깅하고 패치를 제출하는 것이 좋습니다. 자세한 내용은 대체 CNI 플러그인 사용 설명서를 참조하십시오.

+

프라이빗 NAT 게이트웨이 사용

+

Amazon VPC는 이제 프라이빗 NAT 게이트웨이 기능을 제공합니다. Amazon의 프라이빗 NAT 게이트웨이를 사용하면 프라이빗 서브넷의 인스턴스를 CIDR이 겹치는 다른 VPC 및 온프레미스 네트워크에 연결할 수 있습니다. 이 블로그 게시물에 설명된 방법을 활용하여 프라이빗 NAT 게이트웨이를 사용하여 CIDR 중복으로 인한 EKS 워크로드의 통신 문제를 해결하는 것을 고려해 보십시오. 이는 고객이 제기한 중대한 불만 사항입니다. 맞춤형 네트워킹만으로는 중복되는 CIDR 문제를 해결할 수 없으며 구성 문제가 가중됩니다.

+

이 블로그 게시물 구현에 사용된 네트워크 아키텍처는 Amazon VPC 설명서의 중복 네트워크 간 통신 활성화에 있는 권장 사항을 따릅니다. 이 블로그 게시물에서 설명한 것처럼 프라이빗 NAT 게이트웨이를 RFC6598 주소와 함께 사용하여 고객의 프라이빗 IP 고갈 문제를 관리할 수 있는 방법을 모색할 수 있습니다. EKS 클러스터, 워커 노드는 라우팅이 불가능한 100.64.0.0/16 VPC 보조 CIDR 범위에 배포되는 반면, 사설 NAT 게이트웨이인 NAT 게이트웨이는 라우팅 가능한 RFC1918 CIDR 범위에 배포됩니다. 이 블로그에서는 라우팅이 불가능한 CIDR 범위가 겹치는 VPC 간의 통신을 용이하게 하기 위해 트랜짓 게이트웨이를 사용하여 VPC를 연결하는 방법을 설명합니다. VPC의 라우팅 불가능한 주소 범위에 있는 EKS 리소스가 주소 범위가 겹치지 않는 다른 VPC와 통신해야 하는 사용 사례의 경우 고객은 VPC 피어링을 사용하여 이런 VPC를 상호 연결할 수 있습니다. 이제 VPC 피어링 연결을 통한 가용영역 내의 모든 데이터 전송이 무료이므로 이 방법을 사용하면 비용을 절감할 수 있습니다.

+

illustration of network traffic using private NAT gateway

+

노드 및 파드를 위한 고유한 네트워크

+

보안상의 이유로 노드와 파드를 특정 네트워크로 격리해야 하는 경우, 더 큰 보조 CIDR 블록 (예: 100.64.0.0/8) 의 서브넷에 노드와 파드를 배포하는 것이 좋습니다. VPC에 새 CIDR을 설치한 후에는 보조 CIDR을 사용하여 다른 노드 그룹을 배포하고 원래 노드를 드레인하여 파드를 새 워커 노드에 자동으로 재배포할 수 있습니다. 이를 구현하는 방법에 대한 자세한 내용은 이 블로그 게시물을 참조하십시오.

+

아래 다이어그램에 표시된 설정에서는 사용자 지정 네트워킹이 사용되지 않습니다. 대신 쿠버네티스 워커 노드는 VPC의 보조 VPC CIDR 범위 (예: 100.64.0.0/10) 에 속하는 서브넷에 배포됩니다. EKS 클러스터를 계속 실행할 수 있지만 (컨트롤 플레인은 원래 서브넷에 유지됨), 노드와 파드는 보조 서브넷으로 이동합니다. 이는 VPC에서 IP 고갈의 위험을 완화하기 위한 흔하지는 않지만 또 다른 기법입니다.새 워커 노드에 파드를 재배포하기 전에 기존 노드를 비우는 것이 좋습니다.

+

illustration of worker nodes on secondary subnet

+

가용영역 레이블을 사용한 구성 자동화

+

Kubernetes를 활성화하여 워커 노드 가용영역 (AZ) 에 해당하는 eniConfig를 자동으로 적용할 수 있습니다.

+

쿠버네티스는 워커 노드에 topology.kubernetes.io/zone 태그를 자동으로 추가합니다. Amazon EKS는 AZ당 보조 서브넷 (대체 CIDR) 이 하나뿐인 경우 가용영역을 ENI 구성 이름으로 사용할 것을 권장합니다. 참고로 failure-domain.beta.kubernetes.io/zone 태그는 더 이상 사용되지 않으며 topology.kubernetes.io/zone 태그로 대체되었습니다.

+
    +
  1. name 필드를 VPC의 가용영역으로 설정합니다.
  2. +
  3. 다음 명령을 사용하여 자동 구성을 활성화합니다.
  4. +
+
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true
+
+

가용영역당 보조 서브넷이 여러 개 있는 경우, 특정 ENI_CONFIG_LABEL_DEF를 생성해야 합니다. ENI_CONFIG_LABEL_DEFk8s.amazonaws.com/eniConfig로 구성하고 k8s.amazonaws.com/eniConfig=us-west-2a-subnet-1k8s.amazonaws.com/eniConfig=us-west-2a-subnet-2 같은 사용자 정의 eniConfig 이름으로 노드를 레이블링하는 것을 고려할 수 있습니다.

+

보조 네트워킹 구성 시 파드 교체

+

사용자 지정 네트워킹을 활성화해도 기존 노드는 수정되지 않습니다. 맞춤형 네트워킹은 파괴적인 조치입니다. 사용자 지정 네트워킹을 활성화한 후 클러스터의 모든 워커 노드를 순차적으로 교체하는 대신, 워커 노드가 프로비저닝되기 전에 사용자 지정 네트워킹이 가능하도록 Lambda 함수를 호출하는 사용자 지정 리소스로 EKS 시작 안내서의 AWS CloudFormation 템플릿을 업데이트하여 환경 변수로 aws-node 데몬셋을 업데이트하는 것이 좋습니다.

+

사용자 지정 CNI 네트워킹 기능으로 전환하기 전에 클러스터에 파드를 실행하는 노드가 있는 경우, 파드를 차단하고 드레이닝 하여 파드를 정상적으로 종료한 다음 노드를 종료해야 합니다. ENIConfig 레이블 또는 주석과 일치하는 새 노드만 사용자 지정 네트워킹을 사용하므로 이런 새 노드에 스케줄링된 파드에는 보조 CIDR의 IP를 할당받을 수 있습니다.

+

노드당 최대 파드 수 계산

+

노드의 기본 ENI는 더 이상 Pod IP 주소를 할당하는 데 사용되지 않으므로 특정 EC2 인스턴스 유형에서 실행할 수 있는 Pod 수가 감소합니다. 이 제한을 우회하기 위해 사용자 지정 네트워킹과 함께 Prefix 할당을 사용할 수 있습니다. Prefix를 할당하면 보조 ENI에서 각 보조 IP가 /28 Prefix로 대체됩니다.

+

사용자 지정 네트워킹이 있는 m5.large 인스턴스의 최대 파드 수를 고려해봅시다.

+

Prefix를 할당하지 않고 실행할 수 있는 최대 파드 수는 29개입니다.

+
    +
  • ((3 ENIs - 1) * (10 secondary IPs per ENI - 1)) + 2 = 20
  • +
+

프리픽스 어태치먼트를 활성화하면 파드의 수가 290개로 늘어납니다.

+
    +
  • (((3 ENIs - 1) * ((10 secondary IPs per ENI - 1) * 16)) + 2 = 290
  • +
+

하지만 인스턴스의 가상 CPU 수가 매우 적기 때문에 max-pod를 290이 아닌 110으로 설정하는 것이 좋습니다. 더 큰 인스턴스의 경우 EKS는 최대 파드 값을 250으로 설정할 것을 권장합니다. 더 작은 인스턴스 유형 (예: m5.large) 에 Prefix 첨부 파일을 사용할 경우 IP 주소보다 훨씬 먼저 인스턴스의 CPU 및 메모리 리소스가 고갈될 수 있습니다.

+
+

Info

+

CNI Prefix가 ENI에 /28 Prefix를 할당할 때는 연속된 IP 주소 블록이어야 합니다. Prefix가 생성되는 서브넷이 고도로 분할된 경우 Prefix 연결에 실패할 수 있습니다. 클러스터용 전용 VPC를 새로 만들거나 서브넷에 Prefix 첨부 전용으로 CIDR 세트를 예약하여 이런 문제가 발생하지 않도록 할 수 있습니다. 이 주제에 대한 자세한 내용은 서브넷 CIDR 예약을 참조하십시오.

+
+

CG-NAT 공간의 기존 사용 현황 파악

+

사용자 지정 네트워킹을 사용하면 IP 소모 문제를 완화할 수 있지만 모든 문제를 해결할 수는 없습니다. 클러스터에 이미 CG-NAT 공간을 사용하고 있거나 단순히 보조 CIDR을 클러스터 VPC에 연결할 수 없는 경우에는 대체 CNI를 사용하거나 IPv6 클러스터로 이동하는 등의 다른 옵션을 살펴보는 것이 좋습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/index/index.html b/ko/networking/index/index.html new file mode 100644 index 000000000..f3021ddf8 --- /dev/null +++ b/ko/networking/index/index.html @@ -0,0 +1,2213 @@ + + + + + + + + + + + + + + + + + + + + + + + 홈 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

네트워크를 위한 Amazon EKS 모범 사례 가이드

+

클러스터와 애플리케이션을 효율적으로 운영하려면 쿠버네티스 네트워킹를 이해하는 것이 중요합니다. 클러스터 네트워킹이라고도 하는 파드 네트워킹은 쿠버네티스 네트워킹의 중심입니다. 쿠버네티스는 클러스터 네트워킹을 위한 컨테이너 네트워크 인터페이스(CNI) 플러그인을 지원합니다.

+

Amazon EKS는 쿠버네티스 파드 네트워킹을 구현하는 Amazon Virtual Private Cloud(VPC) CNI 플러그인을 공식적으로 지원합니다. VPC CNI는 AWS VPC와의 네이티브 통합을 제공하며 언더레이(underlay) 모드에서 작동합니다. 언더레이 모드에서는 파드와 호스트가 동일한 네트워크 계층에 위치하며 네트워크 네임스페이스를 공유합니다. 파드의 IP 주소는 클러스터 및 VPC 관점에서 일관되게 구성됩니다.

+

이 가이드에서는 쿠버네티스 클러스터 네트워킹의 맥락에서 Amazon VPC 컨테이너 네트워크 인터페이스(VPC CNI)를 소개합니다. VPC CNI는 EKS에서 지원하는 기본 네트워킹 플러그인이므로 이 가이드에서 중점적으로 다루도록 하겠습니다. VPC CNI는 다양한 사용 사례를 지원하도록 구성할 수 있습니다. 또한 이 가이드에는 다양한 VPC CNI 사용 사례, 운영 모드, 하위 구성 요소에 대한 섹션과 권장 사항을 포함하고 있습니다.

+

Amazon EKS는 업스트림 쿠버네티스를 실행하며 쿠버네티스 적합성 인증을 받았습니다. 대체 CNI 플러그인을 사용할 수 있지만, 이 가이드에서는 대체 CNI 관리에 대한 권장 사항을 제공하지 않습니다. 대체 CNI를 효과적으로 관리하기 위한 파트너 및 리소스 목록은 EKS Alternate CNI 설명서를 참조합니다.

+

쿠버네티스 네트워킹 모델

+

쿠버네티스는 클러스터 네트워킹에 대해 다음과 같은 요구 사항을 정의했습니다.

+
    +
  • 동일한 노드에 스케줄링된 파드는 NAT(Network Address Translation)를 사용하지 않고 다른 파드와 통신할 수 있어야 합니다.
  • +
  • 특정 노드에서 실행되는 모든 시스템 데몬(백그라운드 프로세스, 예: kubelet)은 동일한 노드에서 실행되는 파드와 통신할 수 있어야 합니다.
  • +
  • 호스트 네트워크를 사용하는 파드는 NAT를 사용하지 않고 다른 모든 노드의 다른 모든 파드에 접근할 수 있어야 합니다.
  • +
+

쿠버네티스에서 요구하는 호환 가능한 네트워킹 구현에 대한 자세한 내용은 쿠버네티스 네트워크 모델을 참조합니다. 다음 그림은 파드 네트워크 네임스페이스와 호스트 네트워크 네임스페이스 간의 관계를 보여줍니다.

+

illustration of host network and 2 pod network namespaces

+

컨테이너 네트워킹 인터페이스 (CNI)

+

쿠버네티스는 쿠버네티스 네트워크 모델을 구현하기 위한 CNI 사양 및 플러그인을 지원합니다. CNI는 컨테이너에서 네트워크 인터페이스를 구성하기 위한 플러그인을 작성하기 위한 사양(현재 버전 1.0.0)과 라이브러리, 지원 가능한 여러 플러그인으로 구성됩니다. CNI는 컨테이너의 네트워크 연결과 컨테이너 삭제 시 할당된 리소스 제거에만 관여합니다.

+

CNI 플러그인은 kubelet에 --network-plugin=cni 명령줄 옵션을 전달함으로써 활성화됩니다. kubelet은 --cni-conf-dir (기본적으로 /etc/cni/net.d)에서 파일을 읽고 해당 파일의 CNI 구성을 활용하여 각 파드의 네트워크를 설정합니다. CNI 구성 파일은 CNI 사양 (최소 v0.4.0) 과 일치해야 하며 구성에서 참조하는 모든 필수 CNI 플러그인은 --cni-bin-dir 디렉터리(기본적으로 /opt/cni/bin)에 있어야 합니다. 디렉터리에 CNI 구성 파일이 여러 개 있는 경우, kubelet은 구성 파일을 오름차순 기준으로 앞에 오는 이름을 가진 파일을 사용합니다.

+

Amazon Virtual Private Cloud (VPC) CNI

+

AWS에서 제공하는 VPC CNI는 EKS 클러스터의 기본 네트워킹 애드온입니다. VPC CNI 애드온은 EKS 클러스터를 프로비저닝할 때 기본적으로 설치됩니다. VPC CNI는 쿠버네티스 워커 노드에서 실행됩니다. VPC CNI 애드온은 CNI 바이너리와 IP 주소 관리(ipamd) 플러그인으로 구성되어 있습니다. CNI는 VPC 네트워크의 IP 주소를 파드에 할당합니다. ipamd는 각 쿠버네티스 노드에 대한 AWS Elastic Networking Interface(ENI) 를 관리하고 IP 웜 풀을 유지합니다. VPC CNI는 빠른 파드 기동 시간을 위해 ENI와 IP 주소를 사전 할당하기 위한 구성 옵션을 제공합니다. Amazon VPC CNI에서 권장 플러그인 관리 모범 사례를 참조합니다.

+

Amazon EKS는 클러스터를 생성할 때 최소 두 개의 가용 영역에 서브넷을 지정할 것을 권장합니다. Amazon VPC CNI는 노드의 서브넷에서 파드 IP 주소를 할당합니다. 해당 서브넷에서 사용 가능한 IP 주소를 확인할 것을 강력하게 권장합니다. EKS 클러스터를 배포하기 전에 VPC 및 서브넷 권장사항을 고려합니다.

+

Amazon VPC CNI는 노드의 기본 ENI에 연결된 서브넷의 ENI와 보조 IP 주소로 구성된 웜 풀을 할당합니다. 이 VPC CNI 모드를 “보조 IP 모드(secondary IP mode)”라고 합니다. IP 주소 수와 이에 따른 파드 수(파드의 밀도)는 인스턴스 유형별로 정의된 ENI 및 ENI 당 IP 주소 수(제한)에 따라 정의됩니다. 보조 모드는 기본값이며 인스턴스 유형이 작은 소규모 클러스터에 적합합니다. 파드 밀도 문제가 발생하는 경우, Prefix 모드사용을 고려합니다. ENI에 Prefix를 할당하여 파드용 노드에서 사용 가능한 IP 주소를 늘릴 수 있습니다.

+

Amazon VPC CNI는 기본적으로 AWS VPC와 통합되며, 이를 통해 사용자는 기존 AWS VPC 네트워킹 및 보안 모범 사례를 적용하여 쿠버네티스 클러스터를 구축할 수 있습니다. 여기에는 VPC flow logs, VPC 라우팅 정책 및 네트워크 트래픽 격리를 위한 보안 그룹을 사용할 수 있는 기능이 포함됩니다. 기본적으로 Amazon VPC CNI는 노드의 기본 ENI와 연결된 보안 그룹을 파드에 적용합니다. 파드에 별도의 네트워크 규칙을 할당하고 싶은 경우 파드용 보안 그룹 활성화를 고려합니다.

+

기본적으로 VPC CNI는 노드의 기본 ENI에 할당된 서브넷의 IP 주소를 Pod에 할당합니다. 수천 개의 워크로드가 있는 대규모 클러스터를 실행할 경우에는 IPv4 주소 부족이 발생하는 것이 일반적입니다. AWS VPC를 사용하면 보조 CIDR 할당을 통해 사용 가능한 IP를 확장하여 IPv4 CIDR 블록 고갈을 해결할 수 있습니다. AWS VPC CNI를 사용하여 파드에 대해 다른 서브넷 CIDR 범위를 사용할 수 있습니다. VPC CNI의 이러한 기능을 사용자 지정 네트워킹이라고 합니다. EKS에서 100.64.0.0/10 및 198.19.0.0/16 CIDR(CG-NAT)을 함께 사용하려면 사용자 지정 네트워킹을 사용하는 것을 고려해 볼 수 있습니다. 이를 통해 파드가 VPC의 RFC1918 IP 주소를 사용하지 않을 경우의 환경을 효과적으로 구성할 수 있습니다.

+

사용자 지정 네트워킹은 IPv4 주소 고갈 문제를 해결하는 한 가지 방법이지만 운영 오버헤드가 발생합니다. 이러한 문제를 해결하려면 사용자 지정 네트워킹보다 IPv6 클러스터를 사용하는 것이 좋습니다. 특히, VPC에서 사용 가능한 IPv4 주소를 모두 소진한 경우 IPv6 클러스터로 마이그레이션할 것을 권장합니다. IPv6의 지원을 위한 조직에서의 계획을 확인하고, IPv6에 투자하는 것이 장기적 가치가 더 높을지 고려합니다.

+

EKS의 IPv6 지원은 제한된 IPv4 주소 공간으로 인해 발생하는 IP 고갈 문제를 해결하는 데 중점을 두고 있습니다. IPv4 고갈로 인한 고객들의 문제에 대응하여 EKS는 듀얼 스택 파드보다 IPv6 전용 파드의 우선순위를 높입니다. 즉, 파드는 IPv4 리소스에 액세스할 수 있지만 VPC CIDR 범위의 IPv4 주소는 할당되지 않습니다. VPC CNI는 AWS 관리형 VPC IPv6 CIDR 블록에서 파드에 IPv6 주소를 할당합니다.

+

서브넷 계산기

+

이 프로젝트에는 서브넷 계산기 Excel 문서가 포함되어 있습니다. 이 계산기 문서는 WARM_IP_TARGETWARM_ENI_TARGET과 같은 다양한 ENI 구성 옵션에 따라 지정된 워크로드의 IP 주소 사용을 시뮬레이션합니다. 이 문서에는 두 개의 시트가 포함되어 있습니다. 첫 번째 시트는 웜 ENI 모드용이고 다른 하나는 웜 IP 모드용입니다. 이러한 모드에 대한 자세한 내용은 VPC CNI guidance를 참조합니다.

+

입력 값: +- 서브넷 CIDR 크기 +- 웜 ENI 타겟 또는 웜 IP 타겟 +- 인스턴스 목록 + - 유형, 개수 및 인스턴스당 스케줄링된 워크로드 파드의 수

+

출력 값: +- 호스팅된 총 파드 수 +- 사용된 서브넷 IP 수 +- 남아 있는 서브넷 IP 수 +- 인스턴스 수준 세부 정보 + - 인스턴스당 웜 IP/ENI 수 + - 인스턴스당 활성 IP/ENI 수

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/ip-optimization-strategies/index.html b/ko/networking/ip-optimization-strategies/index.html new file mode 100644 index 000000000..cb87b9e89 --- /dev/null +++ b/ko/networking/ip-optimization-strategies/index.html @@ -0,0 +1,2362 @@ + + + + + + + + + + + + + + + + + + + + + + + IP 주소 사용 최적화 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

IP 주소 사용 최적화

+

애플리케이션 현대화로 인하여 컨테이너화된 환경의 규모가 빠른 속도로 증가하고 있습니다. 이는 점점 더 많은 워커 노드와 파드가 배포되고 있음을 의미합니다.

+

아마존 VPC CNI 플러그인은 각 파드에 VPC CIDR의 IP 주소를 할당합니다. 이 접근 방식은 VPC flow logs 및 기타 모니터링 솔루션과 같은 도구를 사용하여 파드 주소를 완벽하게 파악할 수 있도록 합니다. 이로 인해 워크로드 유형에 따라 파드에서 상당한 수의 IP 주소를 사용할 수 있습니다.

+

AWS 네트워킹 아키텍처를 설계할 때는 VPC와 노드 수준에서 Amazon EKS IP 사용을 최적화하는 것이 중요합니다. 이렇게 하면 IP 고갈 문제를 완화하고 노드당 파드 밀도를 높이는 데 도움이 됩니다.

+

이 섹션에서는 이러한 목표를 달성하는 데 도움이 될 수 있는 기술에 대해 설명합니다.

+

노드 레벨 IP 소비 최적화

+

접두사(Prefix) 위임 은 Amazon Virtual Private Cloud(Amazon VPC) 의 기능으로, 이를 통해 Amazon Elastic Compute Cloud (Amazon EC2) 인스턴스에 IPv4 또는 IPv6 접두사를 할당할 수 있습니다. 네트워크 인터페이스당 IP 주소(ENI)가 증가하여 노드당 파드 밀도가 증가하고 컴퓨팅 효율성이 향상됩니다. 사용자 지정 네트워킹에서는 접두사 위임도 지원됩니다.

+

자세한 내용은 Linux 노드를 사용한 접두사 위임윈도우 노드를 사용한 프리픽스 위임 섹션을 참조하십시오.

+

IP 소진 완화

+

클러스터가 사용 가능한 IP 주소를 모두 사용하지 않도록 하려면 성장을 염두에 두고 VPC와 서브넷의 크기를 조정하는 것이 좋습니다.

+

IPv6 채택은 이러한 문제를 처음부터 방지할 수 있는 좋은 방법입니다. 그러나 확장성 요구 사항이 초기 계획을 초과하여 IPv6을 채택할 수 없는 조직의 경우 IP 주소 고갈에 대한 대응책으로 VPC 설계를 개선하는 것이 좋습니다. Amazon EKS 고객이 가장 일반적으로 사용하는 방법은 라우팅이 불가능한 보조 CIDR을 VPC에 추가하고 IP 주소를 Pod에 할당할 때 이 추가 IP 공간을 사용하도록 VPC CNI를 구성하는 것입니다. 이를 일반적으로 사용자 지정 네트워킹이라고 합니다.

+

노드에 할당된 IP 웜 풀을 최적화하는 데 사용할 수 있는 Amazon VPC CNI 변수에 대해 알아보겠습니다. Amazon EKS에 고유하지는 않지만 IP 고갈을 완화하는 데 도움이 될 수 있는 몇 가지 다른 아키텍처 패턴에 대해 설명하면서 이 섹션을 마치겠습니다.

+

IPv6 사용 (권장사항)

+

IPv6을 채택하는 것이 RFC1918 제한을 해결하는 가장 쉬운 방법입니다. 네트워크 아키텍처를 선택할 때는 IPv6을 첫 번째 옵션으로 채택하는 것이 좋습니다. IPv6은 총 IP 주소 공간이 훨씬 더 넓기 때문에 클러스터 관리자는 IPv4 제한을 우회하는 데 노력을 기울이지 않고도 애플리케이션을 마이그레이션하고 확장하는 데 집중할 수 있습니다.

+

Amazon EKS 클러스터는 IPv4와 IPv6을 모두 지원합니다. 기본적으로 EKS 클러스터는 IPv4 주소 공간을 사용합니다. 클러스터 생성 시 IPv6 기반 주소 공간을 지정하면 IPv6을 사용할 수 있습니다. IPv6 EKS 클러스터에서 파드와 서비스는 IPv6 주소를 수신하며, 레거시 IPv4 엔드포인트를 IPv6 클러스터에서 실행되는 서비스에 연결하는 기능을 유지하며 그 반대의 경우도 마찬가지입니다. 클러스터 내의 모든 파드 간 통신은 항상 IPv6을 통해 이루어집니다. VPC(/56)내에서 IPv6 서브넷의 IPv6 CIDR 블록 크기는 /64로 고정됩니다.이는 2^64(약 18억)의 IPv6 주소를 제공하므로 EKS에서 배포를 확장할 수 있습니다.

+

자세한 내용은 IPv6 EKS 클러스터 실행 섹션을 참조하십시오. 핸즈온 실습은 IPv6 실습 워크숍 내에 Amazon EKS에서의 IPv6에 대한 이해 섹션을 참조하십시오.

+

EKS Cluster in IPv6 Mode, traffic flow

+

IPv4 클러스터의 IP 사용 최적화

+

이 섹션은 기존 애플리케이션을 실행 중이거나 IPv6으로 마이그레이션할 준비가 되지 않은 고객을 대상으로 합니다. 모든 조직이 가능한 한 빨리 IPv6으로 마이그레이션하도록 권장하고 있지만 일부 조직에서는 IPv4로 컨테이너 워크로드를 확장하기 위한 대체 접근 방식을 모색해야 할 수도 있다는 점을 알고 있습니다. 이러한 이유로 Amazon EKS 클러스터를 사용하여 IPv4 (RFC1918) 주소 공간 소비를 최적화하는 아키텍처 패턴도 소개합니다.

+

확장을 위한 계획

+

IP 고갈에 대한 첫 번째 방어선으로서 클러스터가 사용 가능한 IP 주소를 모두 소비하지 않도록 성장을 염두에 두고 IPv4 VPC와 서브넷의 크기를 조정하는 것이 좋습니다. 서브넷에 사용 가능한 IP 주소가 충분하지 않으면 새 파드나 노드를 생성할 수 없습니다.

+

VPC와 서브넷을 구축하기 전에 필요한 워크로드 규모에서 역방향으로 작업하는 것이 좋습니다.예를 들어 eksctl(EKS에서 클러스터를 생성하고 관리하기 위한 간단한 CLI 도구)를 사용하여 클러스터를 구축하면 기본적으로 19개의 서브넷이 생성됩니다. /19의 넷마스크는 8000개 이상의 주소를 할당할 수 있는 대부분의 워크로드 유형에 적합합니다.

+
+

Attention

+

VPC와 서브넷의 크기를 조정할 때 IP 주소를 소비할 수 있는 요소 (예: 로드 밸런서, RDS 데이터베이스 및 기타 vpc 내 서비스) 가 여러 개 있을 수 있습니다.

+
+

또한 Amazon EKS는 컨트롤 플레인과의 통신을 허용하는 데 필요한 최대 4개의 엘라스틱 네트워크 인터페이스(X-ENI)를 생성할 수 있습니다. (자세한 내용은 다음 문서를 참고하세요.) 클러스터 업그레이드 중에 Amazon EKS는 새 X-ENI를 생성하고 업그레이드가 성공하면 이전 X-ENI를 삭제합니다. 따라서 EKS 클러스터와 연결된 서브넷의 경우 최소 /28 (16개의 IP 주소)의 넷마스크를 사용하는 것이 좋습니다.

+

샘플 EKS 서브넷 계산기 스프레드시트를 사용하여 네트워크를 계획할 수 있습니다. 스프레드시트는 워크로드 및 VPC ENI 구성을 기반으로 IP 사용량을 계산합니다. IP 사용량을 IPv4 서브넷과 비교하여 구성 및 서브넷 크기가 워크로드에 충분한지 확인합니다. VPC의 서브넷에 사용 가능한 IP 주소가 부족할 경우 VPC의 원래 CIDR 블록을 사용하여 새 서브넷을 생성하는 것이 좋습니다. 이제 Amazon EKS에서 클러스터 서브넷 및 보안 그룹을 수정할 수 있는 점에 유의하세요.

+

IP 공간 확장

+

RFC1918 IP 공간을 거의 사용한 경우 커스텀 네트워킹 패턴을 사용하여 전용 추가 서브넷 내에서 파드를 스케줄링하여 라우팅 가능한 IP를 절약할 수 있습니다. +커스텀 네트워킹은 추가 CIDR 범위에 모든 유효한 VPC 범위를 허용하지만, CG-NAT (Carrier-Grade NAT, RFC 6598) 공간의 CIDR (/16)을 사용하는 것을 추천합니다. (예: 100.64.0.0/10 또는 198.19.0.0/16) 이는 RFC1918 범위보다 기업 환경에서 사용될 가능성이 적기 때문입니다.

+

자세한 내용은 Custom 네트워킹 문서를 참조하십시오.

+

Custom Networking, traffic flow

+

IP 웜 풀 최적화

+

기본 구성을 사용하면 VPC CNI가 전체 ENI(및 관련 IP)를 웜 풀에 보관합니다. 이로 인해 특히 대규모 인스턴스 유형에서 많은 IP가 소모될 수 있습니다.

+

클러스터 서브넷의 사용 가능한 IP 주소 수가 제한되어 있는 경우 다음 VPC CNI 구성 환경 변수를 자세히 살펴보십시오:

+
    +
  • WARM_IP_TARGET
  • +
  • MINIMUM_IP_TARGET
  • +
  • WARM_ENI_TARGET
  • +
+

노드에서 실행할 것으로 예상되는 파드의 수와 거의 일치하도록 MINIMUM_IP_TARGET 값을 구성할 수 있습니다. 이렇게 하면 파드가 생성되고 CNI는 EC2 API를 호출하지 않고도 웜 풀에서 IP 주소를 할당할 수 있습니다.

+

WARM_IP_TARGET 값을 너무 낮게 설정하면 EC2 API에 대한 추가 호출이 발생하고 이로 인해 요청이 병목 현상이 발생할 수 있다는 점에 유의하시기 바랍니다. 대규모 클러스터의 경우 요청의 병목 현상을 피하려면 MINIMUM_IP_TARGET과 함께 사용하십시오.

+

이러한 옵션을 구성하려면 aws-k8s-cni.yaml 매니페스트를 다운로드하고 환경 변수를 설정하면 됩니다. 이 글을 쓰는 시점에서 최신 릴리스는 다음 링크를 참조하세요. 구성 값의 버전이 설치된 VPC CNI 버전과 일치하는지 확인하세요.

+
+

Warning

+

CNI를 업데이트하면 이러한 설정이 기본값으로 재설정됩니다. 업데이트하기 전에 CNI를 백업해 두세요. 구성 설정을 검토하여 업데이트가 성공한 후 다시 적용해야 하는지 결정하십시오.

+
+

기존 애플리케이션을 다운타임 없이 즉시 CNI 파라미터를 조정할 수 있지만 확장성 요구 사항을 지원하는 값을 선택해야 합니다. 예를 들어, 배치 워크로드로 작업하는 경우 파드 스케일 요구 사항에 맞게 기본 WARM_ENI_TARGET으로 업데이트하는 것이 좋다. WARM_ENI_TARGET을 높은 값으로 설정하면 대규모 배치 워크로드를 실행하는 데 필요한 웜 IP 풀을 항상 유지하므로 데이터 처리 지연을 피할 수 있습니다.

+
+

Warning

+

IP 주소 고갈에 대한 대응책은 VPC 설계를 개선하는 것이 좋습니다. IPv6 및 보조 CIDR과 같은 솔루션을 고려해 보십시오. 다른 옵션을 제외한 후에는 이러한 값을 조정하여 웜 IP 수를 최소화하는 것이 일시적인 해결책이 될 수 있습니다. 이러한 값을 잘못 구성하면 클러스터 작동에 방해가 될 수 있습니다.

+

프로덕션 시스템을 변경하기 전에 이 페이지의 고려 사항을 반드시 검토하십시오.

+
+

IP 주소 인벤토리 모니터링

+

위에서 설명한 솔루션 외에도 IP 활용도를 파악하는 것도 중요합니다. CNI 메트릭 헬퍼를 사용하여 서브넷의 IP 주소 인벤토리를 모니터링할 수 있습니다. 사용 가능한 일부 메트릭은 다음과 같습니다.

+
    +
  • 클러스터가 지원할 수 있는 최대 ENI 수
  • +
  • 이미 할당된 ENI 수
  • +
  • 현재 파드에 할당된 IP 주소 수
  • +
  • 사용 가능한 전체 및 최대 IP 주소 수
  • +
+

서브넷의 IP 주소가 부족할 경우 알림을 받도록 CloudWatch 알람을 설정할 수도 있습니다. CNI 메트릭 헬퍼의 설치 지침은 EKS 사용 설명서를 참조하십시오.

+
+

Warning

+

VPC CNI의 DISABLE_METRICS 변수가 false로 설정되어 있는지 확인하십시오.

+
+

추가 고려 사항

+

Amazon EKS에 고유하지 않은 다른 아키텍처 패턴도 IP 고갈에 도움이 될 수 있습니다. 예를 들어 VPC 간 통신을 최적화하거나 여러 계정에서 VPC 공유를 사용하여 IPv4 주소 할당을 제한합니다.

+

여기에서 이러한 패턴에 대해 자세히 알아보세요.

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/ipv6/index.html b/ko/networking/ipv6/index.html new file mode 100644 index 000000000..e7f8961b8 --- /dev/null +++ b/ko/networking/ipv6/index.html @@ -0,0 +1,2357 @@ + + + + + + + + + + + + + + + + + + + + + + + IPv6 클러스터 운영 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

IPv6 EKS 클러스터 실행

+ + +

IPv6 모드의 EKS는 대규모 EKS 클러스터에서 자주 나타나는 IPv4 고갈 문제를 해결합니다. EKS의 IPv6 지원은 IPv4 주소 공간의 제한된 크기로 인해 발생하는 IPv4 고갈 문제를 해결하는 데 중점을 두고 있습니다. 이는 많은 고객이 제기한 중요한 우려 사항이며 Kubernetes의 "IPv4/IPv6 이중 스택" 기능과는 다릅니다.

+

또한 EKS/IPv6는 IPv6 CIDR을 사용하여 네트워크 경계를 상호 연결할 수 있는 유연성을 제공하므로 CIDR 중복으로 발생할 가능성을 최소화하여 2중 문제(클러스터 내, 클러스터 간)를 해결합니다.

+

IPv6 EKS 클러스터에서 파드와 서비스는 레거시 IPv4 엔드포인트와의 호환성을 유지하면서 IPv6 주소를 수신합니다. 여기에는 외부 IPv4 엔드포인트가 클러스터 내 서비스에 액세스하는 기능과 파드가 외부 IPv4 엔드포인트에 액세스하는 기능이 포함됩니다.

+

Amazon EKS IPv6 지원은 네이티브 VPC IPv6 기능을 활용합니다. 각 VPC에는 IPv4 주소 Prefix (CIDR 블록 크기는 /16에서 /28까지 가능) 및 아마존 GUA (글로벌 유니캐스트 주소) 내의 고유한 /56 IPv6 주소 Prefix (고정) 가 할당됩니다. VPC의 각 서브넷에 /64 주소 Prefix를 할당할 수 있습니다. 라우팅 테이블, 네트워크 액세스 제어 목록, 피어링, DNS 확인과 같은 IPv4 기능은 IPv6 지원 VPC에서 동일한 방식으로 작동합니다. VPC를 이중 스택 VPC라고 하며 이중 스택 서브넷에 이어 다음 다이어그램은 EK/IPv6 기반 클러스터를 지원하는 IPv4&IPv6 VPC 기반 패턴을 보여줍니다.

+

Dual Stack VPC, mandatory foundation for EKS cluster in IPv6 mode

+

IPv6 환경에서는 모든 주소를 인터넷으로 라우팅할 수 있습니다. 기본적으로 VPC는 퍼블릭 GUA 범위에서 IPv6 CIDR을 할당합니다. VPC는 RFC 4193 (fd00: :/8 또는 fc00: :/8) 에 정의된 고유 로컬 주소 (ULA) 범위에서 프라이빗 IPv6 주소를 할당하는 것을 지원하지 않습니다. 이는 사용자가 소유한 IPv6 CIDR을 할당하려는 경우에도 마찬가지입니다. VPC에 외부 전용 인터넷 게이트웨이 (EIGW) 를 구현하여 들어오는 트래픽은 모두 차단하면서 아웃바운드 트래픽은 허용함으로써 프라이빗 서브넷에서 인터넷으로 나가는 것을 지원합니다. +다음 다이어그램은 EKS/IPv6 클러스터 내의 Pod IPv6 인터넷 송신 흐름을 보여줍니다.

+

Dual Stack VPC, EKS Cluster in IPv6 Mode, Pods in private subnets egressing to Internet IPv6 endpoints

+

IPv6 서브넷을 구현하는 모범 사례는 VPC 사용 설명서에서 확인할 수 있습니다.

+

IPv6 EKS 클러스터에서 노드와 파드는 퍼블릭 IPv6 주소를 갖습니다. EKS는 고유한 로컬 IPv6 유니캐스트 주소 (ULA) 를 기반으로 서비스에 IPv6 주소를 할당합니다. IPv6 클러스터의 ULA 서비스 CIDR은 IPv4와 달리 클러스터 생성 단계에서 자동으로 할당되며 지정할 수 없습니다. 다음 다이어그램은 EKS/IPv6 기반 클러스터 컨트롤 플레인 및 데이터 플랜 기반 패턴을 보여줍니다.

+

Dual Stack VPC, EKS Cluster in IPv6 Mode, control plane ULA, data plane IPv6 GUA for EC2 & Pods

+

개요

+

EKS/IPv6는 Prefix 모드 (VPC-CNI 플러그인 ENI IP 할당 모드) 에서만 지원됩니다. Prefix 모드에 대해 자세히 알아보십시오.

+
+

Prefix 할당은 Nitro 기반 EC2 인스턴스에서만 작동하므로 EKS/IPv6는 클러스터 데이터 플레인이 EC2 Nitro 기반 인스턴스를 사용하는 경우에만 지원됩니다.

+
+

간단히 말해서 IPv6 Prefix가 /80 (워커 노드당) 이면 최대 10^14 IPv6 주소가 생성되며, 제한 요소는 더 이상 IP가 아니라 파드의 밀도입니다 (참고 자료 기준).

+

IPv6 Prefix 할당은 EKS 워커 노드 부트스트랩 시에만 발생합니다. +이 동작은 프라이빗 IPv4 주소를 적시에 할당하기 위한 VPC CNI 플러그인 (ipamd) 에서 생성되는 API 호출 속도 제한으로 인해 파드 스케줄링에서 파드 이탈이 심한 EKS/IPv4 클러스터가 종종 지연되는 시나리오를 완화하는 것으로 알려져 있습니다. 또한 VPC-CNI 플러그인 고급 설정 튜닝 WARM_IP/ENI, MINIMUM_IP을 불필요하게 만드는 것으로 알려져 있습니다.

+

다음 다이어그램은 IPv6 워커 노드 ENI (엘라스틱 네트워크 인터페이스) 를 확대한 것입니다.

+

illustration of worker subnet, including primary ENI with multiple IPv6 Addresses

+

모든 EKS 워커 노드에는 해당 DNS 항목과 함께 IPv4 및 IPv6 주소가 할당됩니다. 특정 워커 노드의 경우 이중 스택 서브넷의 단일 IPv4 주소만 사용됩니다. IPv6에 대한 EKS 지원을 통해 독보적인 외부 전용 IPv4 모델을 통해 IPv4 엔드포인트 (AWS, 온프레미스, 인터넷) 와 통신할 수 있습니다. EKS는 Pod에 IPv4 주소를 할당하고 구성하는 VPC CNI 플러그인의 보조 기능인 호스트-로컬 CNI 플러그인을 구현합니다. CNI 플러그인은 169.254.172.0/22 범위의 파드에 대해 호스트별로 라우팅할 수 없는 IPv4 주소를 할당합니다. 파드에 할당된 IPv4 주소는 워커 노드에 고유한 것으로 워커 노드 이외의 다른 주소는 알려지지 않습니다. 169.254.172.0/22는 대규모 인스턴스 유형을 지원할 수 있는 최대 1024개의 고유한 IPv4 주소를 제공합니다.

+

다음 다이어그램은 클러스터 경계 외부 (인터넷 아님) 에 있는 IPv4 엔드포인트에 연결하는 IPv6 Pod의 흐름을 보여줍니다.

+

EKS/IPv6, IPv4 egress-only flow

+

위 다이어그램에서 파드는 엔드포인트에 대한 DNS 조회를 수행하고, IPv4 "A" 응답을 받으면 파드의 노드 전용 고유 IPv4 주소가 소스 네트워크 주소 변환 (SNAT) 을 통해 EC2 워커 노드에 연결된 기본 네트워크 인터페이스의 프라이빗 IPv4 (VPC) 주소로 변환됩니다.

+

또한 EK/IPv6 파드는 퍼블릭 IPv4 주소를 사용하여 인터넷을 통해 IPv4 엔드포인트에 연결해야 비슷한 흐름이 존재할 수 있습니다. +다음 다이어그램은 클러스터 경계 외부의 IPv4 엔드포인트에 연결하는 IPv6 Pod의 흐름을 보여줍니다 (인터넷 라우팅 가능).

+

EKS/IPv6, IPv4 Internet egress-only flow

+

위 다이어그램에서 파드는 엔드포인트에 대한 DNS 조회를 수행하고, IPv4 "A" 응답을 받으면 파드의 노드 전용 고유 IPv4 주소는 소스 네트워크 주소 변환 (SNAT) 을 통해 EC2 워커 노드에 연결된 기본 네트워크 인터페이스의 프라이빗 IPv4 (VPC) 주소로 변환됩니다. 그런 다음 파드 IPv4 주소 (소스 IPv4: EC2 기본 IP) 는 IPv4 NAT 게이트웨이로 라우팅되며, 여기서 EC2 기본 IP는 유효한 인터넷 라우팅 가능한 IPv4 퍼블릭 IP 주소 (NAT 게이트웨이 할당 퍼블릭 IP) 로 변환됩니다.

+

노드 간의 모든 파드 간 통신은 항상 IPv6 주소를 사용합니다. VPC CNI는 IPv4 연결을 차단하면서 IPv6를 처리하도록 iptables를 구성합니다.

+

쿠버네티스 서비스는 고유한 로컬 IPv6 유니캐스트 주소 (ULA) 에서 IPv6 주소 (클러스터IP) 만 수신합니다. IPv6 클러스터용 ULA 서비스 CIDR은 EKS 클러스터 생성 단계에서 자동으로 할당되며 수정할 수 없습니다. 다음 다이어그램은 파드에서 쿠버네티스 서비스로의 흐름을 나타냅니다.

+

EKS/IPv6, IPv6 Pod to IPv6 k8s service (ClusterIP ULA) flow

+

서비스는 AWS 로드밸런서를 사용하여 인터넷에 노출됩니다. 로드밸런서는 퍼블릭 IPv4 및 IPv6 주소, 즉 듀얼 스택 로드밸런서를 수신합니다. IPv6 클러스터 쿠버네티스 서비스에 액세스하는 IPv4 클라이언트의 경우 로드밸런서는 IPv4에서 IPv6으로의 변환을 수행합니다.

+

Amazon EKS는 프라이빗 서브넷에서 워커 노드와 파드를 실행할 것을 권장합니다. 퍼블릭 서브넷에 퍼블릭 로드밸런서를 생성하여 프라이빗 서브넷에 있는 노드에서 실행되는 파드로 트래픽을 로드 밸런싱할 수 있습니다. +다음 다이어그램은 EKS/IPv6 인그레스 기반 서비스에 액세스하는 인터넷 IPv4 사용자를 보여줍니다.

+

인터넷 IPv4 사용자를 EKS/IPv6 인그레스 서비스로

+
+

참고: 위 패턴을 사용하려면 AWS 로드밸런서 컨트롤러의 최신 버전을 배포해야 합니다.

+
+

EKS 컨트롤 플레인 <-> 데이터 플레인 통신

+

EKS는 듀얼 스택 모드 (IPv4/IPv6) 에서 크로스 어카운트 ENI (X-eni) 를 프로비저닝할 예정입니다. kubelet 및 kube-proxy와 같은 쿠버네티스 노드 구성 요소는 이중 스택을 지원하도록 구성되어 있습니다. Kubelet과 kube-proxy는 호스트네트워크 모드에서 실행되며 노드의 기본 네트워크 인터페이스에 연결된 IPv4 및 IPv6 주소 모두에 바인딩됩니다. 쿠버네티스 API 서버는 IPv6 기반인 X-ENI를 통해 파드 및 노드 컴포넌트와 통신합니다. 파드는 X-ENI를 통해 API 서버와 통신하며, 파드와 API-서버 간 통신은 항상 IPv6 모드를 사용한다.

+

illustration of cluster including X-ENIs

+

권장 사항

+

IPv4 EKS API에 대한 액세스 유지

+

EKS API는 IPv4에서만 액세스할 수 있습니다. 여기에는 클러스터 API 엔드포인트도 포함됩니다. IPv6 전용 네트워크에서는 클러스터 엔드포인트와 API에 액세스할 수 없습니다. 네트워크는 (1) IPv6와 IPv4 호스트 간의 통신을 용이하게 하는 NAT64/DNS64와 같은 IPv6 전환 메커니즘과 (2) IPv4 엔드포인트의 변환을 지원하는 DNS 서비스를 지원해야 합니다.

+

컴퓨팅 리소스 기반 일정

+

단일 IPv6 Prefix는 단일 노드에서 많은 파드를 실행하기에 충분하다.또한 이는 노드의 최대 파드 수에 대한 ENI 및 IP 제한을 효과적으로 제거합니다. IPv6는 최대 POD에 대한 직접적인 종속성을 제거하지만 m5.large와 같은 작은 인스턴스 유형에 Prefix 첨부 파일을 사용하면 IP 주소를 모두 사용하기 훨씬 전에 인스턴스의 CPU와 메모리 리소스가 고갈될 수 있습니다. 자체 관리형 노드 그룹 또는 사용자 지정 AMI ID가 있는 관리형 노드 그룹을 사용하는 경우 EKS 권장 최대 파드 값을 직접 설정해야 합니다.

+

다음 공식을 사용하여 IPv6 EKS 클러스터의 노드에 배포할 수 있는 최대 파드 수를 결정할 수 있습니다.

+
    +
  • +

    ((인스턴스 유형별 네트워크 인터페이스 수 (네트워크 인터페이스당 Prefix 수-1)* 16) + 2

    +
  • +
  • +

    ((3 ENIs)((10 secondary IPs per ENI-1) 16)) + 2 = 460 (real)

    +
  • +
+

관리형 노드 그룹은 자동으로 최대 파드 수를 계산합니다. 리소스 제한으로 인한 파드 스케줄링 실패를 방지하려면 최대 파드 수에 대한 EKS 권장값을 변경하지 마세요.

+

기존의 사용자 지정 네트워킹의 목적 평가

+

사용자 지정 네트워킹이 현재 활성화되어 있는 경우 Amazon EKS는 IPv6에서 해당 네트워킹 요구 사항을 재평가할 것을 권장합니다. IPv4 고갈 문제를 해결하기 위해 사용자 지정 네트워킹을 사용하기로 선택한 경우 IPv6에서는 더 이상 사용자 지정 네트워킹을 사용할 필요가 없습니다. 사용자 지정 네트워킹을 사용하여 보안 요구 사항(예: 노드와 파드를 위한 별도의 네트워크)을 충족하는 경우 EKS 로드맵 요청을 제출하는 것이 좋습니다.

+

EKS/IPv6 클러스터의 파게이트 파드

+

EKS는 파게이트에서 실행되는 파드용 IPv6를 지원합니다. Fargate에서 실행되는 파드는 VPC CIDR 범위 (IPv4&IPv6) 에서 분할된 IPv6 및 VPC 라우팅 가능한 프라이빗 IPv4 주소를 사용합니다. 간단히 말해서 EKS/Fargate 파드 클러스터 전체 밀도는 사용 가능한 IPv4 및 IPv6 주소로 제한됩니다. 향후 성장에 대비하여 듀얼 스택 서브넷/vPC CIDR의 크기를 조정하는 것이 좋습니다. 기본 서브넷에 사용 가능한 IPv4 주소가 없으면 IPv6 사용 가능 주소와 상관없이 새 Fargate Pod를 예약할 수 없습니다.

+

AWS 로드밸런서 컨트롤러 (LBC) 배포

+

업스트림 인트리 쿠버네티스 서비스 컨트롤러는 IPv6을 지원하지 않습니다. AWS 로드밸런서 컨트롤러 애드온의 최신 버전을 사용하는 것이 좋습니다.LBC는 "alb.ingress.kubernetes.io/ip-address type: dualstack" 및 `"alb.ingress.kubernetes.io/target-type: ip""라는 주석이 달린 해당 쿠버네티스 서비스/인그레스 정의를 사용하는 경우에만 이중 스택 NLB 또는 이중 스택 ALB를 배포합니다.

+

AWS 네트워크 로드밸런서는 듀얼 스택 UDP 프로토콜 주소 유형을 지원하지 않습니다. 지연 시간이 짧은 실시간 스트리밍, 온라인 게임 및 IoT에 대한 강력한 요구 사항이 있는 경우 IPv4 클러스터를 실행하는 것이 좋습니다.UDP 서비스의 상태 점검 관리에 대한 자세한 내용은 "UDP 트래픽을 쿠버네티스로 라우팅하는 방법"을 참조하십시오.

+

IMDSv2에 대한 의존성

+

IPv6 모드의 EKS는 아직 IMDSv2 엔드포인트를 지원하지 않습니다. IMDSv2가 EKS/IPv6으로 마이그레이션하는 데 방해가 되는 경우 지원 티켓을 여십시오.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/ipvs/index.html b/ko/networking/ipvs/index.html new file mode 100644 index 000000000..1b794886a --- /dev/null +++ b/ko/networking/ipvs/index.html @@ -0,0 +1,2281 @@ + + + + + + + + + + + + + + + + + + + + + + + IPVS 모드에서 kube-proxy 실행 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

IPVS 모드에서 kube-proxy 실행

+

IPVS (IP 가상 서버) 모드의 EKS는 레거시 iptables 모드에서 실행되는 kube-proxy와 함께 1,000개 이상의 서비스가 포함된 대규모 클러스터를 실행할 때 흔히 발생하는 네트워크 지연 문제를 해결합니다.이러한 성능 문제는 각 패킷에 대한 iptables 패킷 필터링 규칙을 순차적으로 처리한 결과입니다.이 지연 문제는 iptables의 후속 버전인 nftables에서 해결되었습니다.하지만 이 글을 쓰는 시점 현재, nftable을 활용하기 위한 [kube-proxy는 아직 개발 중] (https://kubernetes.io/docs/reference/networking/virtual-ips/#proxy-mode-nftables) 이다.이 문제를 해결하려면 IPVS 모드에서 kube-proxy가 실행되도록 클러스터를 구성할 수 있다.

+

개요

+

쿠버네티스 버전 1.11 부터 GA가 된 IPVS는 선형 검색이 아닌 해시 테이블을 사용하여 패킷을 처리하므로 수천 개의 노드와 서비스가 있는 클러스터에 효율성을 제공합니다.IPVS는 로드 밸런싱을 위해 설계되었으므로 쿠버네티스 네트워킹 성능 문제에 적합한 솔루션입니다.

+

IPVS는 트래픽을 백엔드 포드에 분산하기 위한 몇 가지 옵션을 제공합니다.각 옵션에 대한 자세한 내용은 공식 쿠버네티스 문서 에서 확인할 수 있지만, 간단한 목록은 아래에 나와 있다.라운드 로빈과 최소 연결은 쿠버네티스의 IPVS 로드 밸런싱 옵션으로 가장 많이 사용되는 옵션 중 하나입니다. +

- rr (라운드 로빈)
+- wrr (웨이티드 라운드 로빈)
+- lc (최소 연결)
+- wlc (가중치가 가장 적은 연결)
+- lblc (지역성 기반 최소 연결)
+- lblcr (복제를 통한 지역성 기반 최소 연결)
+- sh (소스 해싱)
+- dh (데스티네이션 해싱)
+- sed (최단 예상 지연)
+- nq (줄 서지 마세요)
+

+

구현

+

EKS 클러스터에서 IPVS를 활성화하려면 몇 단계만 거치면 됩니다.가장 먼저 해야 할 일은 EKS 작업자 노드 이미지에 Linux 가상 서버 관리 ipvsadm 패키지가 설치되어 있는지 확인하는 것입니다.Amazon Linux 2023과 같은 Fedora 기반 이미지에 이 패키지를 설치하려면 작업자 노드 인스턴스에서 다음 명령을 실행할 수 있습니다. +

sudo dnf install -y ipvsadm
+
+Ubuntu와 같은 데비안 기반 이미지에서는 설치 명령이 다음과 같습니다. +
sudo apt-get install ipvsadm
+

+

다음으로 위에 나열된 IPVS 구성 옵션에 대한 커널 모듈을 로드해야 합니다.재부팅해도 계속 작동하도록 이러한 모듈을 /etc/modules-load.d/ 디렉토리 내의 파일에 기록하는 것이 좋습니다. +

sudo sh -c 'cat << EOF > /etc/modules-load.d/ipvs.conf
+ip_vs
+ip_vs_rr
+ip_vs_wrr
+ip_vs_lc
+ip_vs_wlc
+ip_vs_lblc
+ip_vs_lblcr
+ip_vs_sh
+ip_vs_dh
+ip_vs_sed
+ip_vs_nq
+nf_conntrack
+EOF'
+
+다음 명령을 실행하여 이미 실행 중인 시스템에서 이러한 모듈을 로드할 수 있습니다. +
sudo modprobe ip_vs 
+sudo modprobe ip_vs_rr
+sudo modprobe ip_vs_wrr
+sudo modprobe ip_vs_lc
+sudo modprobe ip_vs_wlc
+sudo modprobe ip_vs_lblc
+sudo modprobe ip_vs_lblcr
+sudo modprobe ip_vs_sh
+sudo modprobe ip_vs_dh
+sudo modprobe ip_vs_sed
+sudo modprobe ip_vs_nq
+sudo modprobe nf_conntrack
+

+
+

Note

+

이러한 작업자 노드 단계는 사용자 데이터 스크립트 를 통해 작업자 노드의 부트스트랩 프로세스의 일부로 실행하거나 사용자 지정 작업자 노드 AMI를 빌드하기 위해 실행되는 빌드 스크립트에서 실행하는 것이 좋습니다.

+
+

다음으로 IPVS 모드에서 실행되도록 클러스터의 kube-proxy DaemonSet를 구성합니다. 이는 kube-proxy modeipvs로 설정하고 ipvs scheduler를 위에 나열된 로드 밸런싱 옵션 중 하나로 설정하면 됩니다(예: 라운드 로빈의 경우 rr).

+
+

Warning

+

이는 운영 중단을 야기하는 변경이므로 근무 시간 외 시간에 수행해야 합니다.영향을 최소화하려면 초기 EKS 클러스터 생성 중에 이러한 변경을 수행하는 것이 좋습니다.

+
+

kube-proxy EKS 애드온을 업데이트하여 AWS CLI 명령을 실행하여 IPVS를 활성화할 수 있습니다. +

aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name kube-proxy \
+  --configuration-values '{"ipvs": {"scheduler": "rr"}, "mode": "ipvs"}' \
+  --resolve-conflicts OVERWRITE
+
+또는 클러스터에서 kube-proxy-config 컨피그맵을 수정하여 이 작업을 수행할 수 있습니다. +
kubectl -n kube-system edit cm kube-proxy-config
+
+ipvs에서 scheduler 설정을 찾아 값을 위에 나열된 IPVS 로드 밸런싱 옵션 중 하나로 설정합니다(예: 라운드 로빈의 경우 rr) +기본값이 iptablesmode 설정을 찾아 값을 ipvs로 변경합니다. +두 옵션 중 하나의 결과는 아래 구성과 유사해야 합니다. +
  iptables:
+    masqueradeAll: false
+    masqueradeBit: 14
+    minSyncPeriod: 0s
+    syncPeriod: 30s
+  ipvs:
+    excludeCIDRs: null
+    minSyncPeriod: 0s
+    scheduler: "rr"
+    syncPeriod: 30s
+  kind: KubeProxyConfiguration
+  metricsBindAddress: 0.0.0.0:10249
+  mode: "ipvs"
+  nodePortAddresses: null
+  oomScoreAdj: -998
+  portRange: ""
+  udpIdleTimeout: 250ms
+

+

이러한 변경을 수행하기 전에 작업자 노드가 클러스터에 연결된 경우 kube-proxy 데몬셋을 다시 시작해야 합니다. +

kubectl -n kube-system rollout restart ds kube-proxy
+

+

유효성 검사

+

작업자 노드 중 하나에서 다음 명령을 실행하여 클러스터 및 작업자 노드가 IPVS 모드에서 실행되고 있는지 확인할 수 있습니다. +

sudo ipvsadm -L
+

+

최소한 쿠버네티스 API 서버 서비스에 대한 항목이 10.100.0.1이고 CoreDNS 서비스에 대한 항목이 10.100.0.10인 아래와 비슷한 결과를 볼 수 있을 것이다. +

IP Virtual Server version 1.2.1 (size=4096)
+Prot LocalAddress:Port Scheduler Flags
+  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
+TCP  ip-10-100-0-1.us-east-1. rr
+  -> ip-192-168-113-81.us-eas Masq        1      0          0
+  -> ip-192-168-162-166.us-ea Masq        1      1          0
+TCP  ip-10-100-0-10.us-east-1 rr
+  -> ip-192-168-104-215.us-ea Masq        1      0          0
+  -> ip-192-168-123-227.us-ea Masq        1      0          0
+UDP  ip-10-100-0-10.us-east-1 rr
+  -> ip-192-168-104-215.us-ea Masq        1      0          0
+  -> ip-192-168-123-227.us-ea Masq        1      0          0
+

+
+

Note

+

이 예제 출력은 서비스 IP 주소 범위가 10.100.0.0/16인 EKS 클러스터에서 가져온 것입니다.

+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/loadbalancing/loadbalancing/index.html b/ko/networking/loadbalancing/loadbalancing/index.html new file mode 100644 index 000000000..abbf7a3ba --- /dev/null +++ b/ko/networking/loadbalancing/loadbalancing/index.html @@ -0,0 +1,2471 @@ + + + + + + + + + + + + + + + + + + + + + + + 로드 밸런싱 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Kubernetes 애플리케이션 및 AWS 로드밸런서를 통한 오류 및 타임아웃 방지

+

필요한 쿠버네티스 리소스 (서비스, 디플로이먼트, 인그레스 등) 를 생성한 후, 파드는 Elastic Load Balancer를 통해 클라이언트로부터 트래픽을 수신할 수 있어야 합니다. 하지만 애플리케이션 또는 Kubernetes 환경을 변경할 때 오류, 시간 초과 또는 연결 재설정이 생성될 수 있습니다. 이런 변경으로 인해 애플리케이션 배포 또는 조정 작업 (수동 또는 자동) 이 트리거될 수 있습니다.

+

안타깝게도 이런 오류는 애플리케이션이 문제를 기록하지 않는 경우에도 생성될 수 있습니다. 클러스터의 리소스를 제어하는 Kubernetes 시스템이 로드밸런서의 대상 등록 및 상태를 제어하는 AWS 시스템보다 빠르게 실행될 수 있기 때문입니다. 애플리케이션이 요청을 수신할 준비가 되기 전에 파드가 트래픽을 수신하기 시작할 수도 있습니다.

+

파드가 Ready 상태가 되는 프로세스와 트래픽을 파드로 라우팅하는 방법을 살펴보겠습니다.

+

파드 Readiness

+

2019 Kubecon talk에서 발췌한 이 다이어그램은 파드가 레디 상태가 되고 '로드밸런서' 서비스에 대한 트래픽을 수신하기 위해 거쳐진 단계를 보여준다. +Ready? A Deep Dive into Pod Readiness Gates for Service Health... - Minhan Xia & Ping Zou
+NodePort 서비스의 멤버인 파드가 생성되면 쿠버네티스는 다음 단계를 거칩니다.

+
    +
  1. 파드는 쿠버네티스 컨트롤 플레인 (즉, kubectl 명령 또는 스케일링 액션으로부터) 에서 생성됩니다.
  2. +
  3. 파드는 kube-scheduler에 의해 스케줄링되며 클러스터의 노드에 할당됩니다.
  4. +
  5. 할당된 노드에서 실행 중인 kubelet은 업데이트를 수신하고 ('watch'를 통해) 로컬 컨테이너 런타임과 통신하여 파드에 지정된 컨테이너를 시작한다.
      +
    1. 컨테이너가 실행을 시작하면 (그리고 선택적으로 ReadinessProbes만 전달하면), kubelet은 kube-apiserver로 업데이트를 전송하여 파드 상태를 Ready로 업데이트합니다.
    2. +
    +
  6. +
  7. 엔드포인트 컨트롤러는 ('watch'를 통해) 서비스의 엔드포인트 목록에 추가할 새 파드가 Ready라는 업데이트를 수신하고 적절한 엔드포인트 배열에 파드 IP/포트 튜플을 추가합니다.
  8. +
  9. kube-proxy는 서비스에 대한 iptables 규칙에 추가할 새 IP/포트가 있다는 업데이트 (watch를 통해) 를 수신한다.
      +
    1. 워커 노드의 로컬 iptables 규칙이 NodePort 서비스의 추가 대상 파드로 업데이트됩니다.
    2. +
    +
  10. +
+
+

참조

+

인그레스 리소스와 인그레스 컨트롤러 (예: AWS 로드밸런서 컨트롤러) 를 사용하는 경우 5단계는 kube-proxy 대신 관련 컨트롤러에서 처리됩니다.그러면 컨트롤러는 필요한 구성 단계 (예: 로드밸런서에 대상 등록/등록 취소) 를 수행하여 트래픽이 예상대로 흐르도록 합니다.

+
+

파드가 종료되거나 준비되지 않은 상태로 변경되는 경우에도 유사한 프로세스가 발생합니다. API 서버는 컨트롤러, kubelet 또는 kubectl 클라이언트로부터 업데이트를 수신하여 파드를 종료합니다. 3~5단계는 거기서부터 계속되지만, 엔드포인트 목록과 iptables 규칙에서 파드 IP/튜플을 삽입하는 대신 제거합니다.

+

배포에 미치는 영향

+

다음은 애플리케이션 배포로 인해 파드 교체가 트리거될 때 취해진 단계를 보여주는 다이어그램입니다: +Ready? A Deep Dive into Pod Readiness Gates for Service Health... - Minhan Xia & Ping Zou
+이 다이어그램에서 주목할 점은 첫 번째 파드가 "Ready" 상태에 도달할 때까지 두 번째 파드가 배포되지 않는다는 것입니다. 이전 섹션의 4단계와 5단계도 위의 배포 작업과 병행하여 수행됩니다.

+

즉, 디플로이먼트 컨트롤러가 다음 파드로 넘어갈 때 새 파드 상태를 전파하는 액션이 여전히 진행 중일 수 있습니다. 이 프로세스는 이전 버전의 파드도 종료하므로, 파드가 Ready 상태에 도달했지만 변경 사항이 계속 전파되고 이전 버전의 파드가 종료되는 상황이 발생할 수 있습니다.

+

위에서 설명한 Kubernetes 시스템은 기본적으로 로드밸런서의 등록 시간이나 상태 확인을 고려하지 않기 때문에 AWS와 같은 클라우드 공급자의 로드밸런서를 사용하면 이 문제가 더욱 악화됩니다. 즉 디플로이먼트 업데이트가 파드 전체에 걸쳐 완전히 순환될 수 있지만 로드밸런서가 상태 점검 수행 또는 새 파드 등록을 완료하지 않아 운영 중단이 발생할 수 있습니다.

+

파드가 종료될 때도 비슷한 문제가 발생합니다. 로드밸런서 구성에 따라 파드의 등록을 취소하고 새 요청 수신을 중지하는 데 1~2분 정도 걸릴 수 있습니다. 쿠버네티스는 이런 등록 취소를 위해 롤링 디플로이먼트를 지연시키지 않으며, 이로 인해 로드밸런서가 이미 종료된 대상 파드의 IP/포트로 트래픽을 계속 보내는 상태로 이어질 수 있습니다.

+

이런 문제를 방지하기 위해 Kubernetes 시스템이 AWS Load Balancer 동작에 더 부합하는 조치를 취하도록 구성을 추가할 수 있습니다.

+

권장 사항

+

IP 대상 유형 로드밸런서 이용

+

LoadBalancer 유형의 서비스를 생성할 때, 인스턴스 대상 유형 등록을 통해 로드밸런서에서 클러스터의 모든 노드로 트래픽이 전송됩니다. 그러면 각 노드가 'NodePort'의 트래픽을 서비스 엔드포인트 어레이의 파드/IP 튜플로 리디렉션합니다. 이 타겟은 별도의 워커 노드에서 실행될 수 있습니다.

+
+

Note

+

배열에는 "Ready" 파드만 있어야 한다는 점을 기억하세요.

+
+

이렇게 하면 요청에 홉이 추가되고 로드밸런서 구성이 복잡해집니다.예를 들어 위의 로드밸런서가 세션 어피니티로 구성된 경우 어피니티는 어피니티 구성에 따라 로드밸런서와 백엔드 노드 사이에만 유지될 수 있습니다.

+

로드밸런서가 백엔드 파드와 직접 통신하지 않기 때문에 쿠버네티스 시스템으로 트래픽 흐름과 타이밍을 제어하기가 더 어려워집니다.

+

AWS 로드밸런서 컨트롤러를 사용하는 경우, IP 대상 유형을 사용하여 파드 IP/포트 튜플을 로드밸런서에 직접 등록할 수 있습니다.

+

이는 로드밸런서에서 대상 파드로의 트래픽 경로를 단순화 합니다. 즉 새 대상이 등록되면 대상이 "Ready" 파드 IP 및 포트인지 확인할 수 있고, 로드밸런서의 상태 확인이 파드에 직접 전달되며, VPC 흐름 로그를 검토하거나 유틸리티를 모니터링할 때 로드밸런서와 파드 간의 트래픽을 쉽게 추적할 수 있습니다.

+

또한 IP 등록을 사용하면 NodePort 규칙을 통해 연결을 관리하는 대신 백엔드 파드에 대한 트래픽의 타이밍과 구성을 직접 제어할 수 있습니다.

+

파드 Readiness 게이트 활용

+

파드 Readiness 게이트 는 파드가 "Ready" 상태에 도달하기 전에 충족되어야 하는 추가 요구사항이다.

+
+

[...] AWS Load Balancer 컨트롤러는 인그레스 또는 서비스 백엔드를 구성하는 파드에 대한 준비 조건을 설정할 수 있습니다. ALB/NLB 대상 그룹의 해당 대상의 상태가 "Healthy"로 표시되는 경우에만 파드의 조건 상태가 'True'로 설정됩니다. 이렇게 하면 새로 생성된 파드가 ALB/NLB 대상 그룹에서 "정상"으로 되어 트래픽을 받을 준비가 될 때까지 디플로이먼트의 롤링 업데이트가 기존 파드를 종료하지 않도록 한다.

+
+

Readiness 게이트는 배포 중에 새 복제본을 생성할 때 쿠버네티스가 "너무 빨리" 움직이지 않도록 하고 쿠버네티스는 배포를 완료했지만 새 파드가 등록을 완료하지 않은 상황을 방지합니다.

+

이를 활성화하려면 다음을 수행해야 합니다.

+
    +
  1. 최신 버전의 AWS 로드밸런서 컨트롤를 배포합니다. (이전 버전을 업그레이드하는 경우 설명서를 참조)
  2. +
  3. 파드 Readiness 게이트를 자동으로 주입하려면 elbv2.k8s.aws/pod-readiness-gate-inject: enabled 레이블로 타겟 파드가 실행 중인 네임스페이스에 레이블을 붙입니다..
  4. +
  5. 네임스페이스의 모든 파드가 준비 게이트 컨피그레이션을 가져오도록 하려면 인그레스 또는 서비스를 생성하고 파드를 생성하기 전에 네임스페이스에 레이블을 지정해야 한다.
  6. +
+

종료에 로드밸런서에서 파드의 등록이 취소되었는지 확인

+

When a pod is terminated steps 4 and 5 from the pod readiness section occur at the same time that the container processes receive the termination signals. This means that if your container is able to shut down quickly it may shut down faster than the Load Balancer is able to deregister the target. To avoid this situation adjust the Pod spec with:

+
    +
  1. 애플리케이션이 등록을 취소하고 연결을 정상적으로 종료할 수 있도록 'PreStop' 라이프사이클 훅을 추가합니다. 이 훅은 API 요청 또는 관리 이벤트 (예: 라이브니스/스타트업 프로브 실패, 선점, 리소스 경합 등) 로 인해 컨테이너가 종료되기 직전에 호출됩니다. 중요한 점은 이 훅이 호출되어 종료 신호가 전송되기 전에 완료되도록 허용됨 입니다. 단, 유예 기간이 실행을 수용할 수 있을 만큼 충분히 길어야 합니다.
  2. +
+

        lifecycle:
+          preStop:
+            exec:
+              command: ["/bin/sh", "-c", "sleep 180"] 
+
+위와 같은 간단한 sleep 명령을 사용하면 파드가 Terminating (종료 중)으로 표시된 시점 (그리고 로드밸런서 등록 취소가 시작되는 시점) 과 종료 신호가 컨테이너 프로세스로 전송되는 시점 사이에 짧은 지연을 발생시킬 수 있다.필요한 경우 이 훅를 고급 애플리케이션 종료/종료 절차에도 활용할 수 있습니다.

+
    +
  1. 전체 프리스톱 실행 시간과 애플리케이션이 종료 신호에 정상적으로 응답하는 데 걸리는 시간을 수용할 수 있도록 'TerminationGracePeriodsSeconds'를 연장하십시오.아래 예시에서는 유예 기간을 200초로 연장하여 전체 sleep 180 명령을 완료한 다음, 앱이 정상적으로 종료될 수 있도록 20초 더 연장했습니다.
  2. +
+
    spec:
+      terminationGracePeriodSeconds: 200
+      containers:
+      - name: webapp
+        image: webapp-st:v1.3
+        [...]
+        lifecycle:
+          preStop:
+            exec:
+              command: ["/bin/sh", "-c", "sleep 180"] 
+
+

파드에 Readiness 프로브가 있는지 확인

+

쿠버네티스에서 파드를 생성할 때 기본 준비 상태는 "Ready"이지만, 대부분의 애플리케이션은 인스턴스화하고 요청을 받을 준비가 되는 데 1~2분 정도 걸립니다. 파드 스펙에서 'Readiness 프로브'를 정의할 수 있습니다. 실행 명령어 또는 네트워크 요청을 사용하여 애플리케이션이 시작을 완료하고 트래픽을 처리할 준비가 되었는지 확인하는 데 사용됩니다.

+

Readiness 프로브로 정의된 파드는 "NotReady" 상태에서 시작하며, Readiness 프로브가 성공했을 때만 "준비 완료"로 변경됩니다. 이렇게 하면 애플리케이션 시작이 완료될 때까지 애플리케이션이 "서비스 중"으로 전환되지 않습니다.

+

장애 상태 (예: 교착 상태) 에 들어갈 때 애플리케이션을 다시 시작할 수 있도록 라이브니스 프로브를 사용하는 것이 좋지만, 활성 장애가 발생하면 애플리케이션 재시작을 트리거하므로 Stateful 애플리케이션을 사용할 때는 주의해야 합니다. 시작 속도가 느린 애플리케이션에도 스타트업 프로브를 활용할 수 있습니다.

+

아래 프로브는 포트 80에 대한 HTTP 프로브를 사용하여 웹 애플리케이션이 언제 준비되는지 확인합니다 (활성 프로브에도 동일한 프로브 구성이 사용됨).

+
        [...]
+        ports:
+        - containerPort: 80
+        livenessProbe:
+          httpGet:
+            path: /
+            port: 80
+          failureThreshold: 1
+          periodSeconds: 10
+          initialDelaySeconds: 5
+        readinessProbe:
+          httpGet:
+            path: /
+            port: 80
+          periodSeconds: 5
+        [...]
+
+

파드 중단 예산 (PDB) 설정

+

파드 중단 예산 (PDB)은 복제된 애플리케이션 중 자발적 중단으로 인해 동시에 다운되는 파드의 수를 제한합니다. 예를 들어 쿼럼 기반 응용 프로그램에서는 실행 중인 복제본 수가 쿼럼에 필요한 수 이하로 떨어지지 않도록 하려는 경우가 있습니다. 웹 프런트 엔드는 부하를 처리하는 복제본의 수가 전체 복제본의 특정 비율 이하로 떨어지지 않도록 해야 할 수 있습니다.

+

PDB는 노드 드레이닝 또는 애플리케이션 배포와 같은 것으로부터 애플리케이션을 보호합니다. PDB는 이런 조치를 취하는 동안 사용할 수 있는 파드의 수 또는 비율을 최소한으로 유지합니다.

+
+

Caution

+

PDB는 호스트 OS 장애 또는 네트워크 연결 손실과 같은 비자발적 중단으로부터 애플리케이션을 보호하지 않습니다.

+
+

아래 예시는 app: echoserver 레이블이 붙은 파드를 항상 1개 이상 사용할 수 있도록 만듭니다. 애플리케이션에 맞는 올바른 레플리카 수를 구성하거나 백분율을 사용할 수 있습니다.:

+
apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+  name: echoserver-pdb
+  namespace: echoserver
+spec:
+  minAvailable: 1
+  selector:
+    matchLabels:
+      app: echoserver
+
+

종료 신호를 정상적으로 처리

+

파드가 종료되면 컨테이너 내에서 실행되는 애플리케이션은 두 개의 Signal을 수신합니다. 첫 번째는 SIGTERM 신호이며, 이는 프로세스 실행을 중단하라는 "정중한" 요청입니다. 이 신호는 차단될 수도 있고 애플리케이션이 단순히 이 신호를 무시할 수도 있으므로, terminationGracePeriodSeconds이 경과하면 애플리케이션은 SIGKILL 신호를 받게 됩니다. SIGKILL은 프로세스를 강제로 중지하는 데 사용되며, 차단, 처리 또는 무시 될 수 없으므로 항상 치명적입니다.

+

이런 신호는 컨테이너 런타임에서 애플리케이션 종료를 트리거하는 데 사용됩니다.또한 SIGTERM 신호는 preStop 훅이 실행된 후에 전송됩니다. 위 구성에서 preStop 훅은 로드밸런서에서 파드의 등록이 취소되었는지 확인하므로 애플리케이션은 SIGTERM 신호가 수신될 때 열려 있는 나머지 연결을 정상적으로 종료할 수 있습니다.

+
+

참조

+

애플리케이션의 진입점에 "래퍼 스크립트"를 사용할 경우 컨테이너 환경에서의 신호 처리는 복잡할 수 있습니다. 스크립트는 PID 1이므로 신호를 애플리케이션으로 전달하지 않을 수 있습니다.

+
+

등록 취소 지연에 주의

+

Elastic Load Balancing은 등록 취소 중인 대상에 대한 요청 전송을 중지합니다.기본적으로 Elastic Load Balancing은 등록 취소 프로세스를 완료하기 전에 300초 정도 대기하므로 대상에 대한 진행 중인 요청을 완료하는 데 도움이 될 수 있습니다. Elastic Load Balancing이 대기하는 시간을 변경하려면 등록 취소 지연 값을 업데이트하십시오. +등록 취소 대상의 초기 상태는 draining입니다. 등록 취소 지연이 경과하면 등록 취소 프로세스가 완료되고 대상의 상태는 unused가 됩니다. 대상이 Auto Scaling 그룹의 일부인 경우 대상을 종료하고 교체할 수 있습니다.

+

등록 취소 대상에 진행 중인 요청이 없고 활성 연결이 없는 경우 Elastic Load Balancing은 등록 취소 지연이 경과할 때까지 기다리지 않고 등록 취소 프로세스를 즉시 완료합니다.

+
+

Caution

+

대상 등록 취소가 완료되더라도 등록 취소 지연 제한 시간이 만료될 때까지 대상 상태가 '드레이닝 중'으로 표시됩니다. 제한 시간이 만료되면 대상은 '미사용' 상태로 전환됩니다.

+
+

등록 취소 지연이 경과하기 전에 등록 취소 대상이 연결을 종료하면 클라이언트는 500레벨 오류 응답을 받습니다..

+

이는 alb.ingress.kubernetes.io/target-group-attributes 어노테이션을 사용하여 인그레스 리소스의 어노테이션을 사용하여 구성할 수 있습니다. 아래는 예제입니다.

+
apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: echoserver-ip
+  namespace: echoserver
+  annotations:
+    alb.ingress.kubernetes.io/scheme: internet-facing
+    alb.ingress.kubernetes.io/target-type: ip
+    alb.ingress.kubernetes.io/load-balancer-name: echoserver-ip
+    alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30
+spec:
+  ingressClassName: alb
+  rules:
+    - host: echoserver.example.com
+      http:
+        paths:
+          - path: /
+            pathType: Exact
+            backend:
+              service:
+                name: echoserver-service
+                port:
+                  number: 8080
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/monitoring/index.html b/ko/networking/monitoring/index.html new file mode 100644 index 000000000..a99a3243a --- /dev/null +++ b/ko/networking/monitoring/index.html @@ -0,0 +1,2506 @@ + + + + + + + + + + + + + + + + + + + + + + + 네트워크 성능 문제에 대한 EKS 워크로드 모니터링 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

네트워크 성능 문제에 대한 EKS 워크로드 모니터링

+

DNS 스로틀링 문제에 대한 CoreDNS 트래픽 모니터링

+

DNS 집약적 워크로드를 실행하면 DNS 스로틀링으로 인해 간헐적으로 CoreDNS 장애가 발생할 수 있으며, 이로 인해 가끔 알 수 없는 Hostexception 오류가 발생할 수 있는 애플리케이션에 영향을 미칠 수 있습니다.

+

CoreDNS 배포에는 Kubernetes 스케줄러가 클러스터의 개별 워커 노드에서 CoreDNS 인스턴스를 실행하도록 지시하는 반선호도 정책이 있습니다. 즉, 동일한 워커 노드에 복제본을 같은 위치에 배치하지 않도록 해야 합니다. 이렇게 하면 각 복제본의 트래픽이 다른 ENI를 통해 라우팅되므로 네트워크 인터페이스당 DNS 쿼리 수가 효과적으로 줄어듭니다. 초당 1024개의 패킷 제한으로 인해 DNS 쿼리가 병목 현상을 겪는 경우 1) 코어 DNS 복제본 수를 늘리거나 2) NodeLocal DNS 캐시를 구현해 볼 수 있습니다.자세한 내용은 CoreDNS 메트릭 모니터링을 참조하십시오.

+

도전 과제

+
    +
  • 패킷 드롭은 몇 초 만에 발생하며 DNS 스로틀링이 실제로 발생하는지 확인하기 위해 이런 패턴을 적절하게 모니터링하는 것은 까다로울 수 있습니다.
  • +
  • DNS 쿼리는 엘라스틱 네트워크 인터페이스 수준에서 조절됩니다. 따라서 병목 현상이 발생한 쿼리는 쿼리 로깅에 나타나지 않습니다.
  • +
  • 플로우 로그는 모든 IP 트래픽을 캡처하지 않습니다. 예: 인스턴스가 Amazon DNS 서버에 접속할 때 생성되는 트래픽 자체 DNS 서버를 사용하는 경우 해당 DNS 서버로 향하는 모든 트래픽이 로깅됩니다.
  • +
+

솔루션

+

워커 노드의 DNS 조절 문제를 쉽게 식별할 수 있는 방법은 linklocal_allowance_exeded 메트릭을 캡처하는 것입니다. linklocal_allowance_exceeded는 로컬 프록시 서비스에 대한 트래픽의 PPS가 네트워크 인터페이스의 최대값을 초과하여 삭제된 패킷 수입니다. 이는 DNS 서비스, 인스턴스 메타데이터 서비스 및 Amazon Time Sync 서비스에 대한 트래픽에 영향을 미칩니다. 이 이벤트를 실시간으로 추적하는 대신 이 지표를 Amazon Managed Service for Prometheus에도 스트리밍하여 Amazon Managed Grafana에서 시각화할 수 있습니다.

+

Conntrack 메트릭을 사용하여 DNS 쿼리 지연 모니터링

+

CoreDNS 스로틀링/쿼리 지연을 모니터링하는 데 도움이 될 수 있는 또 다른 지표는 conntrack_allowance_availableconntrack_allowance_exceed입니다. +연결 추적 허용량을 초과하여 발생하는 연결 장애는 다른 허용치를 초과하여 발생하는 연결 실패보다 더 큰 영향을 미칠 수 있습니다. TCP를 사용하여 데이터를 전송하는 경우, 대역폭, PPS 등과 같이 EC2 인스턴스 네트워크 허용량을 초과하여 대기열에 있거나 삭제되는 패킷은 일반적으로 TCP의 혼잡 제어 기능 덕분에 정상적으로 처리됩니다. 영향을 받은 흐름은 느려지고 손실된 패킷은 재전송됩니다. 그러나 인스턴스가 Connections Tracked 허용량을 초과하는 경우 새 연결을 위한 공간을 마련하기 위해 기존 연결 중 일부를 닫기 전까지는 새 연결을 설정할 수 없습니다.

+

conntrack_allowance_availableconntrack_allowance_exceed는 고객이 모든 인스턴스마다 달라지는 연결 추적 허용량을 모니터링하는 데 도움이 됩니다. 이런 네트워크 성능 지표를 통해 고객은 네트워크 대역폭, 초당 패킷 수 (PPS), 추적된 연결, 링크 로컬 서비스 액세스 (Amazon DNS, 인스턴스 메타 데이터 서비스, Amazon Time Sync) 와 같은 인스턴스의 네트워킹 허용량을 초과했을 때 대기 또는 삭제되는 패킷 수를 파악할 수 있습니다.

+

conntrack_allowance_available은 해당 인스턴스 유형의 추적된 연결 허용량에 도달하기 전에 인스턴스가 설정할 수 있는 추적된 연결 수입니다 (니트로 기반 인스턴스에서만 지원됨). +conntrack_allowance_exceed는 인스턴스의 연결 추적이 최대치를 초과하여 새 연결을 설정할 수 없어서 삭제된 패킷 수입니다.

+

기타 중요한 네트워크 성능 지표

+

기타 중요한 네트워크 성능 지표는 다음과 같습니다.

+

bw_in_allowance_exceed (이상적인 지표 값은 0이어야 함) 는 인바운드 집계 대역폭이 인스턴스의 최대값을 초과하여 대기 및/또는 삭제된 패킷 수입니다.

+

bw_out_allowance_exceed (이상적인 지표 값은 0이어야 함) 는 아웃바운드 총 대역폭이 해당 인스턴스의 최대값을 초과하여 대기 및/또는 삭제된 패킷 수입니다.

+

pps_allowance_exceed (이상적인 지표 값은 0이어야 함) 는 양방향 PPS가 인스턴스의 최대값을 초과하여 대기 및/또는 삭제된 패킷 수입니다.

+

네트워크 성능 문제에 대한 워크로드 모니터링을 위한 지표 캡처

+

Elastic Network Adapter (ENA) 드라이버는 위에서 설명한 네트워크 성능 메트릭이 활성화된 인스턴스로부터 해당 메트릭을 게시합니다. CloudWatch 에이전트를 사용하여 모든 네트워크 성능 지표를 CloudWatch에 게시할 수 있습니다. 자세한 내용은 블로그를 참조하십시오.

+

이제 위에서 설명한 지표를 캡처하여 Prometheus용 Amazon Managed Service에 저장하고 Amazon Managed Grafana를 사용하여 시각화해 보겠습니다.

+

사전 요구 사항

+
    +
  • ethtool - 워커 노드에 ethtool이 설치되어 있는지 확인하십시오.
  • +
  • AWS 계정에 구성된 AMP 워크스페이스.지침은 AMP 사용 설명서의 워크스페이스 만들기를 참조하십시오.
  • +
  • Amazon Managed Grafana 워크스페이스
  • +
+

프로메테우스 ethtool 익스포터 구축

+

배포에는 ethtool에서 정보를 가져와 프로메테우스 형식으로 게시하는 Python 스크립트가 포함되어 있습니다.

+
kubectl apply -f https://raw.githubusercontent.com/Showmax/prometheus-ethtool-exporter/master/deploy/k8s-daemonset.yaml
+
+

ADOT 컬렉터를 배포하여 ethtool 메트릭을 스크랩하고 프로메테우스용 아마존 매니지드 서비스 워크스페이스에 저장

+

OpenTelemetry용 AWS 배포판 (ADOT) 을 설치하는 각 클러스터에는 이 역할이 있어야 AWS 서비스 어카운트에 메트릭을 Prometheus용 Amazon 관리형 서비스에 저장할 수 있는 권한을 부여할 수 있습니다.다음 단계에 따라 IRSA를 사용하여 IAM 역할을 생성하고 Amazon EKS 서비스 어카운트에 연결하십시오.

+
eksctl create iamserviceaccount --name adot-collector --namespace default --cluster <CLUSTER_NAME> --attach-policy-arn arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess --attach-policy-arn arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess --attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy --region <REGION> --approve  --override-existing-serviceaccounts
+
+

ADOT 컬렉터를 배포하여 프로메테우스 ethtool 익스포터의 메트릭을 스크랩하여 프로메테우스용 아마존 매니지드 서비스에 저장해 보겠습니다.

+

다음 절차에서는 배포를 모드 값으로 사용하는 예제 YAML 파일을 사용합니다. 이 모드는 기본 모드이며 독립 실행형 응용 프로그램과 유사하게 ADOT Collector를 배포합니다. 이 구성은 클러스터의 파드에서 스크랩한 샘플 애플리케이션과 Amazon Managed Service for Prometheus 지표로부터 OTLP 지표를 수신합니다.

+
curl -o collector-config-amp.yaml https://raw.githubusercontent.com/aws-observability/aws-otel-community/master/sample-configs/operator/collector-config-amp.yaml
+
+

컬렉터-config-amp.yaml에서 다음을 사용자 고유의 값으로 바꾸십시오. +* mode: deployment +* serviceAccount: adot-collector +* endpoint: "" +* region: "" +* name: adot-collector

+
kubectl apply -f collector-config-amp.yaml 
+
+

채택 수집기가 배포되면 지표가 Amazon Prometheus에 성공적으로 저장됩니다.

+

Prometheus가 알림을 보내도록 Amazon 관리 서비스의 알림 관리자를 구성

+

지금까지 설명한 메트릭을 확인하기 위해 기록 규칙 및 경고 규칙을 구성해 보겠습니다.

+

프로메테우스용 아마존 매니지드 서비스용 ACK 컨트롤러를 사용하여 알림 및 기록 규칙을 규정할 것입니다.

+

프로메테우스용 Amazon 관리 서비스 서비스를 위한 ACL 컨트롤러를 배포해 보겠습니다.

+
export SERVICE=prometheusservice
+export RELEASE_VERSION=`curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4`
+export ACK_SYSTEM_NAMESPACE=ack-system
+export AWS_REGION=us-east-1
+aws ecr-public get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin public.ecr.aws
+helm install --create-namespace -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller \
+oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION --set=aws.region=$AWS_REGION
+
+

명령을 실행하면 몇 분 후 다음 메시지가 표시됩니다.

+
You are now able to create Amazon Managed Service for Prometheus (AMP) resources!
+
+The controller is running in "cluster" mode.
+
+The controller is configured to manage AWS resources in region: "us-east-1"
+
+The ACK controller has been successfully installed and ACK can now be used to provision an Amazon Managed Service for Prometheus workspace.
+
+

이제 경고 관리자 정의 및 규칙 그룹을 프로비저닝하기 위한 yaml 파일을 생성해 보겠습니다. +아래 파일을 rulegroup.yaml로 저장합니다.

+
apiVersion: prometheusservice.services.k8s.aws/v1alpha1
+kind: RuleGroupsNamespace
+metadata:
+   name: default-rule
+spec:
+   workspaceID: <Your WORKSPACE-ID>
+   name: default-rule
+   configuration: |
+     groups:
+     - name: ppsallowance
+       rules:
+       - record: metric:pps_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="pps_allowance_exceeded"}[30s])
+       - alert: PPSAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="pps_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})
+           description: "PPSAllowanceExceeded is greater than 0"
+     - name: bw_in
+       rules:
+       - record: metric:bw_in_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="bw_in_allowance_exceeded"}[30s])
+       - alert: BWINAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="bw_in_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})
+           description: "BWInAllowanceExceeded is greater than 0"
+     - name: bw_out
+       rules:
+       - record: metric:bw_out_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="bw_out_allowance_exceeded"}[30s])
+       - alert: BWOutAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="bw_out_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})
+           description: "BWoutAllowanceExceeded is greater than 0"            
+     - name: conntrack
+       rules:
+       - record: metric:conntrack_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="conntrack_allowance_exceeded"}[30s])
+       - alert: ConntrackAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="conntrack_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})
+           description: "ConnTrackAllowanceExceeded is greater than 0"
+     - name: linklocal
+       rules:
+       - record: metric:linklocal_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="linklocal_allowance_exceeded"}[30s])
+       - alert: LinkLocalAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="linklocal_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Packets dropped due to PPS rate allowance exceeded for local services  (instance {{ $labels.instance }})
+           description: "LinkLocalAllowanceExceeded is greater than 0"
+
+

WORKSPACE-ID를 사용 중인 작업 공간의 작업 영역 ID로 바꾸십시오.

+

이제 알림 관리자 정의를 구성해 보겠습니다.아래 파일을 alertmanager.yaml으로 저장합니다.

+
apiVersion: prometheusservice.services.k8s.aws/v1alpha1  
+kind: AlertManagerDefinition
+metadata:
+  name: alert-manager
+spec:
+  workspaceID: <Your WORKSPACE-ID >
+  configuration: |
+    alertmanager_config: |
+      route:
+         receiver: default_receiver
+       receivers:
+       - name: default_receiver
+          sns_configs:
+          - topic_arn: TOPIC-ARN
+            sigv4:
+              region: REGION
+            message: |
+              alert_type: {{ .CommonLabels.alertname }}
+              event_type: {{ .CommonLabels.event_type }}     
+
+

WORKSPACE-ID를 새 작업 공간의 작업 공간 ID로, TOPIC-ARN을 알림을 보내려는 Amazon 단순 알림 서비스 주제의 ARN으로, 그리고 REGION을 워크로드의 현재 지역으로 대체합니다. 작업 영역에 Amazon SNS로 메시지를 보낼 권한이 있는지 확인하십시오.

+

아마존 매니지드 그라파나에서 ethtool 메트릭을 시각화

+

Amazon Managed Grafana 내에서 지표를 시각화하고 대시보드를 구축해 보겠습니다.프로메테우스용 아마존 매니지드 서비스를 아마존 매니지드 그라파나 콘솔 내에서 데이터 소스로 구성하십시오.지침은 아마존 프로메테우스를 데이터 소스로 추가를 참조하십시오.

+

이제 아마존 매니지드 그라파나의 메트릭을 살펴보겠습니다. +탐색 버튼을 클릭하고 ethtool을 검색하세요.

+

Node_ethtool metrics

+

rate (node_net_ethtool {device="eth0", type="linklocal_allowance_Exceed "} [30s]) 쿼리를 사용하여 linklocal_allowance_Exceed 지표에 대한 대시보드를 만들어 보겠습니다.그러면 아래 대시보드가 나타납니다.

+

linklocal_allowance_exceeded dashboard

+

값이 0이므로 삭제된 패킷이 없음을 분명히 알 수 있습니다.

+

rate (node_net_ethtool {device="eth0", type="conntrack_allowance_Exceed "} [30s]) 쿼리를 사용하여 conntrack_allowance_Exceed 메트릭에 대한 대시보드를 만들어 보겠습니다.결과는 아래 대시보드와 같습니다.

+

conntrack_allowance_exceeded dashboard

+

여기에 설명된 대로 클라우드워치 에이전트를 실행하면 conntrack_allowance_exceed 지표를 CloudWatch에서 시각화할 수 있습니다. CloudWatch의 결과 대시보드는 다음과 같이 표시됩니다.

+

CW_NW_Performance

+

값이 0이므로 삭제된 패킷이 없음을 분명히 알 수 있습니다.Nitro 기반 인스턴스를 사용하는 경우 'conntrack_allowance_available'에 대한 유사한 대시보드를 만들고 EC2 인스턴스의 연결을 사전에 모니터링할 수 있습니다.Amazon Managed Grafana에서 슬랙, SNS, Pagerduty 등에 알림을 보내도록 알림을 구성하여 이를 더욱 확장할 수 있습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/prefix-mode/index_linux/index.html b/ko/networking/prefix-mode/index_linux/index.html new file mode 100644 index 000000000..d83341e55 --- /dev/null +++ b/ko/networking/prefix-mode/index_linux/index.html @@ -0,0 +1,2322 @@ + + + + + + + + + + + + + + + + + + + + + + + Prefix 모드 - 리눅스 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Linux용 Prefix 모드

+

Amazon VPC CNI는 Amazon EC2 네트워크 인터페이스에 네트워크 Prefix를 할당하여 노드에 사용 가능한 IP 주소 수를 늘리고 노드당 파드 밀도를 높입니다. Amazon VPC CNI 애드온 버전 1.9.0 이상을 구성하여 네트워크 인터페이스에 개별 보조 IP 주소를 할당하는 대신 IPv4 및 IPv6 CIDR을 할당할 수 있습니다.

+

IPv6 클러스터에서는 Prefix 모드만 지원되며 기본적으로 활성화되어있습니다. VPC CNI는 ENI의 슬롯에 /80 IPv6 Prefix를 할당합니다. 이 가이드의 IPv6 섹션을 참조합니다.

+

Prefix 할당 모드에서 인스턴스 유형당 최대 elastic network interfaces 수는 동일하게 유지되지만, 네트워크 인터페이스의 슬롯에 개별 IPv4 주소를 할당하는 대신 /28(16개의 IP 주소) IPv4 주소 Prefix를 할당하도록 Amazon VPC CNI를 구성할 수 있습니다. ENABLE_PREFIX_DELEGATION이 true로 설정되면 CNI는 ENI에 할당된 Prefix에서 파드에 IP 주소를 할당합니다. EKS 사용자 가이드에 나와 있는 안내를 따라 Prefix IP 모드를 활성화합니다.

+

illustration of two worker subnets, comparing ENI secondary IPvs to ENIs with delegated prefixes

+

네트워크 인터페이스에 할당할 수 있는 최대 IP 주소 수는 인스턴스 유형에 따라 다릅니다. 네트워크 인터페이스에 할당하는 각 Prefix는 하나의 IP 주소로 간주합니다. 예를 들어, c5.large 인스턴스의 네트워크 인터페이스당 IPv4 주소는 10개로 제한됩니다. 이 인스턴스의 각 네트워크 인터페이스에는 기본 IPv4 주소가 있습니다. 네트워크 인터페이스에 보조 IPv4 주소가 없는 경우 네트워크 인터페이스에 최대 9개의 Prefix를 할당할 수 있습니다. 네트워크 인터페이스에 할당한 각 추가 IPv4 주소의 경우 네트워크 인터페이스에 접두사를 하나 더 적게 할당할 수 있습니다. 인스턴스 유형별 네트워크 인터페이스당 IP 주소네트워크 인터페이스에 Prefix 할당에 대한 AWS EC2 설명서를 참조합니다.

+

워커 노드를 초기화하는 동안 VPC CNI는 기본 ENI에 하나 이상의 Prefix를 할당합니다. CNI는 웜 풀을 유지 관리하여 파드 시작 속도를 높이기 위해 Prefix를 미리 할당합니다. 환경 변수를 설정하여 웜 풀에 보관할 Prefix 수를 제어할 수 있습니다.

+
    +
  • WARM_PREFIX_TARGET, 현재 필요 수량을 초과하여 할당할 Prefix 수
  • +
  • WARM_IP_TARGET, 현재 필요 수량을 초과하여 할당할 IP 주소 수
  • +
  • MINIMUM_IP_TARGET, 언제든지 사용할 수 있는 최소 IP 주소 수
  • +
  • WARM_IP_TARGETMINIMUM_IP_TARGET이 설정된 경우 WARM_PREFIX_TARGET를 오버라이드 합니다.
  • +
+

더 많은 파드가 예약되면 기존 ENI에 대해 추가 Prefix가 요청됩니다. 먼저 VPC CNI는 기존 ENI에 새 Prefix를 할당하려고 시도합니다. 만약 ENI의 용량이 부족할 경우, VPC CNI는 노드에 새 ENI를 할당하려고 시도합니다. 최대 ENI 한도(인스턴스 유형별로 정의)에 도달할 때까지 새 ENI가 연결됩니다. 새 ENI가 연결되면 ipamd는 WARM_PREFIX_TARGET, WARM_IP_TARGETMINIMUM_IP_TARGET 설정을 유지하는 데 필요한 하나 이상의 Prefix를 할당합니다.

+

flow chart of procedure for assigning IP to pod

+

권장 사항

+

다음과 같은 경우 Prefix 모드 사용

+

워커 노드에서 Pod 밀도 문제가 발생하는 경우 Prefix 모드를 사용합니다. VPC CNI 오류를 방지하려면 Prefix 모드로 마이그레이션하기 전에 서브넷에서 /28 Prefix의 연속된 주소 블록이 있는지 확인할 것을 권장합니다. 서브넷 예약에 대한 세부 정보는 “서브넷 예약을 사용하여 서브넷 파편화 (IPv4) 방지” 섹션을 참조합니다.

+

이전 버전과의 호환성을 위해 max-pods 제한이 보조 IP 모드를 지원하도록 설정되었습니다. 파드 밀도를 높이려면, max-pods 값을 Kubelet에 지정하고, 노드의 사용자 데이터(User data)에 --use-max-pods=false를 지정합니다. max-pod-calculator.sh 스크립트를 사용하여 특정 인스턴스 유형에 대한 EKS의 권장 최대 파드 수를 계산하는 것을 고려해 볼 수 있습니다. 사용자 데이터의 예는 EKS 사용자 가이드를 참조합니다.

+
./max-pods-calculator.sh --instance-type m5.large --cni-version ``1.9``.0 --cni-prefix-delegation-enabled
+
+

Prefix 할당 모드는 기본 ENI가 파드에 사용되지 않는 CNI 커스텀 네트워킹 사용자에게 특히 적합합니다. Prefix 할당을 사용하면 파드에 기본 ENI를 사용하지 않아도 거의 모든 Nitro 인스턴스 유형에서 더 많은 IP를 연결할 수 있습니다.

+

다음과 같은 경우에는 Prefix 모드를 권장하지 않음

+

서브넷이 매우 파편화 되어 있고 사용 가능한 IP 주소가 부족하여 /28 Prefix를 만들 수 없는 경우에는 Prefix 모드를 사용하지 마십시오. Prefix가 생성되는 서브넷이 파편화 된 경우(사용량이 많고 보조 IP 주소가 흩어져 있는 서브넷), Prefix 연결에 실패할 수 있습니다. 신규 서브넷을 만들고 Prefix를 예약하면 이 문제를 피할 수 있습니다.

+

Prefix 모드에서는 워커 노드에 할당된 보안 그룹이 파드에 공유됩니다. 공유 컴퓨팅 리소스에서 다양한 네트워크 보안 요구사항이 있는 애플리케이션을 실행하여 규정 준수를 달성해야 하는 보안 요구사항이 있는 경우 파드의 보안 그룹을 사용하는 것을 고려합니다.

+

동일한 노드 그룹에서 유사한 인스턴스 유형 사용

+

노드 그룹에는 여러 유형의 인스턴스가 포함될 수 있습니다. 최대 파드 수가 적은 인스턴스의 값이 노드 그룹의 모든 노드에 적용됩니다. 노드 사용을 극대화하려면 노드 그룹에서 유사한 인스턴스 유형을 사용하는 것이 좋습니다. 노드 오토스케일링을 위해 Karpenter를 사용하는 경우 provisioner API의 requirements 부분에서 node.kubernetes.io/instance-type을 구성할 것을 권장합니다.

+
+

Warning

+

특정 노드 그룹의 모든 노드에 대한 최대 파드 수는 노드 그룹 내 단일 인스턴스 유형의 최소 최대 파드 수로 정의됩니다.

+
+

IPv4 주소를 보존하도록 'WARM_PREFIX_TARGET'을 구성

+

설치 매니페스트WARM_PREFIX_TARGET 기본값은 1입니다. 대부분의 경우, WARM_PREFIX_TARGET의 권장 값인 1을 사용하면 인스턴스에 할당된 미사용 IP 주소를 최소화하면서 빠른 파드 시작 시간을 적절히 조합할 수 있습니다.

+

노드당 IPv4 주소를 추가로 보존해야 하는 경우 구성 시 WARM_PREFIX_TARGET을 오버라이드하는 WARM_IP_TARGETMINIMUM_IP_TARGET 설정을 사용합니다. WARM_IP_TARGET을 16 미만의 값으로 설정하면 CNI가 초과 Prefix 전체를 연결하지 않도록 할 수 있습니다.

+

신규 ENI 추가보다는 신규 Prefix 할당

+

EC2 API 작업 시 기존 ENI에 Prefix를 추가로 할당하는 것이 신규 ENI를 생성하여 인스턴스에 연결하는 것보다 더 빠릅니다. Prefix를 사용하면 IPv4 주소 할당을 절약하면서 성능을 향상시킬 수 있습니다. Prefix 연결은 일반적으로 1초 이내에 완료되지만 신규 ENI를 연결하는 데에는 최대 10초가 걸릴 수 있습니다. 대부분의 사용 사례에서 CNI는 Prefix 모드에서 실행할 때 워커 노드당 하나의 ENI만 필요로합니다. (최악의 경우)노드당 최대 15개의 미사용 IP를 감당할 수 있다면, 최신 Prefix 할당 네트워킹 모드를 사용하여 그에 따른 성능 및 효율성 향상을 실현하는 것을 권장합니다.

+

서브넷 예약을 사용하여 서브넷 파편화 (IPv4) 를 방지

+

EC2가 ENI에 /28 IPv4 Prefix를 할당할 경우, 해당 Prefix는 서브넷의 연속된 IP 주소 블록이어야 합니다. Prefix가 생성되는 서브넷이 파편화된 경우 (보조 IP 주소가 흩어져 있고 많이 사용되는 서브넷) Prefix 연결에 실패할 수 있으며 VPC CNI 로그에 다음과 같은 오류 메시지가 표시됩니다.

+
failed to allocate a private IP/Prefix address: InsufficientCidrBlocks: There are not enough free cidr blocks in the specified subnet to satisfy the request.
+
+

파편화를 방지하면서 Prefix를 생성할 수 있는 충분한 연속 공간을 확보하려면 VPC 서브넷 CIDR 예약을 사용하여 Prefix만 사용할 수 있도록 서브넷 내 IP 공간을 예약할 수 있습니다. 예약을 생성하면 VPC CNI 플러그인은 EC2 API를 호출하여 예약된 공간에서 자동으로 할당되는 Prefix를 할당합니다.

+

신규 서브넷을 만들고, Prefix를 위한 공간을 예약하고, VPC CNI를 사용하여 해당 서브넷에서 실행 중인 워커 노드에 Prefix를 할당하도록 설정하는 것을 권장합니다. 신규 서브넷이 VPC CNI Prefix 할당을 활성화한 상태에서 EKS 클러스터에서 실행되는 파드 전용으로 사용되는 경우, Prefix 예약 단계를 건너뛸 수 있습니다.

+

VPC CNI 다운그레이드를 피할 것

+

Prefix 모드는 VPC CNI 버전 1.9.0 이상에서 작동합니다. Prefix 모드를 활성화하고 Prefix를 ENI에 할당한 후에는 Amazon VPC CNI 추가 기능을 1.9.0 미만 버전으로 다운그레이드하지 않아야 합니다. VPC CNI를 다운그레이드하려면 노드를 삭제하고 다시 생성해야 합니다.

+

Prefix Delegation으로 전환하는 동안 모든 노드를 교체

+

기존 워커 노드를 순차적으로 교체하는 대신 신규 노드 그룹을 생성하여 사용 가능한 IP 주소 수를 늘리는 것을 권장합니다. 기존 노드를 모두 차단하고 drain하여 모든 기존 파드를 안전하게 제거합니다. 서비스 중단을 방지하려면 중요한 워크로드를 위해 프로덕션 클러스터에 Pod Disruption Budgets을 설정하는 것을 권장합니다. 신규 노드의 파드에는 ENI에 할당된 Prefix의 IP가 할당됩니다. 파드가 실행되고 있는지 확인한 후, 기존 노드와 노드 그룹을 삭제할 수 있습니다. 관리형 노드 그룹을 사용하는 경우, 설명된 단계에 따라 안전하게 노드 그룹 삭제를 진행합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/prefix-mode/index_windows/index.html b/ko/networking/prefix-mode/index_windows/index.html new file mode 100644 index 000000000..1d8265dc9 --- /dev/null +++ b/ko/networking/prefix-mode/index_windows/index.html @@ -0,0 +1,2305 @@ + + + + + + + + + + + + + + + + + + + + + + + Prefix 모드 - 윈도우 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

윈도우용 Prefix 모드

+

Amazon EKS에서 윈도우 호스트에서 실행되는 각 파드는 기본적으로 VPC 리소스 컨트롤러에 의해 보조 IP 주소가 할당됩니다. 이 IP 주소는 호스트의 서브넷에서 할당되어 VPC에서 라우팅이 가능한 주소입니다. Linux에서는 인스턴스에 연결된 각 ENI에 보조 IP 주소 또는 /28 CIDR(Prefix)로 채울 수 있는 여러 슬롯이 있습니다. 하지만 윈도우 호스트는 단일 ENI와 슬롯만 지원합니다. 보조 IP 주소만 사용하면 할당할 수 있는 IP 주소가 많더라도 윈도우 호스트에서 실행할 수 있는 파드 수가 인위적으로 제한될 수 있습니다.

+

특히 비교적 작은 인스턴스 유형을 사용하는 경우 윈도우 호스트에서 파드 밀도를 높이기 위해 윈도우 노드에 Prefix Delegation을 활성화할 수 있습니다. Prefix Delegation을 활성화하면 /28 IPv4 Prefix가 보조 IP 주소 대신 ENI 슬롯에 할당됩니다. Prefix Delegation은 amazon-vpc-cni 컨피그맵에 enable-windows-prefix-delegation: "true"항목을 추가하여 활성화할 수 있습니다. 해당 컨피그맵은 윈도우 지원을 활성화하기 위해 enable-windows-ipam: "true" 항목을 설정했던 것과 동일한 컨피그맵입니다.

+

EKS 사용자 가이드에 설명된 안내에 따라 윈도우 노드에 대해 Prefix Delegation 모드를 활성화합니다.

+

illustration of two worker subnets, comparing ENI secondary IPvs to ENIs with delegated prefixes

+

그림: 보조 IP 모드와 Prefix Delegation 모드의 비교

+

네트워크 인터페이스에 할당할 수 있는 최대 IP 주소 수는 인스턴스 유형과 크기에 따라 다릅니다. 네트워크 인터페이스에 할당된 각 Prefix는 가용 슬롯을 사용합니다. 예를 들어, c5.large 인스턴스는 네트워크 인터페이스당 10 슬롯으로 제한됩니다. 네트워크 인터페이스의 첫 번째 슬롯은 항상 인터페이스의 기본 IP 주소로 사용되므로 Prefix 및/또는 보조 IP 주소를 위한 슬롯이 9개만 남게 됩니다. 이러한 슬롯에 Prefix가 할당된 경우 노드는 (9 * 16) 144 IP 주소를 지원할 수 있지만 보조 IP 주소가 할당된 경우 9개의 IP 주소만 지원할 수 있습니다. 자세한 내용은 인스턴스 유형별 네트워크 인터페이스당 IP 주소네트워크 인터페이스에 Prefix 할당 설명서를 참조합니다.

+

워커 노드 초기화 중에 VPC 리소스 컨트롤러는 IP 주소의 웜 풀을 유지하여 파드 시작 속도를 높이기 위해 기본 ENI에 하나 이상의 Prefix를 할당합니다. 웜 풀에 보관할 Prefix 수는 amazon-vpc-cni 컨피그맵에서 다음 구성 매개변수를 구성하여 제어할 수 있습니다.

+
    +
  • warm-prefix-target, 현재 필요 수량을 초과하여 할당할 Prefix 수
  • +
  • warm-ip-target, 현재 필요 수량을 초과하여 할당할 IP 주소 수
  • +
  • minimum-ip-target, 언제든지 사용할 수 있는 최소 IP 주소 수
  • +
  • warm-ip-target 및/또는 minimum-ip-target 가 적용된 경우, warm-prefix-target를 오버라이드합니다.
  • +
+

노드에 더 많은 파드가 스케줄링되면 기존 ENI에 대해 추가 Prefix가 요청됩니다. 노드에 파드가 스케줄링되면 VPC 리소스 컨트롤러는 먼저 노드의 기존 Prefix에서 IPv4 주소를 할당하려고 시도합니다. 만약 불가한 경우, 서브넷에 필요한 용량이 있는 한도 내에서 신규 IPv4 Prefix가 요청됩니다.

+

flow chart of procedure for assigning IP to pod

+

그림: IPv4 주소를 Pod에 할당하는 동안의 워크플로

+

권장 사항

+

다음과 같은 경우 Prefix Delegation 사용

+

워커 노드에서 파드 밀도 문제가 발생하는 경우 Prefix Delegation을 사용합니다. 오류를 방지하려면 Prefix 모드로 마이그레이션하기 전에 서브넷에서 /28 Prefix의 연속된 주소 블록이 있는지 확인할 것을 권장합니다. 서브넷 예약 세부 정보는 “서브넷 예약을 사용하여 서브넷 파편화(IPv4) 방지” 섹션을 참조합니다.

+

기본적으로 윈도우 노드의 max-pods110으로 설정되어 있습니다. 대부분의 인스턴스 유형에서는 이 정도면 충분합니다. 해당 한도를 늘리거나 줄이려면 사용자 데이터(User data)의 부트스트랩 명령어에 다음을 추가하세요. +

-KubeletExtraArgs '--max-pods=example-value'
+
+윈도우 노드의 부트스트랩 구성 매개 변수에 대한 자세한 내용은 이 링크의 설명서를 참조합니다.

+

다음과 같은 경우 Prefix Delegation을 권장하지 않음

+

서브넷이 매우 파편화되어 있고 사용 가능한 IP 주소가 부족하여 /28 Prefix를 만들 수 없는 경우에는 Prefix 모드를 사용하지 마십시오. Prefix가 생성되는 서브넷이 파편화 된 경우(사용량이 많고 보조 IP 주소가 흩어져 있는 서브넷), Prefix 연결에 실패할 수 있습니다. 신규 서브넷을 만들고 Prefix를 예약하면 이 문제를 피할 수 있습니다.

+

IPv4 주소를 보존하도록 Prefix Delegation 매개 변수 구성

+

warm-prefix-target, warm-ip-targetminimum-ip-target은 Prefix를 사용한 사전 스케일링 및 동적 스케일링의 동작을 미세조정하는 데 사용할 수 있습니다. 기본적으로 다음과 같은 값이 사용됩니다. +

warm-ip-target: "1"
+minimum-ip-target: "3"
+
+이러한 구성 파라미터를 미세 조정하면 IP 주소 보존과 IP 주소 할당으로 인한 파드 지연 시간 감소 사이에서 최적의 균형을 이룰 수 있습니다. 이러한 구성 매개변수에 대한 자세한 내용은 이 링크의 설명서를 참조합니다.

+

서브넷 예약을 사용하여 서브넷 파편화 (IPv4) 를 방지

+

EC2가 ENI에 /28 IPv4 Prefix를 할당할 경우, 해당 Prefix는 서브넷의 연속된 IP 주소 블록이어야 합니다. Prefix가 생성되는 서브넷이 파편화된 경우 (보조 IP 주소가 흩어져 있고 많이 사용되는 서브넷) Prefix 연결에 실패할 수 있으며 다음과 같은 노드 이벤트가 표시됩니다. +

InsufficientCidrBlocks: The specified subnet does not have enough free cidr blocks to satisfy the request
+
+파편화를 방지하고 Prefix를 생성할 수 있는 충분한 연속 공간을 확보하려면 VPC 서브넷 CIDR 예약을 사용하여 Prefix만 사용할 수 있도록 서브넷 내 IP 공간을 예약합니다. 예약을 생성한 후에는 예약된 블록의 IP 주소가 다른 리소스에 할당되지 않습니다. 이렇게 하면 VPC 리소스 컨트롤러가 노드 ENI에 대한 할당 작업 호출 중에 사용 가능한 Prefix를 가져올 수 있습니다.

+

신규 서브넷을 만들고, Prefix를 위한 공간을 예약하고, 해당 서브넷에서 실행되는 워커 노드에 Prefix 할당을 활성화할 것을 권장합니다. 신규 서브넷이 Prefix Delegation이 활성화된 상태의 EKS 클러스터에서 실행 중인 파드 전용일 경우, Prefix 예약 단계를 건너뛸 수 있습니다.

+

보조 IP 모드에서 Prefix Delegation 모드로 또는 그 반대로 마이그레이션할 경우 모든 노드를 교체

+

기존 워커 노드를 순차적으로 교체하는 대신 신규 노드 그룹을 생성하여 사용 가능한 IP 주소 수를 늘릴 것을 권장합니다.

+

자체 관리형 노드 그룹을 사용하는 경우 마이그레이션 단계는 다음과 같습니다.

+
    +
  • 새 노드가 워크로드를 수용할 수 있도록 클러스터의 용량을 증설합니다.
  • +
  • 윈도우용 Prefix Delegation 기능을 활성화/비활성화합니다.
  • +
  • 기존 노드를 모두 차단하고 drain하여 기존 파드를 안전하게 제거합니다. 서비스 중단을 방지하려면 중요한 워크로드를 위해 프로덕션 클러스터에 Pod Disruption Budgets를 적용할 것을 권장합니다.
  • +
  • 파드가 실행되고 있는지 확인한 후에 이전 노드와 노드 그룹을 삭제 할 수 있습니다. 신규 노드의 파드에는 노드 ENI에 할당된 Prefix의 IPv4 주소가 할당됩니다.
  • +
+

관리형 노드 그룹을 사용하는 경우 마이그레이션 단계는 다음과 같습니다.

+
    +
  • 윈도우용 Prefix Delegation 기능을 활성화/비활성화합니다.
  • +
  • 이 링크에 설명된 단계를 따라 노드 그룹을 업데이트합니다. 이 절차는 위와 비슷한 단계를 수행하지만 EKS에서 관리됩니다.
  • +
+
+

Warning

+

노드의 모든 파드를 동일한 모드로 실행하도록 합니다.

+
+

윈도우의 경우, 보조 IP 모드와 Prefix Delegation 모드에서 동시에 파드를 실행하지 않는 것이 좋습니다. 이러한 상황은 윈도우 워크로드를 실행하는 상태에서 보조 IP 모드에서 Prefix Delegation 모드로 또는 그 반대로 마이그레이션할 때 발생할 수 있습니다.

+

이는 실행 중인 파드에 영향을 주지는 않지만, 노드의 IP 주소 용량과 관련하여 불일치가 발생할 수 있습니다. 예를 들어 보조 IPv4 주소용 슬롯이 14개 있는 t3.xlarge 노드를 예로 들어 보겠습니다. 10개의 파드를 실행하는 경우, ENI의 슬롯 10개가 보조 IP 주소로 사용됩니다. Prefix Delegation을 활성화하면 kube-api 서버에 광고되는 용량은 (슬롯 14개 * Prefix당 16개의 IP 주소) 244개가 되지만, 해당 시점의 실제 용량은 (남은 슬롯 4개 * Prefix당 16개 주소) 64개가 됩니다. 알려진 용량과 실제 용량(남은 슬롯)간의 이러한 불일치로 인해 할당에 사용할 수 있는 IP 주소보다 많은 파드를 실행하는 경우 문제가 발생할 수 있습니다.

+

다만, 위에서 설명한 마이그레이션 전략을 활용하여 파드를 보조 IP 주소에서 Prefix에서 얻은 주소로 안전하게 마이그레이션할 수 있습니다. 모드를 변경해도 파드는 정상적으로 계속 실행되며 다음과 같이 동작합니다.

+
    +
  • 보조 IP 모드에서 Prefix Delegation 모드로 전환할 때 실행 중인 파드에 할당된 보조 IP 주소는 해제되지 않습니다. 빈 슬롯에는 Prefix가 할당됩니다. 파드가 종료되면 사용하던 보조 IP와 슬롯이 해제됩니다.
  • +
  • Prefix Delegation 모드에서 보조 IP 모드로 전환할 경우, 해당 범위 내의 모든 IP가 더 이상 파드에 할당되지 않을 경우 Prefix가 해제됩니다. Prefix의 IP가 파드에 할당된 경우, 해당 Prefix는 파드가 종료될 때까지 유지됩니다.
  • +
+

Prefix Delegation 관련 디버깅 문제

+

이 링크의 디버깅 가이드를 활용하여 윈도우에서 직면하고 있는 Prefix Delegation와 관련된 문제를 자세히 살펴볼 수 있습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/sgpp/index.html b/ko/networking/sgpp/index.html new file mode 100644 index 000000000..eb4ded7ee --- /dev/null +++ b/ko/networking/sgpp/index.html @@ -0,0 +1,2438 @@ + + + + + + + + + + + + + + + + + + + + + + + 파드 보안 그룹 할당 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

파드용 보안 그룹 (Security Group for Pod, SGP)

+

AWS 보안 그룹은 인바운드 및 아웃바운드 트래픽을 제어하기 위해 EC2 인스턴스에 대한 가상 방화벽의 역할을 수행합니다. 기본적으로 Amazon VPC CNI는 노드의 ENI와 연결된 보안 그룹을 사용합니다. 따라서 모든 파드는 기본적으로 파드가 동작하는 노드와 동일한 보안 그룹을 공유하여 이용합니다.

+

아래 그림에서 볼 수 있듯이, 워커 노드에서 동작하는 모든 애플리케이션 파드는 RDS 데이터베이스 서비스에 액세스할 수 있습니다. (RDS 인바운드가 노드의 보안 그룹을 허용한다는 가정하에). 보안 그룹은 노드에서 실행되는 모든 파드에 적용되기 때문에 큰 그룹 단위로 보안 규칙이 적용됩니다. 파드용 보안 그룹 기능을 이용하면 각 Pod별로 보안 규칙을 설정할 수 있으며, 이는 세세한 보안 전략을 세울수 있도록 도와줍니다.

+

illustration of node with security group connecting to RDS +파드용 보안 그룹을 사용하면 공유 컴퓨팅 리소스에서 다양한 네트워크 보안 요구 사항을 가진 애플리케이션을 실행하여 컴퓨팅 효율성을 개선할 수 있습니다. EC2 보안 그룹과 함께 파드와 파드 사이 또는 파드에서 외부 AWS 서비스와 같은 여러 유형의 보안 규칙을 한 곳에서 정의하고 Kubernetes 네이티브 API를 사용하여 워크로드에 적용할 수 있습니다. 아래의 그림은 파드 수준에서 적용된 보안 그룹을 나타내고 있으며, 이런 보안 그룹이 애플리케이션 배포 및 노드 아키텍처를 간소화하는 방법을 보여줍니다. 이제 파드에서 Amazon RDS 데이터베이스에 액세스할 수 있습니다.

+

illustration of pod and node with different security groups connecting to RDS

+

VPC CNI에 대해 ENABLE_POD_ENI=true로 설정하여 파드에 대한 보안 그룹을 활성화할 수 있습니다. 활성화되면 EKS의 컨트롤 플레인에서 실행되는 "VPC 리소스 컨트롤러"가 "aws-k8s-trunk-eni"라는 트렁크 인터페이스를 생성하여 노드에 연결합니다. 트렁크 인터페이스는 인스턴스에 연결된 표준 네트워크 인터페이스 역할을 합니다. 트렁크 인터페이스를 관리하려면 Amazon EKS 클러스터와 함께 제공되는 클러스터 역할에 'AmazonEKSVPCResourceController' 관리형 정책을 추가해야 합니다.

+

또한 컨트롤러는 "aws-k8s-branch-eni"라는 브랜치 인터페이스를 생성하여 트렁크 인터페이스와 연결합니다. 파드는 SecurityGroupPolicy 커스텀 리소스를 사용하여 보안 그룹을 할당받고 브랜치 인터페이스와 연결됩니다. 보안 그룹은 네트워크 인터페이스 단위로 지정되므로, 이제 이런 추가 네트워크 인터페이스에서 특정 보안 그룹을 필요로 하는 파드를 스케줄링할 수 있습니다. 배포 사전 요구 사항을 포함하여 파드용 보안 그룹에 대한 EKS 사용자 가이드 섹션에서 좀더 자세한 내용들을 확인할 수 있습니다.

+

illustration of worker subnet with security groups associated with ENIs

+

분기 인터페이스 용량은 보조 IP 주소에 대한 기존 인스턴스 유형 제한에 추가됩니다. 보안 그룹을 사용하는 파드는 max-pods 공식에서 고려되지 않으며 파드에 보안 그룹을 사용하는 경우 max-pods 값을 높이는 것을 고려하거나 노드가 실제로 지원할 수 있는 것보다 적은 수의 파드를 실행해도 괜찮습니다.

+

m5.large에는 최대 9개의 분기 네트워크 인터페이스가 있을 수 있으며 표준 네트워크 인터페이스에 최대 27개의 보조 IP 주소가 할당될 수 있습니다. 아래 예에 표시된 것처럼 m5.large의 기본 max-pods는 29이며 EKS는 보안 그룹을 사용하는 Pod를 최대 Pod 수로 계산합니다. 노드의 max-pods를 변경하는 방법에 대한 지침은 EKS 사용자 가이드를 참조하십시오.

+

파드용 보안 그룹을 사용자 지정 네트워킹과 함께 사용하는 경우, ENIConfig에 지정된 보안 그룹 대신 파드용 보안 그룹에 정의된 보안 그룹이 사용됩니다. 따라서 사용자 지정 네트워킹이 활성화되면 파드별 보안 그룹을 사용하면서 보안 그룹 순서를 신중하게 살펴봐야 합니다.

+

권장 사항

+

활성 프로브를 위한 TCP Early Demux 기능 비활성화

+

활성 또는 준비 상태 프로브를 사용하는 경우, kubelet이 TCP를 통해 브랜치 네트워크 인터페이스의 파드에 연결할 수 있도록 TCP Early Dmux 기능을 비활성화 해야 합니다. 이는 엄격 모드에서만 필요합니다. 이 작업을 수행하려면 다음 명령을 실행합니다.

+
kubectl edit daemonset aws-node -n kube-system
+
+

초기화 컨테이너 섹션에서 DISABLE_TCP_EARLY_DEMUX의 값을 true로 변경합니다.

+

Pod용 보안 그룹을 사용하여 기존 AWS 구성을 활용하십시오.

+

보안 그룹을 사용하면 RDS 데이터베이스 또는 EC2 인스턴스와 같은 VPC 리소스에 대한 네트워크 액세스를 더 쉽게 제한할 수 있습니다. 파드용 보안 그룹의 분명한 이점 중 하나는 기존 AWS 보안 그룹 리소스를 재사용할 수 있다는 것입니다. +보안 그룹을 네트워크 방화벽으로 사용하여 AWS 서비스에 대한 액세스를 제한하는 경우, 브랜치 ENI를 사용하여 파드에 보안 그룹을 적용하는 것이 좋습니다. EC2 인스턴스에서 EKS로 앱을 전송하고 보안 그룹을 통해 다른 AWS 서비스에 대한 액세스를 제한하는 경우 파드용 보안 그룹을 사용하는 것을 고려해 보십시오.

+

파드용 보안 그룹 Enforcing 모드 구성

+

Amazon VPC CNI 플러그인 버전 1.11에는 POD_SECURITY_GROUP_ENFORCING_MODE("enforcing 모드")라는 새로운 설정이 추가되었습니다. 적용 모드는 파드에 적용되는 보안 그룹과 소스 NAT 활성화 여부를 모두 제어합니다. 적용 모드를 엄격 또는 표준으로 지정할 수 있습니다. 엄격이 기본값이며 ENABLE_POD_ENItrue로 설정된 VPC CNI의 이전 동작을 반영합니다.

+

Strict 모드에서는 분기 ENI 보안 그룹만 적용됩니다. 소스 NAT도 비활성화됩니다.

+

Standard 모드에서는 기본 ENI 및 분기 ENI(파드와 연결됨)와 연결된 보안 그룹이 적용됩니다. 네트워크 트래픽은 두 보안 그룹을 모두 준수해야 합니다.

+
+

Warning

+

모든 모드 변경은 새로 출시된 파드에만 영향을 미칩니다. 기존 파드는 파드가 생성될 때 구성된 모드를 사용한다. 고객은 트래픽 동작을 변경하려는 경우 보안 그룹과 함께 기존 파드를 재활용해야 합니다.

+
+

Enforcing 모드: 파드 및 노드 트래픽을 격리하기 위해 Strict 모드를 사용

+

기본적으로 Pod의 보안 그룹은 “Strict 모드”로 설정됩니다. 파드 트래픽을 나머지 노드 트래픽과 완전히 분리해야 하는 경우 이 설정을 사용한다. Strict 모드에서는 소스 NAT가 꺼져 브랜치 ENI 아웃바운드 보안 그룹을 사용할 수 있습니다.

+
+

Warning

+

모든 모드 변경은 새로 실행된 파드에만 영향을 미칩니다. 기존 파드는 파드가 생성될 때 구성된 모드를 사용합니다. 고객이 트래픽 동작을 변경하려면 보안 그룹이 포함된 기존 파드를 재활용해야 합니다.

+
+

Enforcing 모드: 다음 상황에서는 Standard 모드를 ​​사용

+

파드의 컨테이너에 표시되는 클라이언트 소스 IP

+

클라이언트 소스 IP를 파드의 컨테이너에 표시되도록 유지해야 하는 경우 POD_SECURITY_GROUP_ENFORCING_MODEstandard으로 설정하는 것이 좋습니다. Kubernetes 서비스는 클라이언트 소스 IP(기본 유형 클러스터) 보존을 지원하기 위해 externalTrafficPolicy=local을 지원합니다. 이제 표준 모드에서 externalTrafficPolicy가 Local로 설정된 인스턴스 대상을 사용하여 NodePort 및 LoadBalancer 유형의 Kubernetes 서비스를 실행할 수 있습니다. Local은 클라이언트 소스 IP를 유지하고 LoadBalancer 및 NodePort 유형 서비스에 대한 두 번째 홉을 방지합니다.

+

NodeLocal DNSCache 배포

+

파드에 보안 그룹을 사용하는 경우 NodeLocal DNSCache를 사용하는 파드를 지원하도록 표준 모드를 ​​구성합니다. NodeLocal DNSCache는 클러스터 노드에서 DNS 캐싱 에이전트를 DaemonSet으로 실행하여 클러스터 DNS 성능을 향상시킵니다. 이렇게 하면 DNS QPS 요구 사항이 가장 높은 파드가 로컬 캐시가 있는 로컬 kube-dns/CoreDNS를 쿼리하는 데 도움이 되어 대기 시간이 향상됩니다.

+

NodeLocal DNSCache는 노드에 대한 모든 네트워크 트래픽이 VPC로 진입하므로 strict 모드에서는 지원되지 않습니다.

+

Kubernetes 네트워크 정책 지원

+

연결된 보안 그룹이 있는 파드에 네트워크 정책을 사용할 때는 표준 시행 모드를 사용하는 것이 좋습니다.

+

클러스터에 속하지 않은 AWS 서비스에 대한 네트워크 수준 액세스를 제한하려면 파드용 보안 그룹을 활용하는 것이 좋습니다. 클러스터 내부 파드 간의 네트워크 트래픽(종종 East/West 트래픽이라고도 함)을 제한하려면 네트워크 정책을 고려하세요.

+

파드당 보안 그룹과의 비호환성 식별

+

Windows 기반 및 비 Nitro 인스턴스는 파드에 대한 보안 그룹을 지원하지 않습니다. 파드에서 보안 그룹을 활용하려면 인스턴스에 isTrunkingEnabled 태그를 지정해야 합니다. 파드가 VPC 내부 또는 외부의 AWS 서비스에 의존하지 않는 경우 네트워크 정책을 사용하여 보안 그룹이 아닌 파드 간의 액세스를 관리합니다.

+

파드당 보안 그룹을 사용하여 AWS 서비스에 대한 트래픽을 효율적으로 제어

+

EKS 클러스터 내에서 실행되는 애플리케이션이 VPC 내의 다른 리소스와 통신해야 하는 경우. RDS 데이터베이스를 구축한 다음 파드에 SG를 사용하는 것을 권장 드립니다. CIDR 또는 DNS 이름을 지정할 수 있는 정책 엔진이 있지만 VPC 내에 엔드포인트가 있는 AWS 서비스와 통신할 때는 덜 최적의 선택입니다.

+

이와 대조적으로 Kubernetes 네트워크 정책은 클러스터 내부 및 외부 모두에서 수신 및 송신 트래픽을 제어하기 위한 메커니즘을 제공합니다. 애플리케이션이 다른 AWS 서비스에 대한 종속성이 제한적인 경우 Kubernetes 네트워크 정책을 고려해야 합니다. SG와 같은 AWS 기본 의미 체계와 반대로 AWS 서비스에 대한 액세스를 제한하기 위해 CIDR 범위를 기반으로 송신 규칙을 지정하는 네트워크 정책을 구성할 수 있습니다. Kubernetes 네트워크 정책을 사용하여 파드 간(종종 East/West 트래픽이라고도 함) 및 파드와 외부 서비스 간의 네트워크 트래픽을 제어할 수 있습니다. Kubernetes 네트워크 정책은 OSI 레벨 3과 4에서 구현됩니다.

+

Amazon EKS를 사용하면 CalicoCilium. 기본적으로 네트워크 정책 엔진은 설치되지 않습니다. 설정 방법에 대한 지침은 해당 설치 가이드를 확인하세요. 네트워크 정책 사용 방법에 대한 자세한 내용은 EKS 보안 모범 사례를 참조하세요. DNS 호스트 이름 기능은 엔터프라이즈 버전의 네트워크 정책 엔진에서 사용할 수 있으며, 이는 Kubernetes 서비스/파드와 AWS 외부에서 실행되는 리소스 간의 트래픽을 제어하는 ​​데 유용할 수 있습니다. 또한 기본적으로 보안 그룹을 지원하지 않는 AWS 서비스에 대한 DNS 호스트 이름 지원을 고려할 수 있습니다.

+

AWS Loadbalancer Controller를 사용하도록 단일 보안 그룹에 태그 지정

+

많은 보안 그룹이 파드에 할당된 경우 Amazon EKS는 공유 또는 소유된 kubernetes.io/cluster/$name으로 단일 보안 그룹에 태그를 지정할 것을 권장합니다. 태그를 사용하면 AWS Loadbalancer Controller가 보안 그룹의 규칙을 업데이트하여 트래픽을 파드로 라우팅할 수 있습니다. 파드에 하나의 보안 그룹만 제공되는 경우 태그 할당은 선택 사항입니다. 보안 그룹에 설정된 권한은 추가되므로 로드밸런서 컨트롤러가 규칙을 찾고 조정하려면 단일 보안 그룹에 태그를 지정하는 것으로 충분합니다. 또한 보안 그룹에서 정의한 기본 할당량을 준수하는 데 도움이 됩니다.

+

아웃바운드 트래픽에 대한 NAT 구성

+

소스 NAT는 보안 그룹이 할당된 파드의 아웃바운드 트래픽에 대해 비활성화됩니다. 인터넷 액세스가 필요한 보안 그룹을 사용하는 파드의 경우 NAT 게이트웨이 또는 인스턴스로 구성된 프라이빗 서브넷에서 워커 노드를 시작하고 CNI에서 외부 SNAT를 활성화합니다.

+
kubectl set env daemonset -n kube-system aws-node AWS_VPC_K8S_CNI_EXTERNALSNAT=true
+
+

보안 그룹이 있는 파드를 프라이빗 서브넷에 배포

+

보안 그룹이 할당된 파드는 프라이빗 서브넷에 배포된 노드에서 실행되어야 합니다. 단 퍼블릭 서브넷에 배포된 보안 그룹이 할당된 파드는 인터넷에 액세스할 수 없습니다.

+

파드 스펙에서 terminationGracePeriodSeconds 부분 확인

+

파드 사양 파일에서 'terminationGracePeriodSeconds'가 0이 아닌지 확인하세요. (기본값 30초) 이는 Amazon VPC CNI가 워커 노드에서 파드 네트워크를 삭제하는 데 필수적입니다. 0으로 설정하면 CNI 플러그인이 호스트에서 파드 네트워크를 제거하지 않으며 분기 ENI가 효과적으로 정리되지 않습니다.

+

Fargate를 이용하는 파드용 보안 그룹 사용

+

Fargate에서 실행되는 파드의 보안 그룹은 EC2 워커 노드에서 실행되는 파드와 매우 유사하게 작동한다. 예를 들어 Fargate 파드에 연결하는 보안 그룹 정책에서 보안 그룹을 참조하기 전에 먼저 보안 그룹을 생성해야 합니다.기본적으로 보안 그룹 정책을 Fargate 파드에 명시적으로 할당하지 않으면 클러스터 보안 그룹이 모든 Fargate 파드에 할당됩니다. 단순화를 위해 Fagate Pod의 SecurityGroupPolicy에 클러스터 보안 그룹을 추가할 수도 있습니다. 그렇지 않으면 보안 그룹에 최소 보안 그룹 규칙을 추가해야 합니다. 설명 클러스터 API를 사용하여 클러스터 보안 그룹을 찾을 수 있습니다.

+
 aws eks describe-cluster --name CLUSTER_NAME --query 'cluster.resourcesVpcConfig.clusterSecurityGroupId'
+
+
cat >my-fargate-sg-policy.yaml <<EOF
+apiVersion: vpcresources.k8s.aws/v1beta1
+kind: SecurityGroupPolicy
+metadata:
+  name: my-fargate-sg-policy
+  namespace: my-fargate-namespace
+spec:
+  podSelector: 
+    matchLabels:
+      role: my-fargate-role
+  securityGroups:
+    groupIds:
+      - cluster_security_group_id
+      - my_fargate_pod_security_group_id
+EOF
+
+

최소 보안 그룹 규칙은 여기에 나와 있습니다. 이런 규칙을 통해 Fargate 파드는 kube-apiserver, kubelet, CoreDNS와 같은 클러스터 내 서비스와 통신할 수 있다. 또한 Fargate 파드와의 인바운드 및 아웃바운드 연결을 허용하는 규칙을 추가해야 합니다. 이렇게 하면 파드가 VPC의 다른 파드나 리소스와 통신할 수 있게 된다. 또한 Fargate가 Amazon ECR 또는 DockerHub와 같은 다른 컨테이너 레지스트리에서 컨테이너 이미지를 가져오도록 하는 규칙을 포함해야 합니다. 자세한 내용은 AWS 일반 참조의 AWS IP 주소 범위를 참조하십시오.

+

아래 명령을 사용하여 Fargate Pod에 적용된 보안 그룹을 찾을 수 있습니다.

+
kubectl get pod FARGATE_POD -o jsonpath='{.metadata.annotations.vpc\.amazonaws\.com/pod-eni}{"\n"}'
+
+

위 명령의 ENI ID를 적어 둡니다.

+
aws ec2 describe-network-interfaces --network-interface-ids ENI_ID --query 'NetworkInterfaces[*].Groups[*]'
+
+

새 보안 그룹을 적용하려면 기존 Fargate 파드를 삭제하고 다시 만들어야 합니다. 예를 들어 다음 명령은 example-app 배포를 시작합니다. 특정 파드를 업데이트하려면 아래 명령어에서 네임스페이스와 디플로이먼트 이름을 변경할 수 있습니다.

+
kubectl rollout restart -n example-ns deployment example-pod
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/subnets/index.html b/ko/networking/subnets/index.html new file mode 100644 index 000000000..45b66b86e --- /dev/null +++ b/ko/networking/subnets/index.html @@ -0,0 +1,2665 @@ + + + + + + + + + + + + + + + + + + + + + + + VPC 및 서브넷 고려사항 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

VPC 및 서브넷 고려 사항

+

EKS 클러스터를 운영하려면 쿠버네티스 네트워킹 외에도 AWS VPC 네트워킹에 대한 지식이 필요합니다.

+

VPC를 설계하거나 기존 VPC에 클러스터를 배포하기 전에 EKS 컨트롤 플레인 통신 메커니즘을 이해할 것을 권장합니다.

+

EKS에서 사용할 VPC와 서브넷을 설계할 때는 클러스터 VPC 고려 사항Amazon EKS 보안 그룹 고려 사항을 참조합니다.

+

개요

+

EKS 클러스터 아키텍처

+

EKS 클러스터는 두 개의 VPC로 구성됩니다.

+
    +
  • 쿠버네티스 컨트롤 플레인을 호스팅하는 AWS 관리형 VPC. 이 VPC는 고객 계정에 표시되지 않습니다.
  • +
  • 쿠버네티스 노드를 호스팅하는 고객 관리형 VPC. 여기에서 컨테이너는 물론 클러스터에서 사용하는 로드 밸런서와 같은 기타 고객 관리형 AWS 인프라가 실행됩니다. 이 VPC는 고객 계정에 표시됩니다. 클러스터를 생성하기 전, 고객 관리형 VPC를 생성해야 합니다. 사용자가 VPC를 제공하지 않을 경우 eksctl이 VPC를 생성합니다.
  • +
+

고객 VPC의 노드는 AWS VPC의 관리형 API 서버 엔드포인트에 연결할 수 있어야 합니다. 이를 통해 노드가 쿠버네티스 컨트롤 플레인에 등록되고 애플리케이션 파드를 실행하라는 요청을 수신할 수 있습니다.

+

노드는 (a) EKS 퍼블릭 엔드포인트 또는 (b) EKS에서 관리하는 교차 계정 Elastic Network Interface(X-ENI)를 통해 EKS 컨트롤 플레인에 연결됩니다. 클러스터를 생성할 때는 최소 두 개의 VPC 서브넷을 지정해야 합니다. EKS는 클러스터 생성 시 지정된 각 서브넷(클러스터 서브넷이라고도 함)에 X-ENI를 배치합니다. 쿠버네티스 API 서버는 이러한 교차 계정 ENI를 사용하여 고객 관리형 클러스터 VPC 서브넷에 배포된 노드와 통신합니다.

+

general illustration of cluster networking, including load balancer, nodes, and pods.

+

노드를 시작하면 EKS 부트스트랩 스크립트가 실행되고 쿠버네티스 노드 구성 파일이 설치됩니다. 각 인스턴스의 부팅 프로세스의 일부로써 컨테이너 런타임 에이전트, kubelet 및 쿠버네티스 노드 에이전트가 시작됩니다.

+

노드를 등록하기 위해 kubelet은 쿠버네티스 클러스터 엔드포인트에 접속합니다. VPC 외부의 퍼블릭 엔드포인트 또는 VPC 내의 프라이빗 엔드포인트와의 연결을 설정합니다. Kubelet은 API 명령을 수신하고 엔드포인트에 상태 업데이트 및 하트비트를 정기적으로 제공합니다.

+

EKS 컨트롤 플레인 통신

+

EKS에는 클러스터 엔드포인트에 대한 접근을 제어하는 두 가지 방법이 있습니다. 엔드포인트 접근 제어를 통해 엔드포인트가 퍼블릭 인터넷을 통해 접근할 수 있는지 아니면 VPC를 통해서만 접근할 수 있는지 여부를 선택할 수 있습니다. 퍼블릭 엔드포인트(기본값), 프라이빗 엔드포인트 또는 둘 다를 한 번에 활성화 할 수 있습니다.

+

클러스터 API 엔드포인트의 구성은 노드가 컨트롤 플레인과 통신하기 위한 경로를 결정합니다. 참고로 이러한 엔드포인트 설정은 EKS 콘솔 또는 API를 통해 언제든지 변경할 수 있습니다.

+

퍼블릭 엔드포인트

+

해당 구성은 신규 Amazon EKS 클러스터의 기본 동작입니다. 클러스터의 퍼블릭 엔드포인트만 활성화된 경우 클러스터의 VPC 내에서 시작된 쿠버네티스 API 요청(예: 워커노드에서 컨트롤 플레인으로의 통신)은 VPC 외부로 가지만 Amazon 네트워크 외부로는 가지 않습니다. 노드가 컨트롤 플레인에 연결되려면 퍼블릭 IP 주소 및 인터넷 게이트웨이에 대한 경로 또는 NAT 게이트웨이의 퍼블릭 IP 주소를 사용할 수 있는 NAT 게이트웨이에 대한 경로가 있어야 합니다.

+

퍼블릭 및 프라이빗 엔드포인트

+

퍼블릭 엔드포인트와 프라이빗 엔드포인트가 모두 활성화되면 VPC 내의 쿠버네티스 API 요청이 VPC 내의 X-ENI를 통해 컨트롤 플레인과 통신합니다. 클러스터 API 서버는 인터넷에서 액세스할 수 있습니다.

+

프라이빗 엔드포인트

+

프라이빗 엔드포인트만 활성화된 경우 인터넷에서 API 서버에 공개적으로 액세스할 수 없습니다. 클러스터 API 서버로 향하는 모든 트래픽은 클러스터의 VPC 또는 연결된 네트워크 안에서 들어와야 합니다. 노드는 VPC 내의 X-ENI를 통해 API 서버와 통신합니다. 단, 클러스터 관리 도구는 프라이빗 엔드포인트에 액세스할 수 있어야 합니다. Amazon VPC 외부에서 프라이빗 Amazon EKS 클러스터 엔드포인트에 연결하는 방법에 대해 자세히 알아봅니다.

+

참고로 클러스터의 API 서버 엔드포인트는 퍼블릭 DNS 서버에서 VPC의 프라이빗 IP 주소로 리졸브됩니다. 과거에는 VPC 내에서만 엔드포인트를 리졸브할 수 있었습니다.

+

VPC 구성

+

Amazon VPC는 IPv4 및 IPv6 주소를 지원합니다. Amazon EKS는 기본적으로 IPv4를 지원합니다. VPC에는 IPv4 CIDR 블록이 연결되어 있어야 합니다. 선택적으로 여러 IPv4 Classless Inter-Domain Routing(CIDR) 블록과 여러 IPv6 CIDR 블록을 VPC에 연결할 수 있습니다. VPC를 생성할 경우 RFC 1918에 지정된 프라이빗 IPv4 주소 범위에서 VPC에 대한 IPv4 CIDR 블록을 지정해야 합니다. 허용되는 블록 크기는 /16 Prefix(65,536개의 IP 주소)와 /28 Prefix(16개의 IP 주소) 사이입니다.

+

신규 VPC를 만들 때는 하나의 IPv6 CIDR 블록을 연결할 수 있으며, 기존 VPC를 변경할 때는 최대 5개까지 연결할 수 있습니다. IPv6 VPC의 CIDR 블록의 Prefix 길이는 1조 개 이상의 IP 주소를 가진 /64로 고정됩니다. Amazon에서 관리하는 IPv6 주소 풀에서 IPv6 CIDR 블록을 요청할 수 있습니다.

+

Amazon EKS 클러스터는 IPv4와 IPv6를 모두 지원합니다. 기본적으로 EKS 클러스터는 IPv4 IP를 사용합니다. 클러스터 생성 시 IPv6을 지정하면 IPv6 클러스터를 사용할 수 있습니다. IPv6 클러스터에는 듀얼 스택 VPC와 서브넷이 필요합니다.

+

Amazon EKS는 클러스터를 생성하는 동안 서로 다른 가용 영역에 있는 서브넷을 두 개 이상 사용할 것을 권장합니다. 클러스터를 생성할 때 전달하는 서브넷을 클러스터 서브넷이라고 합니다. 클러스터를 생성할 때 Amazon EKS는 지정한 서브넷에 최대 4개의 교차 계정(x-account 또는 x-ENIs) ENI를 생성합니다.x-ENI는 항상 배포되며 로그 전송, 실행 및 프록시와 같은 클러스터 관리 트래픽에 사용됩니다. 전체 VPC 및 서브넷 요구 사항 세부 정보는 EKS 사용자 가이드를 참조합니다.

+

쿠버네티스 워커 노드는 클러스터 서브넷에서 실행할 수 있지만 권장되지는 않습니다. 클러스터 업그레이드 중에 Amazon EKS는 클러스터 서브넷에 추가 ENI를 프로비저닝합니다. 클러스터가 확장되면 워커 노드와 파드가 클러스터 서브넷에서 가용 IP를 사용할 수 있습니다. 따라서 사용 가능한 IP를 충분히 확보하려면 /28 넷마스크가 있는 전용 클러스터 서브넷 사용을 고려할 수 있습니다.

+

쿠버네티스 워커 노드는 퍼블릭 또는 프라이빗 서브넷에서 실행할 수 있습니다. 서브넷이 퍼블릭인지 프라이빗인지는 서브넷 내의 트래픽이 인터넷 게이트웨이를 통해 라우팅되는지 여부를 뜻합니다. 퍼블릭 서브넷에는 인터넷 게이트웨이를 통해 인터넷으로 라우팅되는 라우트 테이블 항목이 있지만 프라이빗 서브넷에는 없습니다.

+

다른 곳에서 시작하여 노드에 도착하는 트래픽을 인그레스(ingress)라고 합니다. 노드에서 시작하여 네트워크 외부로 가는 트래픽을 이그레스(egress)라고 합니다. 인터넷 게이트웨이로 구성된 서브넷 내에 퍼블릭 또는 Elastic IP Address(EIP)를 가진 노드는 VPC 외부로부터의 인그레스를 허용합니다. 프라이빗 서브넷에는 일반적으로 NAT 게이트웨이가 포함되어 있는데, 이 게이트웨이는 노드로부터의 트래픽이 VPC를 나가는 것(이그레스)은 허용하면서 VPC 내에서 노드로의 인그레스 트래픽만 허용합니다.

+

IPv6 환경에서는 모든 주소를 인터넷으로 라우팅할 수 있습니다. 노드 및 파드와 연결된 IPv6 주소는 퍼블릭입니다. 프라이빗 서브넷은 VPC에 egress-only internet gateways (EIGW)를 구성하여 아웃바운드 트래픽을 허용하는 동시에 들어오는 트래픽은 모두 차단하는 방식으로 지원됩니다. IPv6 서브넷 구현의 모범 사례는 VPC 사용자 가이드에서 확인할 수 있습니다.

+

다음과 같은 세 가지 방법으로 VPC와 서브넷을 구성할 수 있습니다.

+

퍼블릭 서브넷만 사용

+

동일한 퍼블릭 서브넷에서 노드와 인그레스 리소스(예: 로드 밸런서)가 모두 생성됩니다. 퍼블릭 서브넷에 kubernetes.io/role/elb 태그를 지정하여 인터넷에 연결된 로드 밸런서를 구성합니다. 해당 구성에서는 클러스터 엔드포인트를 퍼블릭, 프라이빗 또는 둘 다 (퍼블릭 및 프라이빗)로 구성할 수 있습니다.

+

프라이빗 및 퍼블릭 서브넷 사용

+

노드는 프라이빗 서브넷에서 생성되는 반면, 인그레스 리소스는 퍼블릭 서브넷에서 인스턴스화됩니다. 클러스터 엔드포인트에 대한 퍼블릭, 프라이빗 또는 둘 다(퍼블릭 및 프라이빗) 액세스를 사용하도록 설정할 수 있습니다. 클러스터 엔드포인트의 구성에 따라 노드 트래픽은 NAT 게이트웨이 또는 ENI를 통해 들어옵니다.

+

프라이빗 서브넷만 사용

+

노드와 인그레스 모두 프라이빗 서브넷에서 생성됩니다. kubernetes.io/role/internal-elb 서브넷 태그를 사용하여 내부용 로드 밸런서를 구성합니다. 클러스터의 엔드포인트에 접근하려면 VPN 연결이 필요합니다. EC2와 모든 Amazon ECR 및 S3 리포지토리에 대해 AWS PrivateLink를 활성화해야 합니다. 클러스터의 프라이빗 엔드포인트만 활성화해야 합니다. 프라이빗 클러스터를 프로비저닝하기 전 EKS 프라이빗 클러스터 요구 사항을 읽어볼 것을 권장합니다.

+

VPC간 통신

+

여러 개의 VPC와 이러한 VPC에 배포된 별도의 EKS 클러스터들이 필요한 경우가 많이 있습니다.

+

Amazon VPC Lattice를 사용하면 여러 VPC와 계정에서 서비스를 일관되고 안전하게 연결할 수 있습니다(VPC 피어링, AWS PrivateLink 또는 AWS Transit Gateway와 같은 서비스에서 추가 연결을 제공할 필요 없음). 여기에서 자세한 내용을 확인할 수 있습니다.

+

Amazon VPC Lattice, traffic flow

+

Amazon VPC Lattice는 IPv4 및 IPv6의 링크 로컬 주소 공간에서 작동하며, IPv4 주소가 겹칠 수 있는 서비스 간의 연결을 제공합니다. 운영 효율성을 위해 EKS 클러스터와 노드를 겹치지 않는 IP 범위에 배포할 것을 권장합니다. 인프라에 IP 범위가 겹치는 VPC가 포함된 경우에는 그에 맞게 네트워크를 설계해야 합니다. 라우팅 가능한 RFC1918 IP 주소를 유지하면서 중복되는 CIDR 문제를 해결하기 위해 프라이빗 NAT 게이트웨이 또는 사용자 지정 네트워킹 모드에서 transit gateway와 함께 VPC CNI를 사용하여 EKS의 워크로드를 통합하는 것을 권장합니다.

+

Private Nat Gateway with Custom Networking, traffic flow

+

서비스 제공자이고 별도의 계정으로 고객 VPC와 쿠버네티스 서비스 및 인그레스(ALB 또는 NLB)를 공유하려는 경우, 엔드포인트 서비스라고도 불리는 AWS PrivateLink 활용을 고려합니다.

+

여러 계정에서의 VPC 공유

+

많은 기업에서 AWS 조직 내 여러 AWS 계정의 네트워크 관리를 간소화하고, 비용을 절감하고, 보안을 개선하기 위한 수단으로 공유 Amazon VPC를 도입합니다. 이들은 AWS Resource Access Manager(RAM)를 활용하여 지원되는 AWS 리소스를 개별 AWS 계정, 조직 단위(OU) 또는 전체 AWS 조직과 안전하게 공유합니다.

+

AWS RAM을 사용하여 다른 AWS 계정의 공유용 VPC 서브넷에 Amazon EKS 클러스터, 관리형 노드 그룹 및 기타 지원 AWS 리소스(예: 로드밸런서, 보안 그룹, 엔드포인트 등)를 배포할 수 있습니다. 아래 그림은 상위 수준 아키텍처의 예를 보여줍니다. 이를 통해 중앙 네트워크 팀은 VPC, 서브넷 등과 같은 네트워킹 구조를 제어하고, 동시에 애플리케이션 또는 플랫폼 팀은 각자의 AWS 계정에 Amazon EKS 클러스터를 배포할 수 있습니다. 이 시나리오에 대한 전체 설명은 이 github 저장소에서 확인할 수 있습니다.

+

Deploying Amazon EKS in VPC Shared Subnets across AWS Accounts.

+

공유 서브넷 사용 시 고려 사항

+
    +
  • +

    Amazon EKS 클러스터와 워커 노드는 모두 동일한 VPC의 공유 서브넷 내에서 생성할 수 있습니다. Amazon EKS는 여러 VPC에서의 클러스터 생성을 지원하지 않습니다.

    +
  • +
  • +

    Amazon EKS는 AWS VPC 보안 그룹(SG)을 사용하여 쿠버네티스 컨트롤 플레인과 클러스터의 워커 노드 사이의 트래픽을 제어합니다. 또한 보안 그룹은 워커 노드, 기타 VPC 리소스 및 외부 IP 주소 간의 트래픽을 제어하는 데에도 사용됩니다. 애플리케이션/참여자(participant) 계정에서 이러한 보안 그룹을 생성해야 합니다. 파드에 사용할 보안 그룹도 참여자 계정에 있는지 확인합니다. 보안 그룹 내에서 인바운드 및 아웃바운드 규칙을 구성하여 중앙 VPC 계정에 있는 보안 그룹에서 송수신되는 필요한 트래픽을 허용할 수 있습니다.

    +
  • +
  • +

    Amazon EKS 클러스터가 있는 참여자 계정 내에 IAM 역할 및 관련 정책을 생성합니다. 이러한 IAM 역할 및 정책은 Amazon EKS에서 관리하는 쿠버네티스 클러스터와 Fargate에서 실행되는 노드 및 파드에 필요한 권한을 부여하기 위해 반드시 필요합니다. 이 권한을 통해 Amazon EKS는 사용자를 대신하여 다른 AWS 서비스를 호출할 수 있습니다.

    +
  • +
  • +

    다음 접근 방식에 따라 쿠버네티스 파드에서 Amazon S3 버킷, Dynamodb 테이블 등과 같은 AWS 리소스의 계정 간 액세스를 허용할 수 있습니다.

    +
      +
    • +

      리소스 기반 정책 접근 방식: AWS 서비스가 리소스 정책을 지원하는 경우 적절한 리소스 기반 정책을 추가하여 쿠버네티스 파드에 할당된 IAM 역할에 대한 계정 간 액세스를 허용할 수 있습니다. 이 시나리오에서는 OIDC 공급자, IAM 역할 및 권한 정책이 애플리케이션 계정에 존재하게 됩니다. 리소스 기반 정책을 지원하는 AWS 서비스를 찾으려면 IAM과 함께 작동하는 AWS 서비스를 참조하고 리소스 기반 열에서 '예'라고 표시된 서비스를 찾아보십시오.

      +
    • +
    • +

      OIDC 공급자 접근 방식: OIDC 공급자, IAM 역할, 권한 및 신뢰 정책과 같은 IAM 리소스는 리소스가 있는 다른 참여자 AWS 계정에서 생성됩니다. 이러한 역할은 애플리케이션 계정의 쿠버네티스 파드에 할당되어 계정 간 리소스에 액세스할 수 있도록 합니다. 이 접근 방식에 대한 자세한 내용은 쿠버네티스 서비스 어카운트를 위한 교차 계정 간 IAM 역할 블로그를 참조합니다.

      +
    • +
    +
  • +
  • +

    Amazon Elastic Loadbalancer(ELB) 리소스(ALB 또는 NLB)를 배포하여 애플리케이션 또는 중앙 네트워킹 계정의 쿠버네티스 파드로 트래픽을 라우팅할 수 있습니다. 중앙 네트워킹 계정에 ELB 리소스를 배포하는 방법에 대한 자세한 안내는 교차 계정 로드 밸런서를 통해 Amazon EKS 파드 노출안내를 참조합니다. 이 옵션은 로드 밸런서 리소스의 보안 구성에 대한 모든 권한을 중앙 네트워킹 계정에 부여하므로 유연성이 향상됩니다.

    +
  • +
  • +

    Amazon VPC CNI의 사용자 지정 네트워킹 기능(custom networking feature)을 사용하는 경우 중앙 네트워킹 계정에 나열된 가용 영역(AZ) ID 매핑을 사용하여 각각의 ENIConfig를 생성해야 합니다. 이는 물리적 AZ를 각 AWS 계정의 AZ 이름에 무작위로 매핑하기 때문입니다.

    +
  • +
+

보안 그룹

+

보안 그룹은 연결된 리소스에 들어오거나 나가는 것이 허용되는 트래픽을 제어합니다. Amazon EKS는 보안 그룹을 사용하여 컨트롤 플레인과 노드간의 통신을 관리합니다. 클러스터를 생성하면 Amazon EKS는 eks-cluster-sg-my-cluster-uniqueID라는 보안 그룹을 생성합니다. EKS는 이러한 보안 그룹을 관리형 ENI 및 노드에 연결합니다. 기본 규칙을 사용하면 클러스터와 노드 간에 모든 트래픽이 자유롭게 전달되고, 모든 아웃바운드 트래픽이 모든 목적지로 전달되도록 허용합니다.

+

클러스터를 생성할 때 자체 보안 그룹을 지정할 수 있습니다. 자체 보안 그룹을 지정하는 경우 보안 그룹 권장 사항을 참조합니다.

+

권장 사항

+

다중 AZ 배포 고려

+

AWS 리전은 물리적으로 분리되고 격리된 여러 가용 영역(AZ)을 제공하며, 이러한 가용 영역은 지연 시간이 짧고 처리량이 높으며 중복성이 높은 네트워킹으로 연결됩니다. 가용 영역을 활용하여 가용 영역 간에 중단 없이 자동으로 장애 조치되는 애플리케이션을 설계하고 운영할 수 있습니다. Amazon EKS는 EKS 클러스터를 여러 가용 영역에 배포할 것을 강력히 권장합니다. 클러스터를 생성할 때 최소 두 개의 가용 영역에 서브넷을 지정하는 것을 고려합니다.

+

노드에서 실행되는 Kubelet은 topology.kubernetes.io/region=us-west-2, topology.kubernetes.io/zone=us-west-2d와 같은 레이블을 노드 오브젝트에 자동으로 추가합니다. 노드 레이블을 Pod topology spread constraints와 함께 사용하여 파드가 여러 영역에 분산되는 방식을 제어할 것을 권장합니다. 이러한 힌트를 통해 쿠버네티스 스케줄러가 예상 가용성을 높이기 위해 파드를 배치하여 상관 관계가 있는 장애가 전체 워크로드에 영향을 미칠 위험을 줄일 수 있습니다. 노드 셀렉터 및 AZ 분산 제약 조건의 예를 보려면 파드에 노드 할당을 참조합니다.

+

노드를 생성할 때 서브넷 또는 가용 영역을 정의할 수 있습니다. 서브넷이 구성되지 않은 경우 노드는 클러스터 서브넷에 배치됩니다. 관리형 노드 그룹에 대한 EKS 지원은 가용 용량을 기준으로 여러 가용 영역에 노드를 자동으로 분산합니다. 워크로드가 topology spread constraints를 정의하는 경우 Karpenter는 노드를 지정된 AZ로 확장하여 AZ 분산 배치를 준수합니다.

+

AWS Elastic Loadbalancer는 쿠버네티스 클러스터의 AWS 로드 밸런서 컨트롤러에 의해 관리됩니다. 쿠버네티스 인그레스 리소스를 위한 애플리케이션 로드 밸런서(ALB)와 로드밸런서 유형의 쿠버네티스 서비스를 위한 네트워크 로드 밸런서(NLB)를 프로비저닝합니다. Elastic Loadbalancer 컨트롤러는 태그를 사용하여 서브넷을 검색합니다. ELB 컨트롤러가 인그레스 리소스를 성공적으로 프로비저닝하려면 최소 두 개의 가용 영역 (AZ)이 필요합니다. 지리적 이중화의 안전성과 안정성을 활용하기 위해 최소 두 개의 AZ에 서브넷을 설정할 것을 권장합니다.

+

프라이빗 서브넷에 노드 배포

+

프라이빗 서브넷과 퍼블릭 서브넷을 모두 포함하는 VPC는 쿠버네티스 워크로드를 EKS에 배포하는 데 가장 적합한 방법입니다. 서로 다른 두 가용 영역에 최소 두 개의 퍼블릭 서브넷과 두 개의 프라이빗 서브넷을 설정할 것을 고려합니다. 퍼블릭 서브넷의 라우팅 테이블에는 인터넷 게이트웨이에 대한 경로가 포함되어 있습니다. 파드는 NAT 게이트웨이를 통해 인터넷과 상호작용할 수 있습니다. IPv6 환경(EIGW)에서의 프라이빗 서브넷은 외부 전용 인터넷 게이트웨이를 통해 지원됩니다.

+

프라이빗 서브넷에서 노드를 인스턴스화하면 노드에 대한 트래픽 제어를 최대화 할 수 있으며 대부분의 쿠버네티스 애플리케이션에 적합합니다. 인그레스 리소스(예: 로드 밸런서)는 퍼블릭 서브넷에서 인스턴스화되고 프라이빗 서브넷에서 작동하는 파드로 트래픽을 라우팅합니다.

+

엄격한 보안 및 네트워크 격리가 필요한 경우 프라이빗 전용 모드를 고려합니다. 이 구성에서는 세 개의 프라이빗 서브넷이 AWS 리전 내 VPC의 서로 다른 가용 영역에 배포됩니다. 서브넷에 배포된 리소스는 인터넷에 액세스할 수 없으며 인터넷에서 서브넷의 리소스로도 액세스할 수 없습니다. 쿠버네티스 애플리케이션이 다른 AWS 서비스에 액세스할 수 있으려면 PrivateLink 인터페이스 및/또는 게이트웨이 엔드포인트를 구성해야 합니다. AWS 로드 밸런서 컨트롤러를 사용하여 내부 로드 밸런서가 트래픽을 파드로 리디렉션하도록 설정할 수 있습니다. 컨트롤러가 로드 밸런서를 프로비저닝하려면 프라이빗 서브넷에 (`kubernetes.io/role/internal-elb: 1) 태그를 지정해야 합니다. 노드를 클러스터에 등록하려면 클러스터 엔드포인트를 프라이빗 모드로 설정해야 합니다. 전체 요구 사항 및 고려 사항은 프라이빗 클러스터 가이드를 참조합니다.

+

클러스터 엔드포인트의 퍼블릭 및 프라이빗 모드 고려

+

Amazon EKS는 퍼블릭 전용, 퍼블릭 및 프라이빗, 프라이빗 전용 클러스터 엔드포인트 모드를 제공합니다. 기본 모드는 퍼블릭 전용이지만 클러스터 엔드포인트를 퍼블릭 및 프라이빗 모드로 구성하는 것을 권장합니다. 이 옵션을 사용하면 클러스터 VPC 내에서의 쿠버네티스 API 호출(예: 노드와 컨트롤 플레인 간 통신)에 프라이빗 VPC 엔드포인트를 활용하고 트래픽이 클러스터의 VPC 내에 유지되도록 할 수 있습니다. 반면 클러스터 API 서버는 인터넷을 통해 연결할 수 있습니다. 하지만 퍼블릭 엔드포인트를 사용할 수 있는 CIDR 블록은 제한하는 것이 좋습니다. CIDR 블록 제한을 포함하여 퍼블릭 및 프라이빗 엔드포인트 액세스를 구성하는 방법을 알아봅니다.

+

보안 및 네트워크 격리가 필요한 경우 프라이빗 전용 엔드포인트를 사용하는 것을 권장합니다. EKS 사용자 가이드에 제시된 옵션 중 하나를 사용하여 API 서버에 프라이빗하게 연결할 것을 권장합니다.

+

보안 그룹을 신중하게 구성

+

Amazon EKS는 사용자 지정 보안 그룹 사용을 지원합니다. 모든 사용자 지정 보안 그룹은 노드와 쿠버네티스 컨트롤 플레인 간의 통신을 허용해야 합니다. 조직 내에서 개방형 통신을 허용하지 않는 경우 포트 요구 사항을 확인하고 규칙을 수동으로 구성합니다.

+

EKS는 클러스터 생성 중에 제공하는 사용자 지정 보안 그룹을 관리형 인터페이스(X-ENI)에 적용합니다. 하지만 사용자 지정 보안그룹이 노드와 즉시 연결되지는 않습니다. 노드 그룹을 생성할 때는 수동으로 사용자 지정 보안 그룹을 연결할 것을 권장합니다. 노드 오토스케일링 중 사용자 지정 보안 그룹의 Karpenter 노드 템플릿 검색을 활성화하려면 securityGroupSelectorTerms를 활성화할 것을 고려합니다.

+

모든 노드 간 통신 트래픽을 허용하는 보안 그룹을 생성하는 것을 강력히 권장합니다. 부트스트랩 프로세스 중에 노드가 클러스터 엔드포인트에 액세스하려면 아웃바운드 인터넷 연결이 필요합니다. 온프레미스 연결 및 컨테이너 레지스트리 액세스와 같은 외부 액세스 요구 사항을 평가하고 규칙을 적절하게 설정합니다. 변경 사항을 프로덕션에 적용하기 전에 개발 환경에서 네트워크 연결을 주의 깊게 확인할 것을 권장합니다.

+

각 가용 영역에 NAT 게이트웨이 배포

+

프라이빗 서브넷(IPv4 및 IPv6)에 노드를 배포하는 경우 각 가용 영역(AZ)에 NAT 게이트웨이를 생성하여 가용 영역에 독립적인 아키텍처를 보장하고 AZ 간 비용을 절감하는 것을 고려합니다. AZ의 각 NAT 게이트웨이는 이중화로 구현됩니다.

+

Cloud9을 사용하여 프라이빗 클러스터에 액세스

+

AWS Cloud9는 AWS Systems Manager를 사용하여 인그레스 액세스 없이 프라이빗 서브넷에서 안전하게 실행할 수 있는 웹 기반 IDE입니다. Cloud9 인스턴스에서 이그레스를 비활성화할 수도 있습니다. Cloud9를 사용하여 프라이빗 클러스터와 서브넷에 액세스하는 방법에 대해 자세히 알아보십시오.

+

illustration of AWS Cloud9 console connecting to no-ingress EC2 instance.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/networking/vpc-cni/index.html b/ko/networking/vpc-cni/index.html new file mode 100644 index 000000000..31fdd1066 --- /dev/null +++ b/ko/networking/vpc-cni/index.html @@ -0,0 +1,2410 @@ + + + + + + + + + + + + + + + + + + + + + + + Amazon VPC CNI - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Amazon VPC CNI

+ + +

Amazon EKS는 Amazon VPC 컨테이너 네트워크 인터페이스(VPC CNI) 플러그인을 통해 클러스터 네트워킹을 구현합니다. CNI 플러그인을 사용하면 쿠버네티스 파드가 VPC 네트워크에서와 동일한 IP 주소를 가질 수 있습니다. 구체적으로는 파드 내부의 모든 컨테이너는 네트워크 네임스페이스를 공유하며 로컬 포트를 사용하여 서로 통신할 수 있습니다.

+

Amazon VPC CNI에는 두 가지 구성 요소가 있습니다.

+
    +
  • CNI 바이너리는 파드 간 통신을 활성화하는 파드 네트워크를 구성합니다. CNI 바이너리는 노드의 루트 파일 시스템에서 실행되며, 새 파드가 노드에 추가되거나 기존 파드가 노드에서 제거될 때 kubelet에 의해 호출됩니다.
  • +
  • 오래 실행되는(long-running) 노드 로컬(node-local) IP 주소 관리 (IPAM) 데몬인 ipamd는 다음을 담당합니다.
  • +
  • 노드의 ENI 관리 및
  • +
  • 사용 가능한 IP 주소 또는 Prefix의 웜 풀 유지
  • +
+

인스턴스가 생성되면 EC2는 기본 서브넷과 연결된 기본 ENI를 생성하여 연결합니다. 기본 서브넷은 퍼블릭 또는 프라이빗일 수 있습니다. 호스트 네트워크 모드에서 실행되는 파드는 노드 기본 ENI에 할당된 기본 IP 주소를 사용하며 호스트와 동일한 네트워크 네임스페이스를 공유합니다.

+

CNI 플러그인은 노드의 Elastic Network Interface (ENI)를 관리합니다. 노드가 프로비저닝되면 CNI 플러그인은 노드의 서브넷에서 기본 ENI에 슬롯 풀(IP 또는 Prefix)을 자동으로 할당합니다. 이 풀을 웜 풀이라고 하며, 크기는 노드의 인스턴스 유형에 따라 결정됩니다. CNI 설정에 따라 슬롯은 IP 주소 또는 Prefix일 수 있습니다. ENI의 슬롯이 할당되면 CNI는 웜 슬롯 풀이 있는 추가 ENI를 노드에 연결할 수 있습니다. 이러한 추가 ENI를 보조 ENI라고 합니다. 각 ENI는 인스턴스 유형에 따라 특정 갯수의 슬롯만 지원할 수 있습니다. CNI는 필요한 슬롯 수를 기반으로 인스턴스에 더 많은 ENI를 연결합니다. 여기서 슬롯 갯수는 보통 파드 갯수에 해당합니다. 이 프로세스는 노드가 추가 ENI를 더 이상 제공할 수 없을 때까지 계속됩니다. 또한 CNI는 파드 시작 속도를 높이기 위해 '웜' ENI와 슬롯을 사전 할당합니다. 참고로, 각 인스턴스 유형에는 연결할 수 있는 최대 ENI 수가 존재합니다. 이 조건은 컴퓨팅 리소스와 더불어 파드 밀도(노드당 파드 수)에 대한 또 하나의 제약 조건입니다.

+

flow chart illustrating procedure when new ENI delegated prefix is needed

+

최대 네트워크 인터페이스 수, 사용할 수 있는 최대 슬롯 수는 EC2 인스턴스 유형에 따라 다릅니다. 각 파드는 슬롯의 IP 주소를 사용하기 때문에 특정 EC2 인스턴스에서 실행할 수 있는 파드의 수는 연결할 수 있는 ENI의 수와 각 ENI가 지원하는 슬롯의 수에 따라 달라집니다. 인스턴스의 CPU 및 메모리 리소스가 고갈되지 않도록 EKS 사용 가이드에 따라 최대 파드를 설정할 것을 권장합니다. hostNetwork를 사용하는 파드는 이 계산에서 제외됩니다. 주어진 인스턴스 유형에 대한 EKS의 권장 최대 파드 개수를 계산하기 위해 max-pod-calculator.sh라는 스크립트를 사용할 수 있습니다.

+

개요

+

보조 IP 모드는 VPC CNI의 기본 모드입니다. 이 가이드에서는 보조 IP 모드가 활성화된 경우의 VPC CNI 동작에 대한 일반적인 개요를 제공합니다. ipamd의 기능(IP 주소 할당)은 VPC CNI의 구성 설정, 예를 들어 Prefix 모드, 파드당 보안 그룹 수, 사용자 지정 네트워킹에 따라 달라질 수 있습니다.

+

Amazon VPC CNI는 워커 노드에 aws-node라는 이름의 쿠버네티스 데몬셋으로 배포됩니다. 워커 노드가 프로비저닝되면 primary ENI라고 하는 기본 ENI가 연결됩니다. CNI는 노드의 기본 ENI에 연결된 서브넷에서 웜 풀의 ENI와 보조 IP 주소를 할당합니다. 기본적으로 ipamd는 노드에 추가 ENI를 할당하려고 시도합니다. 단일 파드가 스케줄되고 기본 ENI의 보조 IP 주소가 할당되면 IPAMD는 추가 ENI를 할당합니다. 이 “웜” ENI는 더 빠른 파드 네트워킹을 가능하게 합니다. 보조 IP 주소 풀이 부족해지면 CNI는 다른 ENI를 추가하여 더 많은 주소를 할당합니다.

+

풀의 ENI 및 IP 주소 수는 WARM_ENI_TARGET, WARM_IP_TARGET, MINIMUM_IP_TARGET이라는 환경 변수를 통해 구성됩니다. aws-node 데몬셋은 충분한 수의 ENI가 연결되어 있는지 주기적으로 확인합니다. WARM_ENI_TARGET 혹은 WARM_IP_TARGETMINIMUM_IP_TARGET 조건이 충족되면 충분한 수의 ENI가 연결됩니다. 연결된 ENI가 충분하지 않은 경우 CNI는 MAX_ENI 한도에 도달할 때까지 EC2에 API를 호출하여 추가로 ENI를 연결합니다.

+
    +
  • WARM_ENI_TARGET - 정수 값, 값이 >0이면 요구 사항이 활성화된 것입니다.
  • +
  • 관리할 웜 ENI의 수입니다. ENI는 노드에 보조 ENI로 연결되면 “웜” 상태가 되지만, 어떤 파드에서도 사용되지 않습니다. 구체적으로 말하면 ENI의 IP 주소가 파드와 연결되지 않은 상태입니다.
  • +
  • 예: ENI가 2개이고 각 ENI가 5개의 IP 주소를 지원하는 인스턴스를 예로 들어 보겠습니다. WARM_ENI_TARGET은 1로 설정되어 있습니다. 인스턴스에 정확히 5개의 IP 주소가 연결된 경우 CNI는 인스턴스에 2개의 ENI를 연결한 상태로 유지합니다. 첫 번째 ENI가 사용 중이며 이 ENI에서 사용 가능한 5개 IP 주소가 모두 사용됩니다. 두 번째 ENI는 풀에 5개 IP 주소가 모두 있는 “웜” 상태입니다. 인스턴스에서 다른 파드를 시작하는 경우 6번째 IP 주소가 필요합니다. CNI는 이 6번째 파드에 두 번째 ENI 및 풀의 5개의 IP에서 IP 주소를 할당합니다. 이제 두 번째 ENI가 사용되며 더 이상 “웜” 상태가 아니게 됩니다. CNI는 최소 1개의 웜 ENI를 유지하기 위해 세 번째 ENI를 할당합니다.
  • +
+
+

Note

+

웜 ENI도 VPC의 CIDR에 있는 IP 주소를 사용합니다. IP 주소는 파드와 같은 워크로드에 연결되기 전까지는 “미사용” 또는 “웜” 상태가 됩니다.

+
+
    +
  • WARM_IP_TARGET, 정수, 값이 >0이면 요구 사항이 활성화된 것입니다.
  • +
  • 유지할 웜 IP 주소 수입니다. 웜 IP는 활성 연결된 ENI에서 사용할 수 있지만 파드에 할당되지는 않습니다. 즉, 사용 가능한 웜 IP의 수는 추가 ENI 없이 파드에 할당할 수 있는 IP의 수입니다.
  • +
  • 예: ENI가 1개이고 각 ENI가 20개의 IP 주소를 지원하는 인스턴스를 예로 들어 보겠습니다. WARM_IP_TARGET은 5로 설정되어 있습니다. WARM_ENI_TARGET은 0으로 설정되어 있습니다. 16번째 IP 주소가 필요할 때까지는 ENI 1개만 연결됩니다. 그 다음에는 CNI가 서브넷 CIDR의 가능한 주소 20개를 사용하여 두 번째 ENI를 연결합니다.
  • +
  • MINIMUM_IP_TARGET, 정수, 값이 >0이면 요구 사항이 활성화된 것입니다.
  • +
  • 언제든지 할당할 수 있는 최소 IP 주소 수입니다. 이는 일반적으로 인스턴스 시작 시 여러 ENI 할당을 미리 불러오는 데 사용됩니다.
  • +
  • 예: 새로 시작한 인스턴스를 예로 들어보겠습니다. ENI는 1개이고 각 ENI는 10개의 IP 주소를 지원합니다. MINIMUM_IP_TARGET은 100으로 설정되어 있습니다. ENI는 총 100개의 주소에 대해 9개의 ENI를 즉시 추가로 연결합니다. 이는 WARM_IP_TARGET 또는 WARM_ENI_TARGET값과 상관없이 발생합니다.
  • +
+

이 프로젝트에는 서브넷 계산기 Excel 문서가 포함되어 있습니다. 이 문서는 WARM_IP_TARGETWARM_ENI_TARGET과 같은 다양한 ENI 구성 옵션에 따라 지정된 워크로드의 IP 주소 사용을 시뮬레이션합니다.

+

illustration of components involved in assigning an IP address to a pod

+

Kubelet이 파드 추가 요청을 받으면, CNI 바이너리는 ipamd에 사용 가능한 IP 주소를 쿼리하고, ipamd는 이를 파드에 제공합니다. CNI 바이너리는 호스트와 파드 네트워크를 연결합니다.

+

노드에 배포된 파드는 기본적으로 기본 ENI와 동일한 보안 그룹에 할당되며, 파드를 다른 보안 그룹으로 구성할 수도 있습니다.

+

second illustration of components involved in assigning an IP address to a pod

+

IP 주소 풀이 고갈되면 플러그인은 자동으로 다른 Elastic Network Interface를 인스턴스에 연결하고 해당 인터페이스에 다른 보조 IP 주소 세트를 할당합니다. 이 프로세스는 노드가 더 이상 추가 Elastic Network Interface를 지원할 수 없을 때까지 계속됩니다.

+

third illustration of components involved in assigning an IP address to a pod

+

파드가 삭제되면 VPC CNI는 파드의 IP 주소를 30초 쿨다운 캐시에 저장합니다. 쿨 다운 캐시의 IP는 신규 파드에 할당되지 않습니다. 쿨링오프 주기가 끝나면 VPC CNI는 파드 IP를 웜 풀로 다시 옮깁니다. 쿨링 오프 주기는 파드 IP 주소가 너무 이르게 재활용되는 것을 방지하고 모든 클러스터 노드의 kube-proxy가 iptables 규칙 업데이트를 완료할 수 있도록 합니다. IP 또는 ENI의 수가 웜 풀 설정 수를 초과하면 ipamd 플러그인은 VPC에 IP와 ENI를 반환합니다.

+

보조 IP 모드에서 앞서 설명한 바와 같이, 각 파드는 인스턴스에 연결된 ENI 중 하나로부터 하나의 보조 프라이빗 IP 주소를 수신합니다. 각 파드는 IP 주소를 사용하기 때문에 특정 EC2 인스턴스에서 실행할 수 있는 파드의 수는 연결할 수 있는 ENI의 수와 지원하는 IP 주소의 수에 따라 달라집니다. VPC CNI는 limit파일을 확인하여 각 인스턴스 유형에 허용되는 ENI와 IP 주소 수를 알아냅니다.

+

다음 공식을 사용하여 노드에 배포할 수 있는 최대 파드 수를 확인할 수 있습니다.

+

(인스턴스 유형의 네트워크 인터페이스 수 × (네트워크 인터페이스당 IP 주소 수 - 1)) + 2

+

+2는 kube-proxy 및 VPC CNI와 같은 호스트 네트워킹에서 필요한 파드를 나타냅니다. Amazon EKS에서는 각 노드에서 kube-proxy 및 VPC CNI가 작동해야 하며, 이러한 요구 사항은 max-pods 값에 반영됩니다. 추가 호스트 네트워킹 파드를 실행하려면 max-pods 값 업데이트를 고려해 보십시오.

+

+2는 kube-proxy 및 VPC CNI와 같은 호스트 네트워킹을 사용하는 쿠버네티스 파드를 나타냅니다. Amazon EKS는 모든 노드에서 kube-proxy와 VPC CNI를 실행해야 하며, 이는 max-pods 기준으로 계산됩니다. 더 많은 호스트 네트워킹 파드를 실행할 계획이라면 max-pods 값을 업데이트하는 것을 고려해 보십시오. 시작 템플릿에서 --kubelet-extra-args "—max-pods=110"을 사용자 데이터로 지정할 수 있습니다.

+

예를 들어, c5.large 노드 3개(ENI 3개, ENI당 최대 10개 IP)가 있는 클러스터에서 클러스터가 시작되고, CoreDNS 파드 2개가 있는 경우 CNI는 49개의 IP 주소를 사용하여 웜 풀에 보관합니다. 웜 풀을 사용하면 애플리케이션 배포 시 파드를 더 빠르게 시작할 수 있습니다.

+

노드 1 (CoreDNS 파드 포함): ENI 2개, IP 20개 할당

+

노드 2 (CoreDNS 파드 포함): ENI 2개, IP 20개 할당

+

노드 3 (파드 없음): ENI 1개, IP 10개 할당

+

주로 데몬셋으로 실행되는 인프라 파드는 각각 최대 파드 수에 영향을 미친다는 점을 고려합니다. 여기에는 다음이 포함될 수 있습니다.

+
    +
  • CoreDNS
  • +
  • Amazon Elastic LoadBalancer
  • +
  • metrics-server 운영용 Pods
  • +
+

이러한 파드들의 용량을 조합하여 인프라를 계획하는 것을 권장합니다. 각 인스턴스 유형에서 지원하는 최대 파드 수 목록은 GitHub의 eni-max-Pods.txt를 참조합니다.

+

illustration of multiple ENIs attached to a node

+

권장 사항

+

VPC CNI 관리형 애드온 배포

+

클러스터를 프로비저닝하면 Amazon EKS가 자동으로 VPC CNI를 설치합니다. 그럼에도 불구하고 Amazon EKS는 클러스터가 컴퓨팅, 스토리지, 네트워킹과 같은 기본 AWS 리소스와 상호 작용할 수 있도록 하는 관리형 애드온을 지원합니다. VPC CNI를 비롯한 관리형 애드온을 사용하여 클러스터를 배포하는 것이 좋습니다.

+

Amazon EKS 관리형 애드온은 Amazon EKS 클러스터를 위한 VPC CNI 설치 및 관리를 제공합니다. Amazon EKS 애드온에는 최신 보안 패치, 버그 수정이 포함되어 있으며, Amazon EKS와 호환되는지 AWS의 검증을 거쳤습니다. VPC CNI 애드온을 사용하면 Amazon EKS 클러스터의 보안 및 안정성을 지속적으로 보장하고 추가 기능을 설치, 구성 및 업데이트하는 데 필요한 노력을 줄일 수 있습니다. 또한 관리형 애드온은 Amazon EKS API, AWS 관리 콘솔, AWS CLI 및 eksctl을 통해 추가, 업데이트 또는 삭제할 수 있습니다.

+

kubectl get 명령어와 함께 --show-managed-fields 플래그를 사용하여 VPC CNI의 관리형 필드를 찾을 수 있습니다.

+
kubectl get daemonset aws-node --show-managed-fields -n kube-system -o yaml
+
+

관리형 애드온은 15분마다 구성을 자동으로 덮어쓰는 것으로 구성 변동을 방지합니다. 즉, 애드온 생성 후 Kubernetes API를 통해 변경한 관리형 애드온은 자동화된 드리프트 방지 프로세스에 의해 덮어쓰여지고 애드온 업데이트 프로세스 중에도 기본값으로 설정됩니다.

+

EKS에서 관리하는 필드는 managedFields 하위에 나열되며 이 필드에 대한 관리자는 EKS입니다. EKS에서 관리하는 필드에는 서비스 어카운트, 이미지, 이미지 URL, liveness probe, readiness probe, 레이블, 볼륨 및 볼륨 마운트가 포함됩니다.

+
+

Info

+
+

WARM_ENI_TARGET, WARM_IP_TARGET, MINIMUM_IP_TARGET과 같이 자주 사용되는 필드는 관리되지 않으며 조정되지 않습니다. 이러한 필드의 변경 사항은 애드온 업데이트 시에도 유지됩니다.

+

프로덕션 클러스터를 업데이트하기 전, 특정 구성의 비프로덕션 클러스터에서 애드온 동작을 테스트하는 것이 좋습니다. 또한 애드온 구성에 대해서는 EKS 사용자 가이드의 단계를 따라합니다.

+

관리형 애드온으로 마이그레이션

+

자체 관리형 VPC CNI의 버전 호환성을 관리하고 보안 패치를 업데이트합니다. 자체 관리형 애드온을 업데이트하려면 EKS 사용자 가이드에 설명된 쿠버네티스 API와 안내 사항들을 활용해야 합니다. 기존 EKS 클러스터의 경우, 관리형 애드온으로 마이그레이션하는 것이 좋으며, 마이그레이션 전에 현재 CNI 설정을 백업해 둘 것을 권장합니다. 관리형 애드온을 구성하기 위해 Amazon EKS API, AWS 관리 콘솔 또는 AWS 명령줄 인터페이스를 활용할 수 있습니다.

+
kubectl apply view-last-applied daemonset aws-node -n kube-system > aws-k8s-cni-old.yaml
+
+

필드 기본 설정에 관리형으로 표시된 경우 Amazon EKS는 CNI 구성 설정을 대체합니다. 관리형 필드를 수정하지 않도록 주의합니다. 애드온은 환경 변수 및 CNI 모드와 같은 구성 필드를 조정하지 않습니다. 파드와 애플리케이션은 관리형 CNI로 마이그레이션하는 동안 계속 실행됩니다.

+

업데이트 전 CNI 설정 백업

+

VPC CNI는 고객의 데이터 플레인(노드)에서 실행되므로 Amazon EKS는 신규 버전이 출시되거나 클러스터를 업데이트한 후에 신규 쿠버네티스 마이너 버전으로 (관리형 및 자체 관리형)애드온을 자동으로 업데이트하지 않습니다. 기존 클러스터의 애드온을 업데이트하려면 update-addon API를 사용하거나 EKS 콘솔에서 애드온용 지금 업데이트(Update Now) 링크를 클릭하여 업데이트를 트리거해야 합니다. 자체 관리형 애드온을 배포한 경우 자체 관리형 VPC CNI 애드온 업데이트에 안내된 단계를 따르도록 합니다.

+

마이너 버전은 한 번에 하나씩 업데이트하는 것이 좋습니다. 예를 들어 현재 마이너 버전이 1.9이고 1.11로 업데이트할 경우, 먼저 1.10의 최신 패치 버전으로 업데이트한 다음 1.11의 최신 패치 버전으로 업데이트해야 합니다.

+

Amazon VPC CNI를 업데이트하기 전에 AWS 노드 데몬셋 검사를 수행합니다. 기존 설정을 백업합니다. 관리형 애드온을 사용하는 경우 Amazon EKS에서 재정의할 수 있는 설정을 업데이트하지 않았는지 확인합니다. 고객의 자동화 워크플로의 업데이트 훅을 적용하거나 혹은 애드온 업데이트 후 수동 적용 단계를 추가할 것을 권장합니다.

+
kubectl apply view-last-applied daemonset aws-node -n kube-system > aws-k8s-cni-old.yaml
+
+

자체 관리형 애드온의 경우 백업을 GitHub의 releases 내용과 비교하여 사용 가능한 버전을 확인하고 업데이트하려는 버전의 변경 사항을 숙지합니다. 자체 관리형 애드온을 관리하기 위해 헬름(Helm)을 사용하는 것을 권장하며, values 파일을 활용하여 설정을 적용하는 것을 권장합니다. 데몬셋 삭제와 관련된 모든 업데이트 작업은 애플리케이션 다운타임으로 이어지므로 피해야 합니다.

+

보안 컨텍스트 이해

+

VPC CNI를 효율적으로 관리하기 위해 구성된 보안 컨텍스트를 이해하는 것이 좋습니다. Amazon VPC CNI에는 CNI 바이너리와 ipamd(aws-node) 데몬셋이라는 두 가지 구성 요소가 있습니다. CNI는 노드에서 바이너리로 실행되며 노드 루트 파일 시스템에 액세스할 수 있으며, 노드 수준에서 iptables를 처리하므로 privileged 접근에 대한 권한도 갖습니다. CNI 바이너리는 파드가 추가되거나 제거될 때 kubelet에 의해 호출됩니다.

+

aws-node 데몬셋은 노드 수준에서 IP 주소 관리를 담당하는 장기 실행 프로세스입니다. aws-node는 hostNetwork 모드에서 실행되며 루프백 디바이스 및 동일한 노드에 있는 다른 파드의 네트워크 활동에 대한 액세스를 허용합니다. aws-node의 초기화 컨테이너는 privileged 모드에서 실행되며 CRI 소켓을 마운트하여 데몬셋이 노드에서 실행되는 파드의 IP 사용을 모니터링할 수 있도록 합니다. Amazon EKS는 aws-node의 초기화 컨테이너의 privileged 요구 조건을 제거하기 위해 노력하고 있습니다. 또한 aws-node는 NAT 엔트리를 업데이트하고 iptables 모듈을 로드해야 하므로 NET_ADMIN 권한으로 실행해야 합니다.

+

Amazon EKS는 파드 및 네트워킹 설정의 IP 관리를 위해 aws-node 매니페스트에서 정의한 대로 보안 정책을 배포할 것을 권장합니다. 최신 버전의 VPC CNI로 업데이트하는 것을 고려합니다. 또한 특정 보안 요구 사항이 있는 경우 GitHub 이슈를 확인합니다.

+

CNI에 별도의 IAM 역할 사용

+

AWS VPC CNI에는 AWS 자격 증명 및 액세스 관리(IAM) 권한이 필요합니다. IAM 역할을 사용하려면 먼저 CNI 정책을 설정해야 합니다. IPv4 클러스터용 AWS 관리형 정책인 AmazonEKS_CNI_Policy를 사용할 수 있습니다. Amazon EKS의 CNI 관리형 정책에는 IPv4 클러스터에 대한 권한만 존재합니다. 이 링크에 표시된 권한을 사용하여 IPv6 클러스터에 대해 별도의 IAM 정책을 생성해야 합니다.

+

기본적으로 VPC CNI는 (관리형 노드 그룹과 자체 관리형 노드 그룹 모두)Amazon EKS 노드 IAM 역할을 상속합니다.

+

Amazon VPC CNI에 대한 관련 정책을 사용하여 별도의 IAM 역할을 구성하는 것을 강력하게 권장합니다. 그렇지 않은 경우 Amazon VPC CNI의 파드는 노드 IAM 역할에 할당된 권한을 얻고 노드에 할당된 인스턴스 프로필에 접근할 수 있습니다.

+

VPC CNI 플러그인은 aws-node라는 서비스 어카운트를 생성하고 구성합니다. 기본적으로 서비스 어카운트는 Amazon EKS CNI 정책이 연결된 Amazon EKS 노드의 IAM 역할에 바인딩됩니다. 별도의 IAM 역할을 사용하려면 Amazon EKS CNI 정책이 연결된 신규 서비스 어카운트를 생성하는 것이 좋습니다. 신규 서비스 어카운트를 사용하려면 CNI 파드 재배포를 진행해야 합니다. 신규 클러스터를 생성할 때 VPC CNI 관리형 애드온에 --service-account-role-arn을 지정하는 것을 고려합니다. 이 때 Amazon EKS 노드의 IAM 역할에서 IPv4와 IPv6 모두에 대한 Amazon EKS CNI 정책을 반드시 제거해야 합니다.

+

보안 침해의 피해 범위를 최소화하기 위해 인스턴스 메타데이터 접근을 차단하는 것을 권장합니다.

+

Liveness/Readiness Probe 실패 처리

+

프로브 실패로 인해 애플리케이션의 파드가 컨테이너 생성 상태에서 멈추는 것을 방지하기 위해 EKS 1.20 버전 이상 클러스터의 liveness 및 readiness probe의 타임아웃 값 (기본값 TimeoutSeconds: 10)을 높일 것을 권장합니다. 해당 이슈는 데이터 집약적인 배치 처리 클러스터에서 발견되었습니다. CPU를 많이 사용하면 aws-node 프로브 상태 장애가 발생하여 Pod CPU 리소스 요청이 충족되지 않을 수 있습니다. 프로브 타임아웃을 수정하는 것 외에도 aws-node에 대한 CPU 리소스 요청(기본값 CPU: 25m)이 올바르게 구성되었는지 확인합니다. 노드에 문제가 있는 경우가 아니면 설정을 업데이트하지 않는 것을 권장합니다.

+

Amazon EKS 지원을 받는 동안 노드에서 sudo bash /opt/cni/bin/aws-cni-support.sh를 실행할 것을 권장합니다. 이 스크립트는 노드의 kubelet 로그와 메모리 사용률을 평가하는 데 도움이 됩니다. 스크립트를 실행하기 위해 Amazon EKS 워커 노드에 SSM 에이전트를 설치하는 것을 고려합니다.

+

비 EKS 최적화 AMI 인스턴스에서 IPtables 전달 정책 구성

+

사용자 지정 AMI를 사용하는 경우 kubelet.service에서 iptables 전달 정책을 ACCEPT로 설정해야 합니다. 많은 시스템에서 iptables 전달 정책이 DROP으로 설정됩니다. HashiCorp Packer를 사용하여 사용자 지정 AMI를, AWS GitHub의 Amazon EKS AMI 저장소에서 리소스 및 구성 스크립트를 사용하여 빌드 사양을 생성할 수 있습니다. kubelet.service를 업데이트하고 이 링크에 설명된 안내에 따라 사용자 지정 AMI를 생성할 수 있습니다.

+

정기적인 CNI 버전 업그레이드

+

VPC CNI는 이전 버전과 호환됩니다. 최신 버전은 모든 Amazon EKS에서 지원하는 쿠버네티스 버전과 호환됩니다. 또한 VPC CNI는 EKS 애드온으로 제공됩니다(위의 “VPC CNI 관리형 애드온 배포” 참조). EKS 애드온은 애드온 업그레이드를 관리하지만 CNI와 같은 애드온은 데이터 플레인에서 실행되므로 자동으로 업그레이드되지 않습니다. 관리형 및 자체 관리형 워커 노드 업그레이드 후에는 VPC CNI 애드온을 업그레이드해야 합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/performance/performance_WIP/index.html b/ko/performance/performance_WIP/index.html new file mode 100644 index 000000000..26a2dcf8d --- /dev/null +++ b/ko/performance/performance_WIP/index.html @@ -0,0 +1,2317 @@ + + + + + + + + + + + + + + + + + + + Performance Efficiency Pillar - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Performance Efficiency Pillar

+

The performance efficiency pillar focuses on the efficient use of computing resources to meet requirements and how to maintain that efficiency as demand changes and technologies evolve. This section provides in-depth, best practices guidance for architecting for performance efficiency on AWS.

+

Definition

+

To ensure the efficient use of EKS container services, you should gather data on all aspects of the architecture, from the high-level design to the selection of EKS resource types. By reviewing your choices on a regular basis, you ensure that you are taking advantage of the continually evolving Amazon EKS and Container services. Monitoring will ensure that you are aware of any deviance from expected performance so you can take action on it.

+

Performance efficiency for EKS containers is composed of three areas:

+
    +
  • +

    Optimize your container

    +
  • +
  • +

    Resource Management

    +
  • +
  • +

    Scalability Management

    +
  • +
+

Best Practices

+

Optimize your container

+

You can run most applications in a Docker container without too much hassle. There are a number of things that you need to do to ensure it's running effectively in a production environment, including streamlining the build process. The following best practices will help you to achieve that.

+

Recommendations

+
    +
  • Make your container images stateless: A container created with a Docker image should be ephemeral and immutable. In other words, the container should be disposable and independent, i.e. a new one can be built and put in place with absolutely no configuration changes. Design your containers to be stateless. If you would like to use persistent data, use volumes instead. If you would like to store secrets or sensitive application data used by services, you can use solutions like AWS Systems ManagerParameter Store or third-party offerings or open source solutions, such as HashiCorp Valut and Consul, for runtime configurations.
  • +
  • Minimal base image : Start with a small base image. Every other instruction in the Dockerfile builds on top of this image. The smaller the base image, the smaller the resulting image is, and the more quickly it can be downloaded. For example, the alpine:3.7 image is 71 MB smaller than the centos:7 image. You can even use the scratch base image, which is an empty image on which you can build your own runtime environment.
  • +
  • Avoid unnecessary packages: When building a container image, include only the dependencies what your application needs and avoid installing unnecessary packages. For example if your application does not need an SSH server, don't include one. This will reduce complexity, dependencies, file sizes, and build times. To exclude files not relevant to the build use a .dockerignore file.
  • +
  • Use multi-stage build:Multi-stage builds allow you to build your application in a first "build" container and use the result in another container, while using the same Dockerfile. To expand a bit on that, in multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don't want in the final image. This method drastically reduces the size of your final image, without struggling to reduce the number of intermediate layers and files.
  • +
  • Minimize number of layers: Each instruction in the Dockerfile adds an extra layer to the Docker image. The number of instructions and layers should be kept to a minimum as this affects build performance and time. For example, the first instruction below will create multiple layers, whereas the second instruction by using &&(chaining) we reduced the number of layers, which will help provide better performance. The is the best way to reduce the number of layers that will be created in your Dockerfile.
  • +
  • +
            RUN apt-get -y update
    +        RUN apt-get install -y python
    +        RUN apt-get -y update && apt-get install -y python
    +
    +
  • +
  • +

    Properly tag your images: When building images, always tag them with useful and meaningful tags. This is a good way to organize and document metadata describing an image, for example, by including a unique counter like build id from a CI server (e.g. CodeBuild or Jenkins) to help with identifying the correct image. The tag latest is used by default if you do not provide one in your Docker commands. We recommend not to use the automatically created latest tag, because by using this tag you'll automatically be running future major releases, which could include breaking changes for your application. The best practice is to avoid the latest tag and instead use the unique digest created by your CI server.

    +
  • +
  • Use Build Cache to improve build speed : The cache allows you to take advantage of existing cached images, rather than building each image from scratch. For example, you should add the source code of your application as late as possible in your Dockerfile so that the base image and your application's dependencies get cached and aren't rebuilt on every build. To reuse already cached images, By default in Amazon EKS, the kubelet will try to pull each image from the specified registry. However, if the imagePullPolicy property of the container is set to IfNotPresent or Never, then a local image is used (preferentially or exclusively, respectively).
  • +
  • +

    Image Security : Using public images may be a great way to start working on containers and deploying it to Kubernetes. However, using them in production can come with a set of challenges. Especially when it comes to security. Ensure to follow the best practices for packaging and distributing the containers/applications. For example, don't build your containers with passwords baked in also you might need to control what's inside them. Recommend to use private repository such as Amazon ECR and leverage the in-built image scanning feature to identify software vulnerabilities in your container images.

    +
  • +
  • +

    Right size your containers: As you develop and run applications in containers, there are a few key areas to consider. How you size containers and manage your application deployments can negatively impact the end-user experience of services that you provide. To help you succeed, the following best practices will help you right size your containers. After you determine the number of resources required for your application, you should set requests and limits Kubernetes to ensure that your applications are running correctly.

    +
  • +
+

                (a) Perform testing of the application: to gather vital statistics and other performance                 Based upon this data you can work out the optimal configuration, in terms of memory and                 CPU, for your container. Vital statistics such as : CPU, Latency, I/O, Memory usage,                Network . Determine expected, mean, and peak container memory and CPU usage by                 doing a separate load test if necessary. Also consider all the processes that might                 potentially run in parallel in the container.

+

                Recommend to use CloudWatch Container insights or partner products, which will give                 you the right information to size containers and the Worker nodes.

+

                (b)Test services independently: As many applications depend on each other in a true                 microservice architecture, you need to test them with a high degree of independence                 meaning that the services are both able to properly function by themselves, as well as                 function as part of a cohesive system.

+

Resource Management

+

One of the most common questions that asked in the adoption of Kubernetes is "What should I put in a Pod?". For example, a three tier LAMP application container. Should we keep this application in the same pod? Well, this works effectively as a single pod but this is an example of an anti-pattern for Pod creation. There are two reasons for that

+

(a) If you have both the containers in the same Pod, you are forced to use the same scaling strategy which is not ideal for production environment also you can't effectively manage or constraint resources based on the usage. E.g: you might need to scale just the frontend not frontend and backend (MySQL) as a unit also if you would like to increase the resources dedicated just to the backend, you cant just do that.

+

(b) If you have two separate pods, one for frontend and other for backend. Scaling would be very easy and you get a better reliability.

+

The above might not work in all the use-cases. In the above example frontend and backend may land in different machines and they will communicate with each other via network, So you need to ask the question "Will my application work correctly, If they are placed and run on different machines?" If the answer is a "no" may be because of the application design or for some other technical reasons, then grouping of containers in a single pod makes sense. If the answer is "Yes" then multiple Pods is the correct approach.

+

Recommendations

+
    +
  • +

    Package a single application per container: +A container works best when a single application runs inside it. This application should have a single parent process. For example, do not run PHP and MySQL in the same container: it's harder to debug, and you can't horizontally scale the PHP container alone. This separation allows you to better tie the lifecycle of the application to that of the container. Your containers should be both stateless and immutable. Stateless means that any state (persistent data of any kind) is stored outside of the container, for example, you can use different kinds of external storage like Persistent disk, Amazon EBS, and Amazon EFS if needed, or managed database like Amazon RDS. Immutable means that a container will not be modified during its life: no updates, no patches, and no configuration changes. To update the application code or apply a patch, you build a new image and deploy it.

    +
  • +
  • +

    Use Labels to Kubernetes Objects: +Labels allow Kubernetes objects to be queried and operated upon in bulk. They can also be used to identify and organize Kubernetes objects into groups. As such defining labels should figure right at the top of any Kubernetes best practices list.

    +
  • +
  • +

    Setting resource request limits: +Setting request limits is the mechanism used to control the amount of system resources that a container can consume such as CPU and memory. These settings are what the container is guaranteed to get when the container initially starts. If a container requests a resource, container orchestrators such as Kubernetes will only schedule it on a node that can provide that resource. Limits, on the other hand, make sure that a container never goes above a certain value. The container is only allowed to go up to the limit, and then it is restricted.

    +
  • +
+

                In the below example Pod manifest, we add a limit of 1.0 CPU and 256 MB of memory

+
        apiVersion: v1
+        kind: Pod
+        metadata:
+          name: nginx-pod-webserver
+          labels:
+            name: nginx-pod
+        spec:
+          containers:
+          - name: nginx
+            image: nginx:latest
+            resources:
+              limits:
+                memory: "256Mi"
+                cpu: "1000m"
+              requests:
+                memory: "128Mi"
+                cpu: "500m"
+            ports:
+            - containerPort: 80
+
+

               It's a best practice to define these requests and limits in your pod definitions. If you don't                include these values, the scheduler doesn't understand what resources are needed.                Without this information, the scheduler might schedule the pod on a node without                sufficient resources to provide acceptable application performance.

+
    +
  • Limit the number of concurrent disruptions: +Use PodDisruptionBudget, This settings allows you to set a policy on the minimum available and maximum unavailable pods during voluntary eviction events. An example of an eviction would be when perform maintenance on the node or draining a node.
  • +
+

                 Example: A web frontend might want to ensure that 8 Pods to be available at any                  given time. In this scenario, an eviction can evict as many pods as it wants, as long as                  eight are available. +

apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+  name: frontend-demo
+spec:
+  minAvailable: 8
+  selector:
+    matchLabels:
+      app: frontend
+

+

                 N.B: You can also specify pod disruption budget as a percentage by using maxAvailable                  or maxUnavailable parameter.

+
    +
  • Use Namespaces: +Namespaces allows a physical cluster to be shared by multiple teams. A namespace allows to partition created resources into a logically named group. This allows you to set resource quotas per namespace, Role-Based Access Control (RBAC) per namespace, and also network policies per namespace. It gives you soft multitenancy features.
  • +
+

                 For example, If you have three applications running on a single Amazon EKS cluster                  accessed by three different teams which requires multiple resource constraints and                  different levels of QoS each group you could create a namespace per team and give each                  team a quota on the number of resources that it can utilize, such as CPU and memory.

+

                 You can also specify default limits in Kubernetes namespaces level by enabling                  LimitRange admission controller. These default limits will constrain the amount of CPU                  or memory a given Pod can use unless the defaults are explicitly overridden by the Pod's                  configuration.

+
    +
  • +

    Manage Resource Quota: +Each namespace can be assigned resource quota. Specifying quota allows to restrict how much of cluster resources can be consumed across all resources in a namespace. Resource quota can be defined by a ResourceQuota object. A presence of ResourceQuota object in a namespace ensures that resource quotas are enforced.

    +
  • +
  • +

    Configure Health Checks for Pods: +Health checks are a simple way to let the system know if an instance of your app is working or not. If an instance of your app is not working, then other services should not access it or send requests to it. Instead, requests should be sent to another instance of the app that is working. The system also should bring your app back to a healthy state. By default, all the running pods have the restart policy set to always which means the kubelet running within a node will automatically restart a pod when the container encounters an error. Health checks extend this capability of kubelet through the concept of container probes.

    +
  • +
+

Kubernetes provides two types of health checks: readiness and liveness probes. For example, consider if one of your applications, which typically runs for long periods of time, transitions to a non-running state and can only recover by being restarted. You can use liveness probes to detect and remedy such situations. Using health checks gives your applications better reliability, and higher uptime.

+
    +
  • Advanced Scheduling Techniques: +Generally, schedulers ensure that pods are placed only on nodes that have sufficient free resources, and across nodes, they try to balance out the resource utilization across nodes, deployments, replicas, and so on. But sometimes you want to control how your pods are scheduled. For example, perhaps you want to ensure that certain pods are only scheduled on nodes with specialized hardware, such as requiring a GPU machine for an ML workload. Or you want to collocate services that communicate frequently.
  • +
+

Kubernetes offers manyadvanced scheduling featuresand multiple filters/constraints to schedule the pods on the right node. For example, when using Amazon EKS, you can usetaints and tolerationsto restrict what workloads can run on specific nodes. You can also control pod scheduling using node selectorsandaffinity and anti-affinityconstructs and even have your own custom scheduler built for this purpose.

+

Scalability Management

+

Containers are stateless. They are born and when they die, they are not resurrected. There are many techniques that you can leverage on Amazon EKS, not only to scale out your containerized applications but also the Kubernetes worker node.

+

Recommendations

+
    +
  • +

    On Amazon EKS, you can configure Horizontal pod autoscaler,which automatically scales the number of pods in a replication controller, deployment, or replica set based on observed CPU utilization (or usecustom metricsbased on application-provided metrics).

    +
  • +
  • +

    You can use Vertical Pod Autoscaler which automatically adjusts the CPU and memory reservations for your pods to help "right size" your applications. This adjustment can improve cluster resource utilization and free up CPU and memory for other pods. This is useful in scenarios like your production database "MongoDB" does not scale the same way as a stateless application frontend, In this scenario you could use VPA to scale up the MongoDB Pod.

    +
  • +
  • +

    To enable VPA you need to use Kubernetes metrics server, which is an aggregator of resource usage data in your cluster. It is not deployed by default in Amazon EKS clusters. You need to configure it before configure VPA alternatively you can also use Prometheus to provide metrics for the Vertical Pod Autoscaler.

    +
  • +
  • +

    While HPA and VPA scale the deployments and pods, Cluster Autoscaler will scale-out and scale-in the size of the pool of worker nodes. It adjusts the size of a Kubernetes cluster based on the current utilization. Cluster Autoscaler increases the size of the cluster when there are pods that failed to schedule on any of the current nodes due to insufficient resources or when adding a new node would increase the overall availability of cluster resources. Please follow this step by step guide to setup Cluster Autoscaler. If you are using Amazon EKS on AWS Fargate, AWS Manages the control plane for you.

    +

    Please have a look at the reliability pillar for detailed information.

    +
  • +
+

Monitoring

+

Deployment Best Practices

+

Trade-Offs

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/reliability/docs/application/index.html b/ko/reliability/docs/application/index.html new file mode 100644 index 000000000..3ffaf500b --- /dev/null +++ b/ko/reliability/docs/application/index.html @@ -0,0 +1,2986 @@ + + + + + + + + + + + + + + + + + + + + + + + 애플리케이션 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

고가용성 애플리케이션 실행

+

고객은 애플리케이션을 변경할 때나 트래픽이 급증할 때 조차 애플리케이션이 항상 사용 가능하기를 기대합니다. 확장 가능하고 복원력이 뛰어난 아키텍처를 통해 애플리케이션과 서비스를 중단 없이 실행하여 사용자 만족도를 유지할 수 있습니다. 확장 가능한 인프라는 비즈니스 요구 사항에 따라 확장 및 축소됩니다. 단일 장애 지점을 제거하는 것은 애플리케이션의 가용성을 개선하고 복원력을 높이기 위한 중요한 단계입니다.

+

쿠버네티스를 사용하면 가용성과 복원력이 뛰어난 방식으로 애플리케이션을 운영하고 실행할 수 있습니다. 선언적 관리를 통해 애플리케이션을 설정한 후에는 쿠버네티스가 지속적으로 현재 상태를 원하는 상태와 일치하도록 시도할 수 있습니다.

+

권장 사항

+

싱글톤 파드를 실행하지 마세요

+

전체 애플리케이션이 단일 파드에서 실행되는 경우, 해당 파드가 종료되면 애플리케이션을 사용할 수 없게 됩니다. 개별 파드를 사용하여 애플리케이션을 배포하는 대신 디플로이먼트를 생성하십시오. 디플로이먼트로 생성된 파드가 실패하거나 종료되는 경우, 디플로이먼트 컨트롤러는 새 파드를 시작하여 지정된 개수의 레플리카 파드가 항상 실행되도록 합니다.

+

여러 개의 레플리카 실행

+

디플로이먼트를 사용하여 앱의 여러 복제본 파드를 실행하면 가용성이 높은 방식으로 앱을 실행할 수 있습니다. 하나의 복제본에 장애가 발생하더라도 쿠버네티스가 손실을 만회하기 위해 다른 파드를 생성하기 전까지는 용량이 줄어들기는 하지만 나머지 복제본은 여전히 작동한다. 또한 Horizontal Pod Autoscaler를 사용하여 워크로드 수요에 따라 복제본을 자동으로 확장할 수 있습니다.

+

여러 노드에 복제본을 스케줄링합니다.

+

모든 복제본이 동일한 노드에서 실행되고 있고 노드를 사용할 수 없게 되면 여러 복제본을 실행하는 것은 그다지 유용하지 않습니다. 파드 anti-affinity 또는 파드 topology spread contraints을 사용해 디플로이먼트의 복제본을 여러 워커 노드에 분산시키는 것을 고려해 보십시오.

+

여러 AZ에서 실행하여 일반적인 애플리케이션의 신뢰성을 더욱 개선할 수 있습니다.

+

파드 anti-affinity 규칙 사용

+

아래 매니페스트는 쿠버네티스 스케줄러에게 파드를 별도의 노드와 AZ에 배치하도록 prefer라고 지시합니다. 이렇게 되어있다면 별도의 노드나 AZ가 필요하지 않습니다. 그렇게 하면 각 AZ에서 실행 중인 파드가 있으면 쿠버네티스가 어떤 파드도 스케줄링할 수 없기 때문입니다. 애플리케이션에 단 세 개의 복제본이 필요한 경우, topologyKey: topology.kubernetes.io/zone에 대해 requiredDuringSchedulingIgnoredDuringExecution를 사용할 수 있으며, 쿠버네티스 스케줄러는 동일한 AZ에 두 개의 파드를 스케줄링하지 않습니다.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: spread-host-az
+  labels:
+    app: web-server
+spec:
+  replicas: 4
+  selector:
+    matchLabels:
+      app: web-server
+  template:
+    metadata:
+      labels:
+        app: web-server
+    spec:
+      affinity:
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app
+                  operator: In
+                  values:
+                  - web-server
+              topologyKey: topology.kubernetes.io/zone
+            weight: 100
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app
+                  operator: In
+                  values:
+                  - web-server
+              topologyKey: kubernetes.io/hostname 
+            weight: 99
+      containers:
+      - name: web-app
+        image: nginx:1.16-alpine
+
+

파드 topology spread constraints 사용

+

파드 anti-affinity 규칙과 마찬가지로, 파드 topology spread constraints을 사용하면 호스트 또는 AZ와 같은 다양한 장애 (또는 토폴로지) 도메인에서 애플리케이션을 사용할 수 있습니다. 이 접근 방식은 서로 다른 토폴로지 도메인 각각에 여러 복제본을 보유하여 내결함성과 가용성을 보장하려는 경우에 매우 효과적입니다. 반면, 파드 anti-affinity 규칙은 anti-affinity가 있는 파드 서로에 대해 거부 효과가 있기 때문에 토폴로지 도메인에 단일 복제본이 있도록 쉽게 만들 수 있습니다. 이러한 경우 전용 노드의 단일 복제본은 내결함성 측면에서 이상적이지도 않고 리소스를 적절하게 사용하지도 않습니다. topology spread constraints을 사용하면 스케줄러가 토폴로지 도메인 전체에 적용하려고 시도하는 분배 또는 배포를 보다 효과적으로 제어할 수 있습니다. 이 접근 방식에서 사용할 수 있는 몇 가지 중요한 속성은 다음과 같습니다. +1. MaxSkew는 토폴로지 도메인 전체에서 균등하지 않게 분산될 수 있는 최대 정도를 제어하거나 결정하는 데 사용됩니다. 예를 들어 애플리케이션에 10개의 복제본이 있고 3개의 AZ에 배포된 경우 균등하게 분산될 수는 없지만 분포의 불균일성에 영향을 미칠 수 있습니다. 이 경우 MaxSkew는 1에서 10 사이일 수 있습니다.값이 1이면 3개의 AZ에 걸쳐 4,3,3, 3,4,3 또는 3,3,4와 같은 분배가 생성될 수 있습니다. 반대로 값이 10이면 3개의 AZ에 걸쳐 10,0,0, 0,10,0 또는 0,0,10과 같은 분배가 나올 수 있다는 의미입니다. +2. TopologyKey는 노드 레이블 중 하나의 키이며 파드 배포에 사용해야 하는 토폴로지 도메인 유형을 정의합니다. 예를 들어 존(zone)별 분배는 다음과 같은 키-값 쌍을 가집니다. +

topologyKey: "topology.kubernetes.io/zone"
+
+3. WhenUnsatisfiable 속성은 원하는 제약 조건을 충족할 수 없는 경우 스케줄러가 어떻게 응답할지 결정하는 데 사용됩니다. +4. LabelSelector는 일치하는 파드를 찾는 데 사용되며, 이를 통해 스케줄러는 지정한 제약 조건에 따라 파드를 배치할 위치를 결정할 때 이를 인지할 수 있습니다.

+

위의 필드 외에도, 다른 필드에 대해서는 쿠버네티스 설명서에서 더 자세히 알아볼 수 있습니다.

+

파드 토폴로지는 제약 조건을 3개 AZ에 분산시킵니다.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: spread-host-az
+  labels:
+    app: web-server
+spec:
+  replicas: 10
+  selector:
+    matchLabels:
+      app: web-server
+  template:
+    metadata:
+      labels:
+        app: web-server
+    spec:
+      topologySpreadConstraints:
+      - maxSkew: 1
+        topologyKey: "topology.kubernetes.io/zone"
+        whenUnsatisfiable: ScheduleAnyway
+        labelSelector:
+          matchLabels:
+            app: express-test
+      containers:
+      - name: web-app
+        image: nginx:1.16-alpine
+
+

쿠버네티스 메트릭 서버 실행

+

쿠버네티스 메트릭 서버 를 설치하면 애플리케이션 확장에 도움이 됩니다. HPAVPA와 같은 쿠버네티스 오토스케일러 애드온은 애플리케이션의 메트릭을 추적하여 애플리케이션을 확장합니다. 메트릭 서버는 규모 조정 결정을 내리는 데 사용할 수 있는 리소스 메트릭을 수집합니다. 메트릭은 kubelets에서 수집되어 메트릭 API 형식으로 제공됩니다.

+

메트릭 서버는 데이터를 보관하지 않으며 모니터링 솔루션도 아닙니다. 그 목적은 CPU 및 메모리 사용량 메트릭을 다른 시스템에 공개하는 것입니다. 시간 경과에 따른 애플리케이션 상태를 추적하려면 Prometheus 또는 Amazon CloudWatch와 같은 모니터링 도구가 필요합니다.

+

EKS 설명서에 따라 EKS 클러스터에 메트릭 서버를 설치하십시오.

+

Horizontal Pod Autoscaler (HPA)

+

HPA는 수요에 따라 애플리케이션을 자동으로 확장하고 트래픽이 최고조에 달할 때 고객에게 영향을 미치지 않도록 도와줍니다. 쿠버네티스에는 제어 루프로 구현되어 있어 리소스 메트릭을 제공하는 API에서 메트릭을 정기적으로 쿼리합니다.

+

HPA는 다음 API에서 메트릭을 검색할 수 있습니다. +1. 리소스 메트릭 API라고도 하는 metrics.k8s.io — 파드의 CPU 및 메모리 사용량을 제공합니다. +2. custom.metrics.k8s.io — 프로메테우스와 같은 다른 메트릭 콜렉터의 메트릭을 제공합니다. 이러한 메트릭은 쿠버네티스 클러스터에 internal 입니다. +3. external.metrics.k8s.io — 쿠버네티스 클러스터에 __external__인 메트릭을 제공합니다 (예: SQS 대기열 길이, ELB 지연 시간).

+

애플리케이션을 확장하기 위한 메트릭을 제공하려면 이 세 가지 API 중 하나를 사용해야 합니다.

+

사용자 지정 또는 외부 지표를 기반으로 애플리케이션 규모 조정

+

사용자 지정 또는 외부 지표를 사용하여 CPU 또는 메모리 사용률 이외의 지표에 따라 애플리케이션을 확장할 수 있습니다.커스텀 메트릭 API 서버는 HPA가 애플리케이션을 자동 스케일링하는 데 사용할 수 있는 custom-metrics.k8s.io API를 제공합니다.

+

쿠버네티스 메트릭 API용 프로메테우스 어댑터를 사용하여 프로메테우스에서 메트릭을 수집하고 HPA에서 사용할 수 있습니다. 이 경우 프로메테우스 어댑터는 프로메테우스 메트릭을 메트릭 API 형식으로 노출합니다. 모든 커스텀 메트릭 구현 목록은 쿠버네티스 설명서에서 확인할 수 있습니다.

+

프로메테우스 어댑터를 배포한 후에는 kubectl을 사용하여 사용자 지정 메트릭을 쿼리할 수 있습니다. +kubectl get —raw /apis/custom.metrics.k8s.io/v1beta1/

+

외부 메트릭은 이름에서 알 수 있듯이 Horizontal Pod Autoscaler에 쿠버네티스 클러스터 외부의 메트릭을 사용하여 배포를 확장할 수 있는 기능을 제공합니다. 예를 들어 배치 처리 워크로드에서는 SQS 대기열에서 진행 중인 작업 수에 따라 복제본 수를 조정하는 것이 일반적입니다.

+

Kubernetes 워크로드를 자동 확장하려면 여러 사용자 정의 이벤트를 기반으로 컨테이너 확장을 구동할 수 있는 오픈 소스 프로젝트인 KEDA(Kubernetes Event-driven Autoscaling)를 사용할 수 있습니다. 이 AWS 블로그에서는 Kubernetes 워크로드 자동 확장을 위해 Amazon Managed Service for Prometheus를 사용하는 방법을 설명합니다.

+

Vertical Pod Autoscaler (VPA)

+

VPA는 파드의 CPU 및 메모리 예약을 자동으로 조정하여 애플리케이션을 “적절한 크기”로 조정할 수 있도록 합니다. 리소스 할당을 늘려 수직으로 확장해야 하는 애플리케이션의 경우 VPA를 사용하여 파드 복제본을 자동으로 확장하거나 규모 조정 권장 사항을 제공할 수 있습니다.

+

VPA의 현재 구현은 파드에 대한 인플레이스 조정을 수행하지 않고 대신 스케일링이 필요한 파드를 다시 생성하기 때문에 VPA가 애플리케이션을 확장해야 하는 경우 애플리케이션을 일시적으로 사용할 수 없게 될 수 있습니다.

+

EKS 설명서에는 VPA 설정 방법이 수록되어 있습니다.

+

Fairwinds Goldilocks 프로젝트는 CPU 및 메모리 요청 및 제한에 대한 VPA 권장 사항을 시각화할 수 있는 대시보드를 제공합니다. VPA 업데이트 모드를 사용하면 VPA 권장 사항에 따라 파드를 자동 확장할 수 있습니다.

+

애플리케이션 업데이트

+

최신 애플리케이션에는 높은 수준의 안정성과 가용성을 갖춘 빠른 혁신이 필요합니다. 쿠버네티스는 고객에게 영향을 주지 않으면서 애플리케이션을 지속적으로 업데이트할 수 있는 도구를 제공합니다.

+

가용성 저하 없이 변경 사항을 신속하게 배포할 수 있는 몇 가지 모범 사례를 살펴보겠습니다.

+

롤백을 수행할 수 있는 메커니즘 마련

+

실행 취소 버튼이 있으면 재해를 피할 수 있습니다. 프로덕션 클러스터를 업데이트하기 전에 별도의 하위 환경(테스트 또는 개발 환경)에서 배포를 테스트하는 것이 가장 좋습니다. CI/CD 파이프라인을 사용하면 배포를 자동화하고 테스트하는 데 도움이 될 수 있습니다. 지속적 배포 파이프라인을 사용하면 업그레이드에 결함이 발생할 경우 이전 버전으로 빠르게 되돌릴 수 있습니다.

+

디플로이먼트를 사용하여 실행 중인 애플리케이션을 업데이트할 수 있습니다. 이는 일반적으로 컨테이너 이미지를 업데이트하여 수행됩니다. kubectl을 사용하여 다음과 같이 디플로이먼트를 업데이트할 수 있습니다.

+
kubectl --record deployment.apps/nginx-deployment set image nginx-deployment nginx=nginx:1.16.1
+
+

--record 인수는 디플로이먼트의 변경 사항을 기록하고 롤백을 수행해야 하는 경우 도움이 됩니다. kubectl rollout history deployment는 클러스터의 디플로이먼트에 대해 기록된 변경 사항을 보여준다. kubectl rollout undo deployment <DEPLOYMENT_NAME>을 사용하여 변경사항을 롤백할 수 있습니다.

+

기본적으로, 파드를 재생성해야 하는 디플로이먼트를 업데이트하면 디플로이먼트는 롤링 업데이트를 수행합니다. 즉, 쿠버네티스는 디플로이먼트에서 실행 중인 파드의 일부만 업데이트하고 모든 파드는 한 번에 업데이트하지 않습니다. RollingUpdateStrategy 프로퍼티를 통해 쿠버네티스가 롤링 업데이트를 수행하는 방식을 제어할 수 있습니다.

+

디플로이먼트의 롤링 업데이트를 수행할 때, Max Unavailable 프로퍼티를 사용하여 업데이트 중에 사용할 수 없는 파드의 최대 개수를 지정할 수 있습니다. 디플로이먼트의 Max Surge 프로퍼티를 사용하면 원하는 파드 수보다 더 생성할 수 있는 최대 파드 수를 설정할 수 있습니다.

+

롤아웃으로 인해 고객이 혼란에 빠지지 않도록 max unavailable을 조정하는 것을 고려해 보십시오. 예를 들어 쿠버네티스는 기본적으로 25%의 max unavailable을 설정합니다. 즉, 100개의 파드가 있는 경우 롤아웃 중에 활성 상태로 작동하는 파드는 75개만 있을 수 있습니다. 애플리케이션에 최소 80개의 파드가 필요한 경우 이 롤아웃으로 인해 중단이 발생할 수 있습니다. 대신, max unavailable을 20% 로 설정하여 롤아웃 내내 작동하는 파드가 80개 이상 있도록 할 수 있습니다.

+

블루/그린 배포 사용

+

변경은 본질적으로 위험하지만, 취소할 수 없는 변경은 잠재적으로 치명적일 수 있습니다. 롤백을 통해 시간을 효과적으로 되돌릴 수 있는 변경 절차를 사용하면 향상된 기능과 실험이 더 안전해집니다. 블루/그린 배포는 문제가 발생할 경우 변경 사항을 신속하게 철회할 수 있는 방법을 제공합니다. 이 배포 전략에서는 새 버전을 위한 환경을 만듭니다. 이 환경은 업데이트 중인 애플리케이션의 현재 버전과 동일합니다. 새 환경이 프로비전되면 트래픽이 새 환경으로 라우팅됩니다. 새 버전에서 오류가 발생하지 않고 원하는 결과를 얻을 경우 이전 환경은 종료됩니다. 그렇지 않으면 트래픽이 이전 버전으로 복원됩니다.

+

기존 버전의 디플로이먼트와 동일한 새 디플로이먼트를 생성하여 쿠버네티스에서 블루/그린 디플로이먼트를 수행할 수 있습니다. 새 디플로이먼트의 파드가 오류 없이 실행되고 있는지 확인했으면, 트래픽을 애플리케이션의 파드로 라우팅하는 서비스의 selector 스펙을 변경하여 새 디플로이먼트로 트래픽을 보내기 시작할 수 있습니다.

+

Flux, Jenkins, Spinnaker와 같은 많은 지속적 통합(CI) 도구를 사용하면 블루/그린 배포를 자동화할 수 있습니다. 쿠버네티스 블로그에는 Jenkins를 사용한 단계별 설명이 포함되어 있습니다: Jenkins를 사용한 쿠버네티스의 제로 다운타임 배포

+

Canary 디플로이먼트 사용하기

+

Canary 배포는 블루/그린 배포의 변형으로, 변경으로 인한 위험을 크게 제거할 수 있습니다. 이 배포 전략에서는 기존 디플로이먼트와 함께 더 적은 수의 파드가 포함된 새 디플로이먼트를 생성하고 소량의 트래픽을 새 디플로이먼트로 전환하는 것입니다. 지표에서 새 버전이 기존 버전과 같거나 더 나은 성능을 보인다면, 새 디플로이먼트로 향하는 트래픽을 점진적으로 늘리면서 모든 트래픽이 새 디플로이먼트로 전환될 때까지 규모를 늘립니다. 만약 문제가 발생하면 모든 트래픽을 이전 디플로이먼트로 라우팅하고 새 디플로이먼트로의 트래픽 전송을 중단할 수 있습니다.

+

쿠버네티스는 canary 배포를 수행하는 기본 방법을 제공하지 않지만, Flagger와 같은 도구를 Istio 또는 App Mesh와 함께 사용할 수 있다.

+

상태 점검 및 자가 복구

+

버그가 없는 소프트웨어는 없지만 쿠버네티스를 사용하면 소프트웨어 오류의 영향을 최소화할 수 있습니다. 과거에는 애플리케이션이 충돌하면 누군가 애플리케이션을 수동으로 다시 시작하여 상황을 해결해야 했습니다. 쿠버네티스를 사용하면 파드의 소프트웨어 장애를 감지하고 자동으로 새 복제본으로 교체할 수 있습니다. 쿠버네티스를 사용하면 애플리케이션의 상태를 모니터링하고 비정상 인스턴스를 자동으로 교체할 수 있습니다.

+

쿠버네티스는 세 가지 유형의 상태 검사를 지원합니다.

+
    +
  1. Liveness probe
  2. +
  3. Startup probe (쿠버네티스 버전 1.16 이상에서 지원)
  4. +
  5. Readiness probe
  6. +
+

쿠버네티스 에이전트인 Kubelet은 위에서 언급한 모든 검사를 실행할 책임이 있습니다. Kubelet은 세 가지 방법으로 파드의 상태를 확인할 수 있습니다. kubelet은 파드의 컨테이너 내에서 셸 명령을 실행하거나, 컨테이너에 HTTP GET 요청을 보내거나, 지정된 포트에 TCP 소켓을 열 수 있습니다.

+

컨테이너 내에서 셸 스크립트를 실행하는 exec 기반 프로브를 선택하는 경우, TimeoutSeconds 값이 만료되기 전에 셸 명령어가 종료되는지 확인하십시오. 그렇지 않으면 노드에 노드 장애를 일으키는 <defunct> 프로세스가 생깁니다.

+

권장 사항

+

Liveness Probe를 사용하여 비정상 파드 제거

+

Liveness probe는 프로세스가 계속 실행되지만 애플리케이션이 응답하지 않는 교착 상태를 감지할 수 있습니다. 예를 들어 포트 80에서 수신 대기하는 웹 서비스를 실행 중인 경우 파드의 포트 80에서 HTTP GET 요청을 보내도록 Liveness 프로브를 구성할 수 있습니다. Kubelet은 주기적으로 GET 요청을 파드에 보내고 응답을 기다립니다. 파드가 200-399 사이에서 응답하면 kubelet은 파드가 정상이라고 간주하고, 그렇지 않으면 파드는 비정상으로 표시됩니다. 파드가 상태 체크에 계속 실패하면 kubelet은 파드를 종료합나다.

+

initialDelaySeconds를 사용하여 첫 번째 프로브를 지연시킬 수 있습니다.

+

Liveness Probe를 사용할 때는 모든 파드가 동시에 Liveness Probe에 실패하는 상황이 발생하지 않도록 해야 합니다. 쿠버네티스는 모든 파드를 교체하려고 시도하여 애플리케이션을 오프라인으로 전환하기 때문입니다. 게다가 쿠버네티스는 계속해서 새로운 파드를 만들지만 Liveness Probe도 실패할 것이기 때문에 컨트롤 플레인에 불필요한 부담을 줍니다. 파드 외부 요소(예: 외부 데이터베이스)에 의존하도록 Liveness Probe를 구성하지 마십시오. 다시 말해, 파드 외부 데이터베이스가 응답하지 않는다고 해서 파드가 Liveness Probe에 실패하는 일이 있어서는 안 됩니다.

+

Sandor Szücs의 게시물 활성 프로브는 위험하다에서는 잘못 구성된 프로브로 인해 발생할 수 있는 문제를 설명합니다.

+

시작하는 데 시간이 오래 걸리는 어플리케이션에는 Startup Probe를 사용하십시오.

+

앱을 시작하는 데 추가 시간이 필요한 경우 Startup Probe를 사용하여 Liveness 및 Readniness Probe를 지연시킬 수 있습니다. 예를 들어 데이터베이스로 부터 데이터를 캐싱해야 하는 Java 앱이 제대로 작동하려면 최대 2분이 걸릴 수 있습니다. 완전히 작동하기 전까지는 모든 Liveness 또는 Readniness Probe가 실패할 수 있습니다. Startup Probe를 구성하면 Liveness 또는 Readniness Probe를 실행하기 전에 Java 앱을 정상상태로 만들 수 있습니다.

+

Startup Probe가 성공할 때까지 다른 모든 프로브는 비활성화됩니다. 쿠버네티스가 애플리케이션 시작을 위해 대기해야 하는 최대 시간을 정의할 수 있습니다. 최대 구성 시간이 지난 후에도 파드가 여전히 스타트업 프로브에 실패하면 파드는 종료되고 새 파드가 생성됩니다.

+

Startup Probe는 Liveness Probe와 비슷합니다. 즉, 실패하면 파드가 다시 생성됩니다. Ricardo A.가 자신의 글 환상적인 프로브 및 구성 방법에서 설명했듯이, 애플리케이션 시작 시간을 예측할 수 없는 경우에는 Startup Probe를 사용해야 합니다. 애플리케이션을 시작하는 데 10초가 걸린다는 것을 알고 있다면 대신 initialDelaySeconds와 함께 Liveness/Readiness Probe를 사용해야 합니다.

+

Readiness Probe를 사용하여 부분적으로 사용할 수 없는 상태를 감지하세요

+

Liveness probe는 파드 종료(즉, 앱 재시작)를 통해 해결되는 앱 장애를 감지하는 반면, Readiness Probe는 앱을 temporarily 사용할 수 없는 상태를 감지합니다. 이러한 상황에서는 앱이 일시적으로 응답하지 않을 수 있지만 이 작업이 완료되면 다시 정상이 될 것으로 예상됩니다.

+

예를 들어, 집중적인 디스크 I/O 작업 중에는 애플리케이션이 일시적으로 요청을 처리할 수 없을 수 있습니다. 여기서 애플리케이션의 파드를 종료하는 것은 해결책이 아니며, 동시에 파드로 전송된 추가 요청이 실패할 수 있습니다.

+

Readiness Probe를 사용하여 앱의 일시적인 가용성 중단을 감지하고 다시 작동할 때까지 해당 파드에 대한 요청 전송을 중단할 수 있습니다. 실패로 인해 파드가 재생성되는 Liveness Probe와 달리, Readiness Probe가 실패하면 파드는 쿠버네티스 서비스로부터 어떠한 트래픽도 수신하지 않게 됩니다. Readiness Probe가 성공하면 파드는 서비스로부터 트래픽을 다시 수신합니다.

+

Liveness Probe와 마찬가지로 파드 외부의 리소스(예: 데이터베이스)에 의존하는 Readiness Probe를 구성하지 마십시오. 다음은 잘못 구성된 Readiness로 인해 애플리케이션이 작동하지 않을 수 있는 시나리오입니다. 앱의 데이터베이스에 연결할 수 없을 때 파드의 Readiness Probe에 장애가 발생하면 다른 파드 복제본도 동일한 상태 점검 기준을 공유하므로 동시에 실패합니다. 이러한 방식으로 프로브를 설정하면 데이터베이스를 사용할 수 없을 때마다 파드의 Readiness Probe가 실패하고 쿠버네티스가 all 파드로 트래픽 전송을 중지할 수 있습니다.

+

Readiness Probes 사용의 부작용은 디플로이먼트를 업데이트하는 데 걸리는 시간을 늘릴 수 있다는 것입니다. Readiness Probe가 성공하지 않는 한 새 복제본은 트래픽을 수신하지 않습니다. 그때까지는 기존 복제본이 계속해서 트래픽을 수신하게 됩니다.

+
+

장애 처리

+

파드의 수명은 유한합니다. - 파드를 오래 실행하더라도 때가 되면 파드가 올바르게 종료되도록 하는 것이 현명합니다. 업그레이드 전략에 따라 쿠버네티스 클러스터를 업그레이드하려면 새 워커 노드를 생성해야 할 수 있으며, 이 경우 모든 파드를 새 노드에서 다시 생성해야 합니다. 적절한 종료 처리 및 파드 중단 예산을 마련하면 파드가 이전 노드에서 제거되고 새 노드에서 재생성될 때 서비스 중단을 피할 수 있습니다.

+

워커 노드를 업그레이드하는 가장 좋은 방법은 새 워커 노드를 만들고 기존 워커 노드를 종료하는 것입니다. 워커 노드를 종료하기 전에 먼저 워커 노드를 drain 해야 합니다. 워커 노드가 비워지면 해당 노드의 모든 파드가 안전하게 제거됩니다. 여기서 가장 중요한 단어는 안전입니다. 워커 노드에서 파드가 제거되면 단순히 SIGKILL 시그널이 전송되는 것이 아닙니다. 대신, SIGTERM 신호가 제거되는 파드에 있는 각 컨테이너의 메인 프로세스(PID 1)로 보내진다. SIGTERM 신호가 전송된 후, 쿠버네티스는 프로세스에 SIGKILL 신호가 전송되기까지 일정 시간(유예 기간)을 줍니다. 이 유예 기간은 기본적으로 30초입니다. kubectl에서 grace-period 플래그를 사용하여 기본값을 재정의하거나 Podspec에서 terminationGracePeriodSeconds를 선언할 수 있습니다.

+

kubectl delete pod <pod name> —grace-period=<seconds>

+

메인 프로세스에 PID 1이 없는 컨테이너를 사용하는 것이 일반적입니다. 다음과 같은 Python 기반 샘플 컨테이너를 고려해 보십시오.

+
$ kubectl exec python-app -it ps
+ PID USER TIME COMMAND
+ 1   root 0:00 {script.sh} /bin/sh ./script.sh
+ 5   root 0:00 python app.py
+
+

이 예제에서 셸 스크립트는 SIGTERM을 수신하는데, 이 예제의 메인 프로세스는 파이썬 응용 프로그램이지만 SIGTERM 신호를 받지 않습니다. 파드가 종료되면, 파이썬 애플리케이션이 갑자기 종료됩니다. 이 문제는 컨테이너의 ENTRYPOINT를 변경하여 파이썬 애플리케이션을 실행함으로써 해결할 수 있습니다. 또는 dumb-init과 같은 도구를 사용하여 애플리케이션이 신호를 처리할 수 있도록 할 수 있습니다.

+

컨테이너 후크를 사용하여 컨테이너 시작 또는 중지 시 스크립트 또는 HTTP 요청을 실행할 수도 있습니다. Prestop 후크 액션은 컨테이너가 SIGTERM 신호를 수신하기 전에 실행되며 이 신호가 전송되기 전에 완료되어야 합니다. terminationGracePeriodSeconds 값은 SIGTERM 신호가 전송될 때가 아니라 PreStop 후크 액션이 실행되기 시작할 때부터 적용됩니다.

+

권장 사항

+

Pod Disruption Budget으로 중요한 워크로드를 보호하세요

+

Pod Disruption Budget 또는 PDB는 애플리케이션의 복제본 수가 선언된 임계값 아래로 떨어지면 제거 프로세스를 일시적으로 중단할 수 있습니다. 사용 가능한 복제본 수가 임계값을 초과하면 제거 프로세스가 계속됩니다. PDB를 사용하여 복제본의 minAvailablemaxUnavailable 수를 선언할 수 있습니다. 예를 들어 앱 복제본을 3개 이상 사용할 수 있게 하려면 PDB를 만들 수 있습니다.

+
apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+  name: my-svc-pdb
+spec:
+  minAvailable: 3
+  selector:
+    matchLabels:
+      app: my-svc
+
+

위의 PDB 정책은 쿠버네티스에게 3개 이상의 복제본을 사용할 수 있을 때까지 제거 프로세스를 중단하도록 지시합니다. 노드 드레이닝은 PodDisruptionBudgets을 고려합니다. EKS 관리형 노드 그룹 업그레이드 중에는 15분 타임아웃으로 노드가 고갈됩니다. 15분 후 업데이트를 강제 실행하지 않으면(EKS 콘솔에서는 롤링 업데이트라고 함) 업데이트가 실패합니다. 업데이트를 강제로 적용하면 파드가 삭제됩니다.

+

자체 관리형 노드의 경우 AWS Node Termination Handler와 같은 도구를 사용할 수도 있습니다. 이 도구를 사용하면 Kubernetes 컨트롤 플레인이 EC2 유지 관리 이벤트 및 EC2 스팟 중단 등 EC2 인스턴스를 사용할 수 없게 될 수 있는 이벤트에 적절하게 대응합니다. 쿠버네티스 API를 사용하여 노드를 비우고 새 파드가 스케줄되지 않도록 한 다음, 파드를 드레이닝하여 실행 중인 파드를 종료한다.

+

파드 anti-affinity를 사용해 디플로이먼트의 파드를 다른 노드에 스케줄링하고 노드 업그레이드 중 PDB 관련 지연을 피할 수 있습니다.

+

카오스 엔지니어링 연습

+
+

카오스 엔지니어링은 프로덕션에서의 격렬한 조건을 견딜 수 있는 시스템의 성능에 대한 신뢰를 구축하기 위해 분산 시스템을 실험하는 분야입니다.

+
+

Dominik Tornow는 자신의 블로그 쿠버네티스는 선언적 시스템에서 “사용자가 원하는 시스템 상태를 시스템에 표시합니다. 그런 다음 시스템은 현재 상태와 원하는 상태를 고려하여 현재 상태에서 원하는 상태로 전환하기 위한 명령 순서를 결정합니다.”라고 설명합니다. 즉, 쿠버네티스는 항상 원하는 상태를 저장하고 시스템이 이를 벗어나면 쿠버네티스는 상태를 복원하기 위한 조치를 취합니다. 예를 들어 워커 노드를 사용할 수 없게 되면 쿠버네티스는 파드를 다른 워커 노드로 다시 스케줄합니다. 마찬가지로, replica가 충돌하면 디플로이먼트 컨트롤러가 새 replica를 생성합니다. 이런 방식으로 쿠버네티스 컨트롤러는 장애를 자동으로 수정합니다.

+

Gremlin과 같은 카오스 엔지니어링 도구를 사용하면 쿠버네티스 클러스터의 복원력을 테스트하고 단일 장애 지점을 식별할 수 있습니다. 클러스터(및 그 이상)에 인위적인 혼돈을 유발하는 도구를 사용하면 시스템 약점을 발견하고 병목 현상과 잘못된 구성을 식별하며 통제된 환경에서 문제를 수정할 수 있습니다. 카오스 엔지니어링 철학은 의도적으로 문제를 해결하고 인프라에 스트레스를 주어 예상치 못한 다운타임을 최소화하는 것을 권장합니다.

+

서비스 메시 사용

+

서비스 메시를 사용하여 애플리케이션의 복원력을 개선할 수 있습니다. 서비스 메시는 서비스 간 통신을 가능하게 하고 마이크로서비스 네트워크의 가시성을 높입니다. 대부분의 서비스 메시 제품은 애플리케이션의 네트워크 트래픽을 가로채고 검사하는 소규모 네트워크 프록시를 각 서비스와 함께 실행하는 방식으로 작동합니다. 애플리케이션을 수정하지 않고도 애플리케이션을 메시에 배치할 수 있습니다. 서비스 프록시에 내장된 기능을 사용하여 네트워크 통계를 생성하고, 액세스 로그를 생성하고, 분산 추적을 위한 아웃바운드 요청에 HTTP 헤더를 추가하도록 할 수 있습니다.

+

서비스 메시를 사용하면 자동 요청 재시도, 제한 시간, 회로 차단, 속도 제한과 같은 기능을 통해 마이크로서비스의 복원력을 높일 수 있습니다.

+

여러 클러스터를 운영하는 경우 서비스 메시를 사용하여 클러스터 간 서비스 간 통신을 활성화할 수 있습니다.

+

서비스 메시

+ +
+

Observability

+

Observability는 모니터링, 로깅, 추적을 포함하는 포괄적인 용어입니다. 마이크로서비스 기반 애플리케이션은 기본적으로 배포됩니다. 단일 시스템을 모니터링하는 것으로 충분한 모놀리식 애플리케이션과 달리 분산 애플리케이션 아키텍처에서는 각 구성 요소의 성능을 모니터링해야 합니다. 클러스터 수준 모니터링, 로깅 및 분산 추적 시스템을 사용하여 고객이 중단되기 전에 클러스터의 문제를 식별할 수 있습니다.

+

문제 해결 및 모니터링을 위한 쿠버네티스 내장 도구는 제한적입니다. 메트릭 서버는 리소스 메트릭을 수집하여 메모리에 저장하지만 유지하지는 않습니다. kubectl을 사용하여 파드의 로그를 볼 수 있지만, 쿠버네티스는 로그를 자동으로 보관하지 않습니다. 그리고 분산 추적 구현은 애플리케이션 코드 수준에서 또는 서비스 메시를 사용하여 수행됩니다.

+

쿠버네티스의 확장성은 여기서 빛을 발합니다. 쿠버네티스를 사용하면 선호하는 중앙 집중식 모니터링, 로깅 및 추적 솔루션을 가져올 수 있습니다.

+

권장 사항

+

애플리케이션 모니터링

+

최신 애플리케이션에서 모니터링해야 하는 지표의 수는 계속 증가하고 있습니다. 애플리케이션을 자동으로 추적하면 고객의 문제를 해결하는데 집중할 수 있어 도움이 됩니다. Prometheus 또는 CloudWatch Container Insights와 같은 클러스터 전반의 모니터링 도구는 클러스터 및 워크로드를 모니터링하고 문제가 발생할 때 또는 가급적이면 문제가 발생하기 전에 신호를 제공할 수 있습니다.

+

모니터링 도구를 사용하면 운영 팀이 구독할 수 있는 알림을 생성할 수 있습니다. 악화 시 가동 중단으로 이어지거나 애플리케이션 성능에 영향을 미칠 수 있는 이벤트에 대해 경보를 활성화하는 규칙을 고려해 보십시오.

+

어떤 메트릭을 모니터링해야 할지 잘 모르겠다면 다음 방법에서 영감을 얻을 수 있습니다.

+
    +
  • RED method. 요청, 오류, 기간을 나타냅니다.
  • +
  • USE method. 사용률, 포화도, 오류를 나타냅니다.
  • +
+

Sysdig의 게시물 쿠버네티스 알림 모범 사례에는 애플리케이션 가용성에 영향을 미칠 수 있는 구성 요소의 포괄적인 목록이 포함되어 있습니다.

+

프로메테우스 클라이언트 라이브러리를 사용하여 애플리케이션 메트릭을 공개하세요

+

애플리케이션 상태를 모니터링하고 표준 메트릭을 집계하는 것 외에도 프로메테우스 클라이언트 라이브러리를 사용하여 애플리케이션별 사용자 지정 메트릭을 공개하여 애플리케이션의 가시성을 개선할 수 있습니다.

+

중앙 집중식 로깅 도구를 사용하여 로그를 수집하고 유지합니다.

+

EKS 로깅은 컨트롤 플레인 로그와 애플리케이션 로그의 두 가지 범주에 속합니다. EKS 컨트롤 플레인 로깅은 컨트롤 플레인의 감사 및 진단 로그를 계정의 CloudWatch Logs로 직접 제공합니다. 애플리케이션 로그는 클러스터 내에서 실행되는 파드에서 생성되는 로그입니다. 애플리케이션 로그에는 비즈니스 로직 애플리케이션을 실행하는 파드와 CoreDNS, Cluster Autoscaler, Prometheus 등과 같은 쿠버네티스 시스템 컴포넌트에서 생성된 로그가 포함됩니다.

+

EKS는 다섯 가지 유형의 컨트롤 플레인 로그를 제공합니다.:

+
    +
  1. 쿠버네티스 API 서버 구성 요소 로그
  2. +
  3. 감사
  4. +
  5. 인증자(Authenticator)
  6. +
  7. 컨트롤러 매니저
  8. +
  9. 스케줄러
  10. +
+

컨트롤러 관리자 및 스케줄러 로그는 병목 현상 및 오류와 같은 컨트롤 플레인 문제를 진단하는 데 도움이 될 수 있습니다. 기본적으로 EKS 컨트롤 플레인 로그는 CloudWatch Logs로 전송되지 않습니다. 컨트롤 플레인 로깅을 활성화하고 계정의 각 클러스터에 대해 캡처하려는 EKS 컨트롤 플레인 로그의 유형을 선택할 수 있습니다.

+

애플리케이션 로그를 수집하려면 클러스터에 Fluent Bit, Fluentd 또는 CloudWatch Container Insights와 같은 로그 수집 도구를 설치해야 합니다.

+

쿠버네티스 로그 애그리게이터 도구는 데몬셋으로 실행되며 노드의 컨테이너 로그를 스크랩합니다. 그러면 애플리케이션 로그가 중앙 집중식 대상으로 전송되어 저장됩니다. 예를 들어 CloudWatch 컨테이너 인사이트는 Fluent Bit 또는 Fluentd를 사용하여 로그를 수집하고 이를 CloudWatch Logs로 전송하여 저장할 수 있습니다. Fluent Bit과 Fluentd는 Elasticsearch 및 InfluxDB와 같은 널리 사용되는 여러 로그 분석 시스템을 지원하므로 Fluent Bit 또는 Fluentd의 로그 구성을 수정하여 로그의 스토리지 백엔드를 변경할 수 있습니다.

+

분산 추적 시스템을 사용하여 병목 현상을 식별하십시오.

+

일반적인 최신 응용 프로그램에는 네트워크를 통해 구성 요소가 분산되어 있으며 응용 프로그램을 구성하는 각 구성 요소가 제대로 작동하는지에 따라 신뢰성이 달라집니다. 분산 추적 솔루션을 사용하면 요청의 흐름과 시스템이 통신하는 방식을 이해할 수 있습니다. 추적을 통해 애플리케이션 네트워크에서 병목 현상이 발생하는 위치를 파악하고 연쇄적 장애를 일으킬 수 있는 문제를 예방할 수 있습니다.

+

애플리케이션에서 추적을 구현하는 방법에는 두 가지가 있습니다. 공유 라이브러리를 사용하여 코드 수준에서 분산 추적을 구현하거나 서비스 메시를 사용할 수 있습니다.

+

코드 수준에서 추적을 구현하는 것은 불리할 수 있습니다. 이 메서드에서는 코드를 변경해야 합니다. 다국어 응용 프로그램을 사용하는 경우 이는 더 복잡합니다. 또한 서비스 전체에 걸쳐 또 다른 라이브러리를 유지 관리할 책임도 있습니다.

+

LinkerD, Istio, AWS App Mesh와 같은 서비스 메시를 사용하면 애플리케이션 코드를 최소한으로 변경하여 애플리케이션에서 분산 추적을 구현할 수 있습니다. 서비스 메시를 사용하여 지표 생성, 로깅 및 추적을 표준화할 수 있습니다.

+

AWS X-Ray, Jaeger와 같은 추적 도구는 공유 라이브러리와 서비스 메시 구현을 모두 지원합니다.

+

(공유 라이브러리 및 서비스 메시) 구현을 모두 지원하는 AWS X-Ray 또는 Jaeger와 같은 추적 도구를 사용해 보싮시오. 그러면 나중에 서비스 메시를 채택할 때 도구를 전환하지 않아도 됩니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/reliability/docs/controlplane/index.html b/ko/reliability/docs/controlplane/index.html new file mode 100644 index 000000000..62d6178ae --- /dev/null +++ b/ko/reliability/docs/controlplane/index.html @@ -0,0 +1,2530 @@ + + + + + + + + + + + + + + + + + + + + + + + 컨트롤 플레인 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

EKS 컨트롤 플레인

+

Amazon Elastic Kubernetes Service(EKS)는 자체 쿠버네티스 컨트롤 플레인 또는 워커 노드를 설치, 운영 및 유지 관리할 필요 없이 AWS에서 쉽게 쿠버네티스를 실행할 수 있게 해주는 관리형 쿠버네티스 서비스입니다. 업스트림 쿠버네티스를 실행하며 쿠버네티스 규정 준수 인증을 받았습니다. 이러한 규정 준수를 통해 EKS는 EC2 또는 온프레미스에 설치할 수 있는 오픈 소스 커뮤니티 버전과 마찬가지로 쿠버네티스 API를 지원합니다. 업스트림 쿠버네티스에서 실행되는 기존 애플리케이션은 Amazon EKS와 호환됩니다.

+

EKS는 쿠버네티스 컨트롤 플레인 노드의 가용성과 확장성을 자동으로 관리하고 비정상 컨트롤 플레인 노드를 자동으로 대체합니다.

+

EKS 아키텍처

+

EKS 아키텍처는 쿠버네티스 컨트롤 플레인의 가용성과 내구성을 손상시킬 수 있는 단일 장애 지점을 제거하도록 설계되었습니다.

+

EKS로 관리되는 쿠버네티스 컨트롤 플레인은 EKS 관리형 VPC 내에서 실행됩니다. EKS 컨트롤 플레인은 쿠버네티스 API 서버 노드, 기타 클러스터로 구성됩니다. API 서버, 스케줄러, kube-controller-manager와 같은 구성 요소를 실행하는 쿠버네티스 API 서버 노드는 오토 스케일링 그룹에서 실행됩니다. EKS는 AWS 리전 내의 별개의 가용 영역(AZ)에서 최소 2개의 API 서버 노드를 실행합니다. 마찬가지로 내구성을 위해 etcd 서버 노드도 3개의 AZ에 걸친 자동 크기 조정 그룹에서 실행됩니다. EKS는 각 AZ에서 NAT 게이트웨이를 실행하고, API 서버 및 etcd 서버는 프라이빗 서브넷에서 실행됩니다. 이 아키텍처는 단일 AZ의 이벤트가 EKS 클러스터의 가용성에 영향을 미치지 않도록 합니다.

+

새 클러스터를 생성하면 Amazon EKS는 클러스터와 통신하는 데 사용하는 관리형 쿠버네티스 API 서버를 위한 고가용성 엔드포인트를 생성합니다(kubectl과 같은 도구 사용). 관리형 엔드포인트는 NLB를 사용하여 쿠버네티스 API 서버의 부하를 분산합니다. 또한 EKS는 워커 노드와의 원활한 통신을 위해 서로 다른 AZ에 두 개의 ENI를 프로비저닝합니다.

+

EKS 데이터 플레인 네트워크 연결

+

쿠버네티스 클러스터의 API 서버는 퍼블릭 인터넷(퍼블릭 엔드포인트 사용) 또는 VPC(EKS 관리 ENI 사용) 또는 둘 다를 통해 연결할 수 있습니다.

+

사용자와 워커 노드가 퍼블릭 엔드포인트를 사용하여 API 서버에 연결하든 EKS에서 관리하는 ENI를 사용하든 관계없이 연결을 위한 중복 경로가 있습니다.

+

권장 사항

+

컨트롤 플레인 메트릭 모니터링

+

쿠버네티스 API 메트릭을 모니터링하면 컨트롤 플레인 성능에 대한 통찰력을 얻고 문제를 식별할 수 있습니다. 비정상 컨트롤 플레인은 클러스터 내에서 실행되는 워크로드의 가용성을 손상시킬 수 있습니다. 예를 들어 잘못 작성된 컨트롤러는 API 서버에 과부하를 일으켜 애플리케이션의 가용성에 영향을 미칠 수 있습니다.

+

쿠버네티스는 /metrics 엔드포인트에서 컨트롤 플레인 메트릭을 노출합니다.

+

kubectl을 사용하여 노출된 메트릭을 볼 수 있습니다.

+
kubectl get --raw /metrics
+
+

이러한 지표는 프로메테우스 텍스트 형식으로 표시됩니다.

+

프로메테우스를 사용하여 이러한 지표를 수집하고 저장할 수 있습니다. 2020년 5월, CloudWatch는 CloudWatch Container Insights에 프로메테우스 지표 모니터링에 대한 지원을 추가했습니다. 따라서 Amazon CloudWatch를 사용하여 EKS 컨트롤 플레인을 모니터링할 수도 있습니다. 새 Prometheus 스크랩 대상 추가 자습서: 프로메테우스 KPI 서버 지표를 사용하여 지표를 수집하고 CloudWatch 대시보드를 생성하여 클러스터의 컨트롤 플레인을 모니터링할 수 있습니다.

+

쿠버네티스 API 서버 메트릭은 여기에서 찾을 수 있습니다. 예를 들어, apiserver_request_duration_seconds는 API 요청을 실행하는 데 걸리는 시간을 나타낼 수 있습니다.

+

다음과 같은 컨트롤 플레인 메트릭을 모니터링해 보십시오.

+

API 서버

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
메트릭설명
apiserver_request_total각 메소드, 드라이 런 값, 그룹, 버전, 리소스, 범위, 구성 요소, HTTP 응답 코드에 대해 구분된 API 서버 요청 카운터입니다.
apiserver_request_duration_seconds*각 메소드, 드라이 런 값, 그룹, 버전, 리소스, 하위 리소스, 범위, 구성 요소에 대한 응답 지연 시간 분포(초 단위)
apiserver_admission_controller_admission_duration_secondsadmission controller 지연 시간 히스토그램(초), 이름으로 식별되며 각 작업, API 리소스 및 유형별로 구분됨(검증 또는 승인).
apiserver_admission_webhook_rejection_countadmission webhook 거부 건수.이름, 작업, 거부 코드, 유형(검증 또는 승인), 오류 유형(calling_webhook_error, apiserver_internal_error, no_error) 으로 식별됩니다.
rest_client_request_duration_seconds요청 지연 시간(초)동사와 URL별로 분류되어 있습니다.
rest_client_requests_total상태 코드, 메서드, 호스트별로 파티션을 나눈 HTTP 요청 수
+

etcd

+ + + + + + + + + + + + + + + + + +
메트릭설명
etcd_request_duration_seconds각 작업 및 객체 유형에 대한 Etcd 요청 지연 시간(초)
etcd_db_total_size_in_bytes 또는
apiserver_storage_db_total_size_in_bytes (EKS v1.26부터 시작)
Etcd 데이터베이스 크기
+

쿠버네티스 모니터링 개요 대시보드를 사용하여 쿠버네티스 API 서버 요청과 지연 시간 및 etcd 지연 시간 메트릭을 시각화하고 모니터링하는 것을 고려해 보십시오.

+

다음 프로메테우스 쿼리를 사용하여 etcd의 현재 크기를 모니터링할 수 있습니다. 이 쿼리는 API 메트릭 엔드포인트에서 메트릭을 스크랩하는 kube-apiserver라는 작업이 있고 EKS 버전이 v1.26 미만인 것으로 가정합니다.

+
max(etcd_db_total_size_in_bytes{job="kube-apiserver"} / (8 * 1024 * 1024 * 1024))
+
+

클러스터 인증

+

EKS는 현재 bearer/서비스 계정 토큰웹훅 토큰 인증을 사용하는 IAM 인증 등 두 가지 유형의 인증을 지원합니다. 사용자가 쿠버네티스 API를 호출하면 웹훅는 요청에 포함된 인증 토큰을 IAM에 전달합니다. base 64로 서명된 URL인 토큰은 AWS 명령줄 인터페이스(AWS CLI)에 의해 생성됩니다.

+

EKS 클러스터를 생성하는 IAM 사용자 또는 역할은 자동으로 클러스터에 대한 전체 액세스 권한을 얻습니다. aws-auth configmap을 편집하여 EKS 클러스터에 대한 액세스를 관리할 수 있습니다.

+

aws-auth 컨피그맵을 잘못 구성하여 클러스터에 대한 액세스 권한을 잃은 경우에도 클러스터 생성자의 사용자 또는 역할을 사용하여 EKS 클러스터에 액세스할 수 있습니다.

+

드문 경우이긴 하지만 AWS 리전에서 IAM 서비스를 사용할 수 없는 경우에도 쿠버네티스 서비스 계정의 bearer 토큰을 사용하여 클러스터를 관리할 수 있습니다.

+

클러스터에서 모든 작업을 수행할 수 있는 “super-admin” 계정을 생성하십시오.

+
kubectl -n kube-system create serviceaccount super-admin
+
+

super-admin cluster-admin 역할을 부여하는 역할 바인딩을 생성합니다.

+
kubectl create clusterrolebinding super-admin-rb --clusterrole=cluster-admin --serviceaccount=kube-system:super-admin
+
+

서비스 계정 시크릿 가져오기:

+
secret_name=`kubectl -n kube-system get serviceaccount/super-admin -o jsonpath=' {.secrets [0] .name} '`
+
+

시크릿과 관련된 토큰 가져오기:

+
SECRET_NAME=`kubectl -n kube-system get serviceaccount/super-admin -o jsonpath='{.secrets[0].name}'`
+
+

서비스 계정과 토큰을 `kubeconfig'에 추가합니다.

+
TOKEN=`kubectl -n kube-system get secret $SECRET_NAME -o jsonpath='{.data.token}'| base64 --decode`
+
+

super-admin 계정을 사용하도록 kubeconfig에서 현재 컨텍스트를 설정합니다.

+
kubectl config set-credentials super-admin --token=$TOKEN
+
+

최종 kubeconfig는 다음과 같아야 합니다.

+
apiVersion: v1
+clusters:
+- cluster:
+    certificate-authority-data:<REDACTED>
+    server: https://<CLUSTER>.gr7.us-west-2.eks.amazonaws.com
+  name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+contexts:
+- context:
+    cluster: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+    user: super-admin
+  name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+current-context: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+kind: Config
+preferences: {}
+users:
+#- name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+#  user:
+#    exec:
+#      apiVersion: client.authentication.k8s.io/v1alpha1
+#      args:
+#      - --region
+#      - us-west-2
+#      - eks
+#      - get-token
+#      - --cluster-name
+#      - <<cluster name>>
+#      command: aws
+#      env: null
+- name: super-admin
+  user:
+    token: <<super-admin sa’s secret>>
+
+

Admission Webhooks

+

쿠버네티스에는 admission webhooks 검증 및 변경이라는 두 가지 유형의 admission webhooks이 있습니다. 이를 통해 사용자는 쿠버네티스 API를 확장하고 API에서 객체를 승인하기 전에 객체를 검증하거나 변경할 수 있습니다. 이러한 웹훅를 잘못 구성하면 클러스터의 중요한 작업이 차단되어 EKS 컨트롤 플레인이 불안정해질 수 있습니다.

+

클러스터 크리티컬 작업에 영향을 주지 않으려면 다음과 같은 “catch-all” 웹훅을 설정하지 마십시오.

+
- name: "pod-policy.example.com"
+  rules:
+  - apiGroups:   ["*"]
+    apiVersions: ["*"]
+    operations:  ["*"]
+    resources:   ["*"]
+    scope: "*"
+
+

또는 웹훅에 30초 미만의 제한 시간을 가진 Fail Open 정책이 있는지 확인하여 웹훅를 사용할 수 없는 경우 클러스터의 중요한 워크로드에 영향을 주지 않도록 하십시오.

+

안전하지 않은 sysctls가 있는 파드를 차단한다.

+

Sysctl은 사용자가 런타임 중에 커널 파라미터를 수정할 수 있는 리눅스 유틸리티입니다. 이러한 커널 매개변수는 네트워크, 파일 시스템, 가상 메모리, 프로세스 관리 등 운영 체제 동작의 다양한 측면을 제어합니다.

+

쿠버네티스를 사용하면 파드에 sysctl 프로필을 할당할 수 있다.쿠버네티스는 systcls를 안전한 것과 안전하지 않은 것으로 분류합니다. 안전한 sysctls는 컨테이너 또는 파드에 네임스페이스가 지정되며, 이를 설정해도 노드의 다른 파드나 노드 자체에는 영향을 주지 않습니다. 반대로 안전하지 않은 sysctl은 다른 파드를 방해하거나 노드를 불안정하게 만들 수 있으므로 기본적으로 비활성화되어 있습니다.

+

안전하지 않은 sysctls가 기본적으로 비활성화되므로, kubelet은 안전하지 않은 sysctl 프로필을 가진 파드를 생성하지 않습니다. 이러한 파드를 생성하면, 스케줄러는 해당 파드를 노드에 반복적으로 할당하지만 노드는 실행에 실패합니다. 이 무한 루프는 궁극적으로 클러스터 컨트롤 플레인에 부담을 주어 클러스터를 불안정하게 만듭니다.

+

안전하지 않은 sysctls가 있는 파드를 거부하려면 OPA 게이트키퍼 또는 Kyverno를 사용하는 것을 고려해 보십시오.

+

클러스터 업그레이드 처리

+

2021년 4월부터 쿠버네티스 릴리스 주기가 연간 4개 릴리스(분기에 한 번)에서 연간 세 번의 릴리스로 변경되었습니다. 새 마이너 버전(예: 1.21 또는 1.22) 은 대략 15주마다 릴리스됩니다. 쿠버네티스 1.19부터 각 마이너 버전은 처음 릴리스된 후 약 12개월 동안 지원됩니다. 쿠버네티스는 최소 두 개의 마이너 버전에 대해 컨트롤 플레인과 워커 노드 간의 호환성을 지원합니다.

+

쿠버네티스 커뮤니티의 쿠버네티스 버전 지원에 따라 EKS는 언제든지 최소 3개의 프로덕션 버전의 쿠버네티스 제공하며, 네 번째 버전은 지원 중단될 예정입니다.

+

EKS는 지원 종료일 최소 60일 전에 해당 쿠버네티스 마이너 버전의 지원 중단을 발표합니다. 지원 종료일이 되면 지원 중단된 버전을 실행하는 클러스터는 EKS가 지원하는 다음 쿠버네티스 버전으로 자동 업데이트되기 시작합니다.

+

EKS는 쿠버네티스EKS 플랫폼 버전 모두에 대해 in-place 클러스터 업그레이드를 수행합니다. 이를 통해 클러스터 운영이 단순화되고 다운타임 없이 최신 쿠버네티스 기능을 활용하고 보안 패치를 적용할 수 있습니다.

+

새 쿠버네티스 버전에는 중요한 변경 사항이 적용되며 업그레이드 후에는 클러스터를 다운그레이드할 수 없습니다. 최신 쿠버네티스 버전으로 원활하게 전환하려면 클러스터 업그레이드 처리를 위한 프로세스를 잘 문서화해야 합니다. 최신 쿠버네티스 버전으로 업그레이드할 때 in-place 클러스터 업그레이드를 수행하는 대신 새 클러스터로 마이그레이션하는 것을 고려할 수 있습니다. VMware의 Velero와 같은 클러스터 백업 및 복원 도구를 사용하면 새 클러스터로 마이그레이션하는데 도움이 될 수 있습니다.

+
    +
  • 새 버전에서는 기존 애플리케이션을 손상시킬 수 있는 API와 기능을 더 이상 사용하지 못할 수 있으므로 쿠버네티스 지원 중단 정책을 숙지해야 합니다.
  • +
  • 클러스터를 업그레이드하기 전에 쿠버네티스 변경 로그Amazon EKS 쿠버네티스 버전을 검토하여 워크로드에 미치는 부정적인 영향을 파악해야 합니다.
  • +
  • 비프로덕션 환경에서 클러스터 업그레이드를 테스트하고 현재 워크로드 및 컨트롤러에 미치는 영향을 파악해 보십시오. 새 쿠버네티스 버전으로 이동하기 전에 애플리케이션, 컨트롤러 및 사용자 지정 통합의 호환성을 테스트하는 지속적 통합 워크플로를 구축하여 테스트를 자동화할 수 있습니다.
  • +
  • 클러스터를 업그레이드한 후 쿠버네티스 애드온을 업그레이드해야 할 수도 있습니다. Amazon EKS 클러스터 쿠버네티스 버전 업데이트를 검토하여 클러스터 애드온과 클러스터 버전의 호환성을 검증하십시오.
  • +
  • 컨트롤 플레인 로깅을 켜고 로그에서 오류가 있는지 검토해 보십시오.
  • +
  • EKS 클러스터를 관리할 때는 eksctl을 사용하는 것을 고려해 보십시오. eksctl을 사용하여 컨트롤 플레인, 애드온, 워커 노드 업데이트할 수 있습니다.
  • +
  • EKS 컨트롤 플레인 업그레이드에는 워커 노드 업그레이드가 포함되지 않습니다. EKS 워커 노드 업데이트는 사용자의 책임입니다. 워커 노드 업그레이드 프로세스를 자동화하려면 EKS 관리 노드 그룹 또는 EKS on Fargate를 사용하는 것을 고려해 보십시오.
  • +
  • 필요한 경우 kubectl convert 플러그인을 사용하여 쿠버네티스 매니페스트 파일을 다른 API 버전 간에 변환할 수 있습니다
  • +
+

대규모 클러스터 실행

+

EKS는 컨트롤 플레인 인스턴스의 부하를 능동적으로 모니터링하고 자동으로 확장하여 고성능을 보장합니다. 하지만 대규모 클러스터를 실행할 때는 쿠버네티스 및 AWS 서비스의 할당량 내에서 발생할 수 있는 성능 문제와 한계를 고려해야 합니다.

+
    +
  • ProjectCalico 팀에서 수행한 테스트에 따르면, 서비스가 1000개 이상인 클러스터에서 iptables 모드에서 kube-proxy를 사용할 경우 네트워크 지연이 발생할 수 있습니다. 해결 방법은 ipvs 모드에서 kube-proxy로 실행으로 전환하는 것입니다.
  • +
  • CNI에서 파드의 IP 주소를 요청해야 하거나 새 EC2 인스턴스를 자주 생성해야 하는 경우에도 EC2 API 요청 제한이 발생할 수 있습니다. IP 주소를 캐싱하도록 CNI를 구성하면 EC2 API 호출을 줄일 수 있습니다. 더 큰 EC2 인스턴스 유형을 사용하여 EC2 조정 이벤트를 줄일 수 있습니다.
  • +
+

한도 및 서비스 할당량 알아보기

+

AWS는 실수로 리소스를 과도하게 프로비저닝하는 것을 방지하기 위해 서비스 한도(팀이 요청할 수 있는 각 리소스 수의 상한선)를 설정합니다. Amazon EKS 서비스 할당량에는 서비스 한도가 나와 있습니다. AWS 서비스 할당량을 사용하여 변경할 수 있는 두 가지 유형의 한도, Soft limit가 있습니다. Hard limit는 변경할 수 없습니다. 애플리케이션을 설계할 때는 이러한 값을 고려해야 합니다. 이러한 서비스 제한을 정기적으로 검토하여 애플리케이션 설계 중에 통합하는 것이 좋습니다.

+
    +
  • 오케스트레이션 엔진의 제한 외에도 ELB(Elastic Load Balancing) 및 Amazon VPC와 같은 다른 AWS 서비스에는 애플리케이션 성능에 영향을 미칠 수 있는 제한이 있습니다.
  • +
  • EC2 한도에 대한 자세한 내용은 EC2 서비스 제한을 참조하십시오.
  • +
  • 각 EC2 인스턴스는 Amazon 제공 DNS 서버로 전송할 수 있는 패킷 수를 네트워크 인터페이스당 초당 최대 1024 패킷으로 제한합니다.
  • +
  • EKS 환경에서 etcd 스토리지 한도는 업스트림 지침에 따라 8GB입니다. etcd db 크기를 추적하려면 etcd_db_total_size_in_bytes 지표를 모니터링하십시오. 이 모니터링을 설정하려면 경고 규칙 etcdBackendQuotaLowSpaceetcdExcessiveDatabaseGrowth를 참조할 수 있습니다.
  • +
+

추가 리소스:

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/reliability/docs/dataplane/index.html b/ko/reliability/docs/dataplane/index.html new file mode 100644 index 000000000..4e28c3d2c --- /dev/null +++ b/ko/reliability/docs/dataplane/index.html @@ -0,0 +1,2708 @@ + + + + + + + + + + + + + + + + + + + + + + + 데이터 플레인 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

EKS 데이터 플레인

+

가용성과 복원력이 뛰어난 애플리케이션을 운영하려면 가용성과 복원력이 뛰어난 데이터 플레인이 필요합니다. 탄력적인 데이터 플레인을 사용하면 쿠버네티스가 애플리케이션을 자동으로 확장하고 복구할 수 있습니다. 복원력이 뛰어난 데이터 플레인은 2개 이상의 워커 노드로 구성되며, 워크로드에 따라 확장 및 축소될 수 있으며 장애 발생 시 자동으로 복구할 수 있습니다.

+

EKS를 사용하는 워커 노드에는 EC2 인스턴스와 [Fargate] (https://docs.aws.amazon.com/eks/latest/userguide/fargate.html)라는 두 가지 옵션이 있습니다. EC2 인스턴스를 선택하면 워커 노드를 직접 관리하거나 EKS 관리 노드 그룹을 사용할 수 있습니다. 관리형 워커 노드와 자체 관리형 워커 노드와 Fargate가 혼합된 클러스터를 구성할 수 있습니다.

+

Fargate의 EKS는 복원력이 뛰어난 데이터 플레인을 위한 가장 쉬운 방법을 제공합니다. Fargate는 격리된 컴퓨팅 환경에서 각 파드를 실행합니다. Fargate에서 실행되는 각 파드에는 자체 워커 노드가 있습니다. 쿠버네티스가 파드를 확장함에 따라 Fargate는 데이터 플레인을 자동으로 확장합니다. horizontal pod autoscaler를 사용하여 데이터 플레인과 워크로드를 모두 확장할 수 있습니다.

+

EC2 워커 노드를 확장하는 데 선호되는 방법은 쿠버네티스 Cluster Autoscaler, EC2 Auto Scaling 그룹 또는 Atlassian's Esclator와 같은 커뮤니티 프로젝트를 사용하는 것입니다.

+

권장 사항

+

EC2 오토 스케일링 그룹을 사용하여 워커 노드 생성

+

개별 EC2 인스턴스를 생성하여 클러스터에 조인하는 대신 EC2 오토 스케일링 그룹을 사용하여 워커 노드를 생성하는 것이 가장 좋습니다. 오토 스케일링 그룹은 종료되거나 장애가 발생한 노드를 자동으로 교체하므로 클러스터가 항상 워크로드를 실행할 수 있는 용량을 확보할 수 있습니다.

+

쿠버네티스 Cluster Autoscaler를 사용하여 노드를 확장하세요

+

Cluster Autoscaler는 클러스터의 리소스가 충분하지 않아 실행할 수 없는 파드가 있을 때 데이터 플레인 크기를 조정하며, 다른 워커 노드를 추가하여 도움을 줍니다. Cluster Autoscaler는 반응형 프로세스이긴 하지만 클러스터의 용량이 충분하지 않아 파드가 pending 상태가 될 때까지 기다립니다. 이러한 이벤트가 발생하면 클러스터에 EC2 인스턴스가 추가됩니다. 클러스터의 용량이 부족해지면 워커 노드가 추가될 때까지 새 복제본 또는 새 파드를 사용할 수 없게 됩니다(Pending 상태). 데이터 플레인이 워크로드 수요를 충족할 만큼 충분히 빠르게 확장되지 않는 경우 이러한 지연은 애플리케이션의 신뢰성에 영향을 미칠 수 있습니다. 워커 노드의 사용률이 지속적으로 낮고 해당 노드의 모든 파드를 다른 워커 노드에 스케줄링할 수 있는 경우 Cluster Autoscaler는 해당 워커 노드를 종료합니다.

+

Cluster Autoscaler를 사용하여 오버 프로비저닝을 구성합니다.

+

Cluster Autoscaler는 클러스터의 파드가 이미 pending 상태일 때 데이터 플레인 스케일링을 트리거합니다. 따라서 애플리케이션에 더 많은 복제본이 필요한 시점과 실제로 더 많은 복제본을 가져오는 시점 사이에 지연이 있을 수 있습니다. 이러한 지연을 방지할 수 있는 방법은 필요한 것보다 많은 복제본을 추가하여 애플리케이션의 복제본 수를 늘리는 것입니다.

+

Cluster Autoscaler에서 권장하는 또 다른 패턴은 pause 파드와 우선순위 선점 기능입니다. pause 파드pause 컨테이너를 실행하는데, 이름에서 알 수 있듯이 클러스터의 다른 파드에서 사용할 수 있는 컴퓨팅 용량의 placeholder 역할을 하는 것 외에는 아무것도 하지 않습니다. 매우 낮은 할당 우선 순위로 실행되기 때문에, 다른 파드를 생성해야 하고 클러스터에 가용 용량이 없을 때 일시 중지 파드가 노드에서 제거됩니다. 쿠버네티스 스케줄러는 pause 파드의 축출을 감지하고 스케줄을 다시 잡으려고 합니다. 하지만 클러스터가 최대 용량으로 실행되고 있기 때문에 일시 중지 파드는 pending 상태로 유지되며, Cluster Autoscaler는 이에 대응하여 노드를 추가합니다.

+

여러 오토 스케일링 그룹과 함께 Cluster Autoscaler 사용

+

--node-group-auto-discovery 플래그를 활성화한 상태로 Cluster Autoscaler를 실행합니다.이렇게 하면 Cluster Autoscaler가 정의된 특정 태그가 포함된 모든 오토스케일링 그룹을 찾을 수 있으므로 매니페스트에서 각 오토스케일링 그룹을 정의하고 유지할 필요가 없습니다.

+

로컬 스토리지와 함께 Cluster Autoscaler 사용

+

기본적으로 Cluster Autoscaler는 로컬 스토리지가 연결된 상태로 배포된 파드가 있는 노드를 축소하지 않습니다. --skip-nodes-with-local-storage 플래그를 false로 설정하면 Cluster Autoscaler가 이러한 노드를 축소할 수 있습니다.

+

워커 노드와 워크로드를 여러 AZ에 분산합니다.

+

여러 AZ에서 워커 노드와 파드를 실행하여 개별 AZ에서 장애가 발생하지 않도록 워크로드를 보호할 수 있습니다. 노드를 생성하는 서브넷을 사용하여 워커 노드가 생성되는 AZ를 제어할 수 있습니다.

+

쿠버네티스 1.18+를 사용하는 경우 AZ에 파드를 분산하는 데 권장되는 방법은 파드에 대한 토폴로지 분산 제약을 사용하는 것입니다.

+

아래 디플로이먼트는 가능한 경우 AZ에 파드를 분산시키고, 그렇지 않을 경우 해당 파드는 그냥 실행되도록 합니다.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: web-server
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: web-server
+  template:
+    metadata:
+      labels:
+        app: web-server
+    spec:
+      topologySpreadConstraints:
+        - maxSkew: 1
+          whenUnsatisfiable: ScheduleAnyway
+          topologyKey: topology.kubernetes.io/zone
+          labelSelector:
+            matchLabels:
+              app: web-server
+      containers:
+      - name: web-app
+        image: nginx
+        resources:
+          requests:
+            cpu: 1
+
+
+

Note

+

kube-scheduler는 해당 레이블이 있는 노드를 통한 토폴로지 도메인만 인식합니다. 위의 디플로이먼트를 단일 존에만 노드가 있는 클러스터에 배포하면, kube-scheduler가 다른 존을 인식하지 못하므로 모든 파드가 해당 노드에서 스케줄링됩니다. 이 Topology Spread가 스케줄러와 함께 예상대로 작동하려면 모든 존에 노드가 이미 있어야 합니다. 이 문제는 쿠버네티스 1.24에서 MinDomainsInPodToplogySpread 기능 게이트가 추가되면서 해결될 것입니다. 이 기능을 사용하면 스케줄러에 적격 도메인 수를 알리기 위해 MinDomains 속성을 지정할 수 있습니다.

+
+
+

Warning

+

whenUnsatisfiableDonot Schedule로 설정하면 Topology Spread Constraints을 충족할 수 없는 경우 파드를 스케줄링할 수 없게 됩니다. Topology Spread Constraints을 위반하는 대신 파드를 실행하지 않는 것이 더 좋은 경우에만 설정해야 합니다.

+
+

이전 버전의 쿠버네티스에서는 파드 anti-affinity 규칙을 사용하여 여러 AZ에 걸쳐 파드를 스케줄링할 수 있습니다. 아래 매니페스트는 쿠버네티스 스케줄러에게 별개의 AZ에서 파드를 스케줄링하는 것을 선호한다고 알려줍니다.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: web-server
+  labels:
+    app: web-server
+spec:
+  replicas: 4
+  selector:
+    matchLabels:
+      app: web-server
+  template:
+    metadata:
+      labels:
+        app: web-server
+    spec:
+      affinity:
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app
+                  operator: In
+                  values:
+                  - web-server
+              topologyKey: failure-domain.beta.kubernetes.io/zone
+            weight: 100
+      containers:
+      - name: web-app
+        image: nginx
+
+
+

Warning

+

파드를 서로 다른 AZ에 스케줄할 필요는 없습니다. 그렇지 않으면 디플로이먼트의 파드 수가 AZ 수를 절대 초과하지 않습니다.

+
+

EBS 볼륨을 사용할 때 각 AZ의 용량을 확보하십시오.

+

Amazon EBS를 사용하여 영구 볼륨 제공을 사용하는 경우 파드 및 관련 EBS 볼륨이 동일한 AZ에 있는지 확인해야 합니다. 이 글을 쓰는 시점에서 EBS 볼륨은 단일 AZ 내에서만 사용할 수 있습니다. 파드는 다른 AZ에 위치한 EBS 지원 영구 볼륨에 액세스할 수 없습니다. 쿠버네티스 스케줄러는 워커 노드가 어느 AZ에 위치하는지 알고 있습니다. 쿠버네티스는 해당 볼륨과 동일한 AZ에 EBS 볼륨이 필요한 파드를 항상 스케줄링합니다. 하지만 볼륨이 위치한 AZ에 사용 가능한 워커 노드가 없는 경우 파드를 스케줄링할 수 없습니다.

+

클러스터가 항상 필요한 EBS 볼륨과 동일한 AZ에 파드를 스케줄링할 수 있는 용량을 확보할 수 있도록 충분한 용량을 갖춘 각 AZ에 오토 스케일링 그룹을 생성하십시오. 또한 클러스터 오토스케일러에서 --balance-similar-similar-node groups 기능을 활성화해야 합니다.

+

EBS 볼륨을 사용하지만 가용성을 높이기 위한 요구 사항이 없는 애플리케이션을 실행 중인 경우 애플리케이션 배포를 단일 AZ로 제한할 수 있습니다. EKS에서는 워커 노드에 AZ 이름이 포함된 failure-domain.beta.kubernetes.io/zone 레이블이 자동으로 추가됩니다. kubectl get nodes --show-labels를 실행하여 노드에 첨부된 레이블을 확인할 수 있습니다. 빌트인 노드 레이블에 대한 자세한 내용은 [여기] (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#built-in-node-labels)에서 확인할 수 있습니다. 노드 셀렉터를 사용하여 특정 AZ에서 파드를 스케줄링할 수 있습니다.

+

아래 예시에서는 파드가 us-west-2c AZ에서만 스케줄링됩니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: single-az-pod
+spec:
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: failure-domain.beta.kubernetes.io/zone
+            operator: In
+            values:
+            - us-west-2c
+  containers:
+  - name: single-az-container
+    image: kubernetes/pause
+
+

퍼시스턴트 볼륨(EBS 지원) 역시 AZ 이름으로 자동 레이블이 지정됩니다. kubectl get pv -L topology.ebs.csi.aws.com/zone 명령을 실행해 퍼시스턴트 볼륨이 어느 AZ에 속하는지 확인할 수 있습니다. 파드가 생성되고 볼륨을 요청하면, 쿠버네티스는 해당 볼륨과 동일한 AZ에 있는 노드에 파드를 스케줄링합니다.

+

노드 그룹이 하나인 EKS 클러스터가 있는 시나리오를 생각해 보십시오. 이 노드 그룹에는 3개의 AZ에 분산된 세 개의 워커 노드가 있습니다. EBS 지원 퍼시스턴트 볼륨을 사용하는 애플리케이션이 있습니다. 이 애플리케이션과 해당 볼륨을 생성하면 해당 파드가 세 개의 AZ 중 첫 번째 AZ에 생성됩니다. 그러면 이 파드를 실행하는 워커 노드가 비정상 상태가 되고 이후에는 사용할 수 없게 된다. Cluster Autoscaler는 비정상 노드를 새 워커 노드로 교체합니다. 그러나 자동 확장 그룹이 세 개의 AZ에 걸쳐 있기 때문에 상황에 따라 새 워커 노드가 두 번째 또는 세 번째 AZ에서 시작될 수 있지만 첫 번째 AZ에서는 실행되지 않을 수 있습니다. AZ 제약이 있는 EBS 볼륨은 첫 번째 AZ에만 존재하고 해당 AZ에는 사용 가능한 워커 노드가 없으므로 파드를 스케줄링할 수 없습니다. 따라서 각 AZ에 노드 그룹을 하나씩 생성해야 합니다. 그래야 다른 AZ에서 스케줄링할 수 없는 파드를 실행할 수 있는 충분한 용량이 항상 확보됩니다.

+

또는 영구 스토리지가 필요한 애플리케이션을 실행할 때 [EFS] (https://github.com/kubernetes-sigs/aws-efs-csi-driver)를 사용하여 클러스터 자동 크기 조정을 단순화할 수 있습니다. 클라이언트는 리전 내 모든 AZ에서 동시에 EFS 파일 시스템에 액세스할 수 있습니다. EFS 기반 퍼시스턴트 볼륨을 사용하는 파드가 종료되어 다른 AZ에 스케줄되더라도 볼륨을 마운트할 수 있습니다.

+

노드 문제 감지기 실행

+

워커 노드의 장애는 애플리케이션의 가용성에 영향을 미칠 수 있습니다. node-problem-detector는 클러스터에 설치하여 워커 노드 문제를 탐지할 수 있는 쿠버네티스 애드온입니다. npd의 치료 시스템을 사용하여 노드를 자동으로 비우고 종료할 수 있습니다.

+

시스템 및 쿠버네티스 데몬을 위한 리소스 예약

+

운영 체제 및 쿠버네티스 데몬을 위한 컴퓨팅 용량를 예약하여 워커 노드의 안정성을 개선할 수 있습니다. 파드, 특히 limits이 선언되지 않은 파드는 시스템 리소스를 포화시켜 운영체제 프로세스와 쿠버네티스 데몬 (kubelet, 컨테이너 런타임 등)이 시스템 리소스를 놓고 파드와 경쟁하는 상황에 놓이게 됩니다.kubelet 플래그 --system-reserved--kube-reserved를 사용하여 시스템 프로세스 (udev, sshd 등)와 쿠버네티스 데몬을 위한 리소스를 각각 예약할 수 있습니다.

+

EKS에 최적화된 Linux AMI를 사용하는 경우 CPU, 메모리 및 스토리지는 기본적으로 시스템 및 쿠버네티스 데몬용으로 예약됩니다. 이 AMI를 기반으로 하는 워커 노드가 시작되면 bootstrap.sh 스크립트 를 트리거하도록 EC2 사용자 데이터가 구성됩니다. 이 스크립트는 EC2 인스턴스에서 사용할 수 있는 CPU 코어 수와 총 메모리를 기반으로 CPU 및 메모리 예약을 계산합니다. 계산된 값은 /etc/kubernetes/kubelet/kubelet-config.json에 있는 KubeletConfiguration 파일에 기록됩니다.

+

노드에서 사용자 지정 데몬을 실행하고 기본적으로 예약된 CPU 및 메모리 양이 충분하지 않은 경우 시스템 리소스 예약을 늘려야 할 수 있습니다.

+

eksctl시스템 및 쿠버네티스 데몬의 리소스 예약을 사용자 지정하는 가장 쉬운 방법을 제공합니다.

+

QoS 구현

+

중요한 애플리케이션의 경우, 파드의 컨테이너에 대해 requests=limits 정의를 고려해보십시오. 이렇게 하면 다른 파드가 리소스를 요청하더라도 컨테이너가 종료되지 않습니다.

+

모든 컨테이너에 CPU 및 메모리 제한을 적용하는 것이 가장 좋습니다. 이렇게 하면 컨테이너가 실수로 시스템 리소스를 소비하여 같은 위치에 배치된 다른 프로세스의 가용성에 영향을 미치는 것을 방지할 수 있기 때문입니다.

+

모든 워크로드에 대한 리소스 요청/제한 구성 및 크기 조정

+

리소스 요청의 크기 조정 및 워크로드 한도에 대한 몇 가지 일반적인 지침을 적용할 수 있습니다.

+
    +
  • +

    CPU에 리소스 제한을 지정하지 마십시오. 제한이 없는 경우 요청은 컨테이너의 상대적 CPU 사용 시간에 가중치 역할을 합니다. 이렇게 하면 인위적인 제한이나 과다 현상 없이 워크로드에서 CPU 전체를 사용할 수 있습니다.

    +
  • +
  • +

    CPU가 아닌 리소스의 경우, requests=limits를 구성하면 가장 예측 가능한 동작이 제공됩니다. 만약 requests!=limits이면, 컨테이너의 QOS도 Guaranteed에서 Burstable로 감소하여 node pressure 이벤트에 축출될 가능성이 높아졌습니다.

    +
  • +
  • +

    CPU가 아닌 리소스의 경우 요청보다 훨씬 큰 제한을 지정하지 마십시오. limitsrequests에 비해 크게 구성될수록 노드가 오버 커밋되어 워크로드가 중단될 가능성이 높아집니다.

    +
  • +
  • +

    Karpenter 또는 Cluster AutoScaler와 같은 노드 자동 크기 조정 솔루션을 사용할 때는 요청 크기를 올바르게 지정하는 것이 특히 중요합니다. 이러한 도구는 워크로드 요청을 검토하여 프로비저닝할 노드의 수와 크기를 결정합니다. 요청이 너무 작아 제한이 더 큰 경우, 워크로드가 노드에 꽉 차 있으면 워크로드가 제거되거나 OOM으로 종료될 수 있습니다.

    +
  • +
+

리소스 요청을 결정하는 것은 어려울 수 있지만 Vertical Pod Autoscaler와 같은 도구를 사용하면 런타임 시 컨테이너 리소스 사용량을 관찰하여 요청 규모를 '적정'하게 조정할 수 있습니다. 요청 크기를 결정하는 데 유용할 수 있는 다른 도구는 다음과 같습니다.

+ +

네임스페이스의 리소스 할당량 구성

+

네임스페이스는 사용자가 여러 팀 또는 프로젝트에 분산되어 있는 환경에서 사용하기 위한 것입니다. 이름 범위를 제공하고 클러스터 리소스를 여러 팀, 프로젝트, 워크로드 간에 나누는 방법입니다. 네임스페이스의 총 리소스 사용량을 제한할 수 있습니다. ResourceQuota 객체는 유형별로 네임스페이스에 만들 수 있는 개체 수와 해당 프로젝트의 리소스가 소비할 수 있는 총 컴퓨팅 리소스 양을 제한할 수 있습니다. 지정된 네임스페이스에서 요청할 수 있는 스토리지 및/또는 컴퓨팅(CPU 및 메모리) 리소스의 총합을 제한할 수 있습니다.

+
+

CPU 및 메모리와 같은 컴퓨팅 리소스의 네임스페이스에 리소스 쿼터가 활성화된 경우 사용자는 해당 네임스페이스의 각 컨테이너에 대한 요청 또는 제한을 지정해야 합니다.

+
+

각 네임스페이스에 할당량을 구성하는 것을 고려해 보십시오. 네임스페이스 내의 컨테이너에 사전 구성된 제한을 자동으로 적용하려면 LimitRanges를 사용해 보십시오.

+

네임스페이스 내에서 컨테이너 리소스 사용을 제한합니다.

+

리소스 쿼터는 네임스페이스가 사용할 수 있는 리소스의 양을 제한하는 데 도움이 됩니다. LimitRange 개체는 컨테이너가 요청할 수 있는 최소 및 최대 리소스를 구현하는 데 도움이 될 수 있습니다. LimitRange를 사용하면 컨테이너에 대한 기본 요청 및 제한을 설정할 수 있는데, 이는 컴퓨팅 리소스 제한을 설정하는 것이 조직의 표준 관행이 아닌 경우에 유용합니다. 이름에서 알 수 있듯이, LimitRange는 네임스페이스의 파드 또는 컨테이너당 최소 및 최대 컴퓨팅 리소스 사용량을 적용할 수 있습니다. 또한 네임스페이스에서 퍼시스턴트볼륨클레임당 최소 및 최대 스토리지 요청을 적용할 수 있습니다.

+

컨테이너와 네임스페이스 수준에서 제한을 적용하려면 LimitRangeResourceQuota를 함께 사용하는 것이 좋습니다. 이러한 제한을 설정하면 컨테이너 또는 네임스페이스가 클러스터의 다른 테넌트가 사용하는 리소스에 영향을 주지 않도록 할 수 있습니다.

+

CoreDNS

+

CoreDNS는 쿠버네티스에서 이름 확인 및 서비스 검색 기능을 수행합니다. EKS 클러스터에 기본적으로 설치됩니다. 상호 운용성을 위해 CoreDNS용 쿠버네티스 서비스의 이름은 여전히 kube-dns로 지정됩니다. CoreDNS 파드는 디플로이먼트의 일부로 kube-system 네임스페이스에서 실행되며, EKS에서는 기본적으로 요청과 제한이 선언된 두 개의 복제본을 실행합니다. DNS 쿼리는 kube-system 네임스페이스에서 실행되는 kube-dns 서비스로 전송됩니다.

+

권장 사항

+

핵심 DNS 지표 모니터링

+

CoreDNS는 프로메테우스에 대한 지원을 내장하고 있습니다. 특히 CoreDNS 지연 시간(coredns_dns_request_duration_seconds_sum, 1.7.0 버전 이전에는 메트릭이 core_dns_response_rcode_count_total이라고 불렸음), 오류 (coredns_dns_responses_total, NXDOMAIN, SERVFAIL, FormErr) 및 CoreDNS 파드의 메모리 사용량에 대한 모니터링을 고려해야 합니다.

+

문제 해결을 위해 kubectl을 사용하여 CoreDNS 로그를 볼 수 있습니다.

+
for p in $(kubectl get pods —namespace=kube-system -l k8s-app=kube-dns -o name); do kubectl logs —namespace=kube-system $p; done
+
+

노드 로컬 DNS 캐시 사용

+

노드 로컬 DNS 캐시를 실행하여 클러스터 DNS 성능을 개선할 수 있습니다. 이 기능은 클러스터 노드에서 DNS 캐싱 에이전트를 데몬셋으로 실행합니다. 모든 파드는 이름 확인을 위해 kube-dns 서비스를 사용하는 대신 노드에서 실행되는 DNS 캐싱 에이전트를 사용합니다.

+

CoreDNS의 cluster-proportional-scaler를 구성합니다.

+

클러스터 DNS 성능을 개선하는 또 다른 방법은 클러스터의 노드 및 CPU 코어 수에 따라 CoreDNS 배포를 자동으로 수평으로 확장하는 것입니다. 수평 클러스터 비례 자동 확장은 스케줄 가능한 데이터 플레인의 크기에 따라 디플로이먼트의 복제본 수를 조정하는 컨테이너입니다.

+

노드와 노드의 CPU 코어 집계는 CoreDNS를 확장할 수 있는 두 가지 지표입니다. 두 지표를 동시에 사용할 수 있습니다. 더 큰 노드를 사용하는 경우 CoreDNS 스케일링은 CPU 코어 수를 기반으로 합니다. 반면 더 작은 노드를 사용하는 경우 CoreDNS 복제본의 수는 데이터 플레인의 CPU 코어에 따라 달라집니다. 비례 오토스케일러 구성은 다음과 같습니다.

+
linear: '{"coresPerReplica":256,"min":1,"nodesPerReplica":16}'
+
+

노드 그룹이 있는 AMI 선택

+

EKS는 고객이 자체 관리형 노드 그룹과 관리형 노드 그룹을 모두 생성하는 데 사용하는 최적화된 EC2 AMI를 제공합니다. 이러한 AMI는 지원되는 모든 쿠버네티스 버전에 대해 모든 리전에 게시됩니다. EKS는 CVE 또는 버그가 발견되면 이러한 AMI를 더 이상 사용되지 않는 것으로 표시합니다. 따라서 노드 그룹에 사용할 AMI를 선택할 때는 더 이상 사용되지 않는 AMI를 사용하지 않는 것이 좋습니다.

+

Ec2 describe-images api를 사용하여 아래 명령을 사용하여 더 이상 사용되지 않는 AMI를 필터링할 수 있습니다.

+
aws ec2 describe-images --image-id ami-0d551c4f633e7679c --no-include-deprecated
+
+

이미지 설명 출력에 DeprecationTime이 필드로 포함되어 있는지 확인하여 지원 중단된 AMI를 식별할 수도 있습니다. 예를 들면:

+
aws ec2 describe-images --image-id ami-xxx --no-include-deprecated
+{
+    "Images": [
+        {
+            "Architecture": "x86_64",
+            "CreationDate": "2022-07-13T15:54:06.000Z",
+            "ImageId": "ami-xxx",
+            "ImageLocation": "123456789012/eks_xxx",
+            "ImageType": "machine",
+            "Public": false,
+            "OwnerId": "123456789012",
+            "PlatformDetails": "Linux/UNIX",
+            "UsageOperation": "RunInstances",
+            "State": "available",
+            "BlockDeviceMappings": [
+                {
+                    "DeviceName": "/dev/xvda",
+                    "Ebs": {
+                        "DeleteOnTermination": true,
+                        "SnapshotId": "snap-0993a2fc4bbf4f7f4",
+                        "VolumeSize": 20,
+                        "VolumeType": "gp2",
+                        "Encrypted": false
+                    }
+                }
+            ],
+            "Description": "EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.19.15, docker: 20.10.13-2.amzn2, containerd: 1.4.13-3.amzn2)",
+            "EnaSupport": true,
+            "Hypervisor": "xen",
+            "Name": "aws_eks_optimized_xxx",
+            "RootDeviceName": "/dev/xvda",
+            "RootDeviceType": "ebs",
+            "SriovNetSupport": "simple",
+            "VirtualizationType": "hvm",
+            "DeprecationTime": "2023-02-09T19:41:00.000Z"
+        }
+    ]
+}
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/reliability/docs/index.html b/ko/reliability/docs/index.html new file mode 100644 index 000000000..175a8c0c0 --- /dev/null +++ b/ko/reliability/docs/index.html @@ -0,0 +1,2184 @@ + + + + + + + + + + + + + + + + + + + + + + + 홈 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

신뢰성을 위한 Amazon EKS 모범 사례 가이드

+

이 섹션에서는 EKS에서 실행되는 워크로드의 복원력과 가용성을 높이는 방법에 대한 지침을 제공합니다.

+

이 가이드를 사용하는 방법

+

이 안내서는 EKS에서 가용성이 높고 내결함성이 있는 서비스를 개발하고 운영하려는 개발자와 설계자를 대상으로 합니다. 이 가이드는 보다 쉽게 사용할 수 있도록 다양한 주제 영역으로 구성되어 있습니다. 각 항목은 간략한 개요로 시작하여 EKS 클러스터의 신뢰성을 위한 권장 사항 및 모범 사례 목록이 이어집니다.

+

소개

+

EKS의 신뢰성 모범 사례는 다음 주제에 따라 그룹화되었습니다.

+
    +
  • 애플리케이션
  • +
  • 컨트롤 플레인
  • +
  • 데이터 플레인
  • +
+
+

무엇이 시스템을 신뢰할 수 있게 만드나요? 일정 기간의 환경 변화에도 불구하고 시스템이 일관되게 작동하고 요구 사항을 충족할 수 있다면 신뢰할 수 있다고 할 수 있습니다. 이를 위해서는 시스템이 장애를 감지하고 자동으로 복구하며 수요에 따라 확장할 수 있어야 합니다.

+

고객은 Kubernetes를 기반으로 사용하여 업무상 중요한 애플리케이션 및 서비스를 안정적으로 운영할 수 있습니다. 그러나 컨테이너 기반 애플리케이션 설계 원칙을 통합하는 것 외에도 워크로드를 안정적으로 실행하려면 신뢰할 수 있는 인프라가 필요합니다. 쿠버네티스에서 인프라는 컨트롤 플레인과 데이터 플레인으로 구성됩니다.

+

EKS는 가용성과 내결함성을 제공하도록 설계된 프로덕션 등급의 Kubernetes 컨트롤 플레인을 제공합니다.

+

EKS에서 AWS는 쿠버네티스 컨트롤 플레인의 신뢰성을 책임집니다. EKS는 AWS 리전의 세 가용 영역에서 쿠버네티스 컨트롤 플레인을 실행합니다. 쿠버네티스 API 서버 및 etcd 클러스터의 가용성과 확장성을 자동으로 관리합니다.

+

데이터 플레인의 신뢰성에 대한 책임은 사용자, 고객, AWS 간에 공유됩니다. EKS는 Kubernetes 데이터 플레인에 대한 세 가지 옵션을 제공합니다. 가장 많이 관리되는 옵션인 Fargate는 데이터 플레인의 프로비저닝 및 확장을 처리합니다. 두 번째 옵션인 관리형 노드 그룹화는 데이터 플레인의 프로비저닝 및 업데이트를 처리합니다. 마지막으로, 자체 관리형 노드는 데이터 플레인에 대한 관리가 가장 적은 옵션입니다. AWS 관리형 데이터 플레인을 더 많이 사용할수록 책임은 줄어듭니다.

+

관리형 노드 그룹은 EC2 노드의 프로비저닝 및 수명 주기 관리를 자동화합니다. EKS API (EKS 콘솔, AWS API, AWS CLI, CloudFormation, Terraform 또는 eksctl 사용)를 사용하여 관리형 노드를 생성, 확장 및 업그레이드할 수 있습니다. 관리형 노드는 계정에서 EKS에 최적화된 Amazon Linux 2 EC2 인스턴스를 실행하며, SSH 액세스를 활성화하여 사용자 지정 소프트웨어 패키지를 설치할 수 있습니다. 관리형 노드를 프로비저닝하면 여러 가용 영역에 걸쳐 있을 수 있는 EKS 관리형 Auto Scaling 그룹의 일부로 실행되므로 관리형 노드를 생성할 때 제공하는 서브넷을 통해 이를 제어할 수 있습니다. 또한 EKS는 관리형 노드에 자동으로 태그를 지정하여 클러스터 오토스케일러에서 사용할 수 있도록 합니다.

+
+

Amazon EKS는 관리형 노드 그룹의 CVE 및 보안 패치에 대한 공동 책임 모델을 따릅니다. 관리형 노드는 Amazon EKS에 최적화된 AMI들을 실행하므로 Amazon EKS는 버그 수정 시 이러한 AMI들의 패치 버전을 만들 책임이 있습니다. 하지만 이러한 패치가 적용된 AMI 버전을 관리형 노드 그룹에 배포하는 것은 사용자의 책임입니다.

+
+

EKS는 업데이트 프로세스를 시작해야 하지만 노드 업데이트도 관리합니다. 관리형 노드 업데이트 프로세스는 EKS 설명서에 설명되어 있습니다.

+

자체 관리형 노드를 실행하는 경우 Amazon EKS에 최적화된 Linux AMI를 사용하여 워커 노드를 생성할 수 있습니다. AMI와 노드의 패치 및 업그레이드는 사용자가 담당합니다. eksctl, CloudFormation 또는 코드형 인프라 도구를 사용하여 자체 관리형 노드를 프로비저닝하는 것이 가장 좋습니다. 이렇게 하면 자체 관리형 노드 업그레이드를 쉽게 할 수 있기 때문입니다. 마이그레이션 프로세스에서는 이전 노드 그룹을 NoScheduletaints하고 새 스택이 기존 파드 워크로드를 수용할 준비가 되면 노드를 drains하기 때문에 워커 노드를 업데이트할 때 새 노드로 마이그레이션하는 것을 고려해 보십시오. 하지만 자체 관리형 노드의 in-place 업그레이드를 수행할 수도 있습니다.

+

공동 책임 모델 - Fargate

+

공동 책임 모델 - MNG

+

이 가이드에는 EKS 데이터 플레인, Kubernetes 핵심 구성 요소 및 애플리케이션의 신뢰성을 개선하는 데 사용할 수 있는 일련의 권장 사항이 포함되어 있습니다.

+

피드백

+

이 가이드는 광범위한 EKS/Kubernetes 커뮤니티로부터 직접적인 피드백과 제안을 수집하기 위해 GitHub에 게시 되었습니다. 가이드에 포함시켜야 한다고 생각되는 모범 사례가 있다면 GitHub 리포지토리에 문제를 제출하거나 PR을 제출해 주세요. 서비스에 새로운 기능이 추가되거나 새로운 모범 사례가 개발되면 가이드를 정기적으로 업데이트할 계획입니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/cluster-services/index.html b/ko/scalability/docs/cluster-services/index.html new file mode 100644 index 000000000..6ff90653e --- /dev/null +++ b/ko/scalability/docs/cluster-services/index.html @@ -0,0 +1,2297 @@ + + + + + + + + + + + + + + + + + + + + + + + 클러스터 서비스 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

클러스터 서비스

+

클러스터 서비스는 EKS 클러스터 내에서 실행되지만 사용자 워크로드는 아닙니다. 리눅스 서버를 사용하는 경우 워크로드를 지원하기 위해 NTP, syslog 및 컨테이너 런타임과 같은 서비스를 실행해야 하는 경우가 많습니다. 클러스터 서비스도 비슷하며 클러스터를 자동화하고 운영하는 데 도움이 되는 서비스를 지원합니다. 쿠버네티스에서 이들은 일반적으로 kube-system 네임스페이스에서 실행되고 일부는 데몬셋로 실행됩니다.

+

클러스터 서비스는 가동 시간이 길어질 것으로 예상되며 정전 및 문제 해결에 중요한 역할을 하는 경우가 많습니다. 코어 클러스터 서비스를 사용할 수 없는 경우 장애 복구 또는 예방에 도움이 되는 데이터 (예: 높은 디스크 사용률)에 액세스할 수 없게 될 수 있습니다. 별도의 노드 그룹 또는 AWS Fargate와 같은 전용 컴퓨팅 인스턴스에서 실행해야 합니다. 이렇게 하면 규모가 커지거나 리소스를 더 많이 사용하는 워크로드가 공유 인스턴스에 미치는 영향을 클러스터 서비스가 받지 않도록 할 수 있습니다.

+

CoreDNS 스케일링

+

CoreDNS 스케일링에는 두 가지 기본 메커니즘이 있습니다. CoreDNS 서비스에 대한 호출 수를 줄이고 복제본 수를 늘립니다.

+

ndot을 줄여 외부 쿼리를 줄입니다.

+

ndots 설정은 DNS 쿼리를 피하기에 충분하다고 간주되는 도메인 이름의 마침표 (일명 "점") 수를 지정합니다. 애플리케이션의 ndots 설정이 5 (기본값) 이고 api.example.com (점 2개) 과 같은 외부 도메인에서 리소스를 요청하는 경우 /etc/resolv.conf에 정의된 각 검색 도메인에 대해 CoreDNS가 쿼리되어 더 구체적인 도메인이 검색됩니다. 기본적으로 외부 요청을 하기 전에 다음 도메인이 검색됩니다.

+
api.example.<namespace>.svc.cluster.local
+api.example.svc.cluster.local
+api.example.cluster.local
+api.example.<region>.compute.internal
+
+

namespaceregion 값은 워크로드 네임스페이스 및 컴퓨팅 지역으로 대체됩니다. 클러스터 설정에 따라 추가 검색 도메인이 있을 수 있습니다.

+

워크로드의 ndots 옵션 낮추기 또는 후행 항목을 포함하여 도메인 요청을 완전히 검증하여 CoreDNS에 대한 요청 수를 줄일 수 있습니다.(예: api.example.com.). 워크로드가 DNS를 통해 외부 서비스에 연결하는 경우 워크로드가 불필요하게 클러스터 내에서 DNS 쿼리를 클러스터링하지 않도록 ndots를 2로 설정하는 것이 좋습니다. 워크로드에 클러스터 내부 서비스에 대한 액세스가 필요하지 않은 경우 다른 DNS 서버 및 검색 도메인을 설정할 수 있습니다.

+
spec:
+  dnsPolicy: "None"
+  dnsConfig:
+    options:
+      - name: ndots
+        value: "2"
+      - name: edns0
+
+

ndots를 너무 낮은 값으로 낮추거나 연결하려는 도메인의 구체성이 충분하지 않은 경우 (후행 포함) DNS 조회가 실패할 수 있습니다.이 설정이 워크로드에 어떤 영향을 미칠지 테스트해야 합니다.

+

CoreDNS 수평 스케일링

+

CoreDNS 인스턴스는 배포에 복제본을 추가하여 확장할 수 있습니다. CoreDNS를 확장하려면 NodeLocal DNS 또는 cluster proportional autoscaler를 사용하는 것이 좋습니다.

+

NodeLocal DNS는 노드당 하나의 인스턴스를 데몬셋으로 실행해야 하며, 이를 위해서는 클러스터에 더 많은 컴퓨팅 리소스가 필요하지만 DNS 요청 실패를 방지하고 클러스터의 DNS 쿼리에 대한 응답 시간을 줄입니다. Cluster propertional autoscaler는 클러스터의 노드 또는 코어 수에 따라 CoreDNS의 크기를 조정합니다. 이는 쿼리 요청과 직접적인 상관 관계는 아니지만 워크로드 및 클러스터 크기에 따라 유용할 수 있습니다. 기본 비례 척도는 클러스터의 256개 코어 또는 16개 노드마다 추가 복제본을 추가하는 것입니다(둘 중 먼저 발생하는 기준).

+

쿠버네티스 Metric Server 수직 확장

+

쿠버네티스 Metric Server는 수평 및 수직 확장을 지원합니다. Metric Server를 수평적으로 확장하면 가용성은 높아지지만 더 많은 클러스터 메트릭을 처리할 수 있을 만큼 수평적으로 확장되지는 않습니다. 노드와 수집된 지표가 클러스터에 추가됨에 따라 권장 사항에 따라 메트릭 서버를 수직으로 확장해야 합니다.

+

Metric Server는 수집, 집계 및 제공하는 데이터를 메모리에 보관합니다. 클러스터가 커지면 Metric Server가 저장하는 데이터 양도 늘어납니다. 대규모 클러스터에서 Metric Server는 기본 설치에 지정된 메모리 및 CPU 예약량보다 더 많은 컴퓨팅 리소스를 필요로 합니다.Vertical Pod Autoscaler(VPA) 또는 Addon Resizer를 사용하여 Metric Server를 확장할 수 있습니다. Addon Resizer는 Worker 노드에 비례하여 수직으로 확장되고 VPA는 CPU 및 메모리 사용량에 따라 조정됩니다.

+

CoreDNS lameduck 지속 시간

+

파드는 이름 확인을 위해 kube-dns 서비스를 사용합니다. 쿠버네티스는 Destination NAT (DNAT) 를 사용하여 노드에서 CoreDNS 백엔드 파드로 kube-dns 트래픽을 리디렉션합니다. CoreDNS Deployment를 확장하면, kube-proxy는 노드의 iptables 규칙 및 체인을 업데이트하여 DNS 트래픽을 CoreDNS 파드로 리디렉션합니다. 확장 시 새 엔드포인트를 전파하고 축소할 때 규칙을 삭제하는데 클러스터 크기에 따라 CoreDNS를 삭제하는 데 1~10초 정도 걸릴 수 있습니다.

+

이러한 전파 지연으로 인해 CoreDNS 파드가 종료되었지만 노드의 iptables 규칙이 업데이트되지 않은 경우 DNS 조회 오류가 발생할 수 있습니다. 이 시나리오에서 노드는 종료된 CoreDNS 파드에 DNS 쿼리를 계속 전송할 수 있다.

+

CoreDNS 파드에 lameduck 기간을 설정하여 DNS 조회 실패를 줄일 수 있습니다. Lameduck 모드에 있는 동안 CoreDNS는 계속해서 진행 중인 요청에 응답합니다.Lameduck 기간을 설정하면 CoreDNS 종료 프로세스가 지연되어 노드가 iptables 규칙 및 체인을 업데이트하는 데 필요한 시간을 확보할 수 있습니다.

+

CoreDNS lameduck 지속 시간을 30초로 설정하는 것이 좋습니다.

+

CoreDNS readiness 프로브

+

CoreDNS의 Readiness 프로브에는 /health 대신 /ready를 사용하는 것을 추천합니다.

+

Lameduck 지속 시간을 30초로 설정하라는 이전 권장 사항에 따라, 파드 종료 전에 노드의 iptables 규칙을 업데이트할 수 있는 충분한 시간을 제공합니다. CoreDNS 준비 상태 프로브에 /health 대신 `/ready'를 사용하면 시작 시 CoreDNS 파드가 DNS 요청에 즉시 응답할 수 있도록 완벽하게 준비됩니다.

+
readinessProbe:
+  httpGet:
+    path: /ready
+    port: 8181
+    scheme: HTTP
+
+

CoreDNS Ready 플러그인에 대한 자세한 내용은 https://coredns.io/plugins/ready/ 을 참조하십시오.

+

로깅 및 모니터링 에이전트

+

로깅 및 모니터링 에이전트는 API 서버를 쿼리하여 워크로드 메타데이터로 로그와 메트릭을 보강하므로 클러스터 컨트롤 플레인에 상당한 로드를 추가할 수 있습니다. 노드의 에이전트는 컨테이너 및 프로세스 이름과 같은 항목을 보기 위해 로컬 노드 리소스에만 액세스할 수 있습니다. API 서버를 쿼리하면 Kubernetes Deployment 이름 및 레이블과 같은 세부 정보를 추가할 수 있습니다. 이는 문제 해결에는 매우 유용하지만 확장에는 해로울 수 있습니다.

+

로깅 및 모니터링에 대한 옵션이 너무 다양하기 때문에 모든 공급자에 대한 예를 표시할 수는 없습니다. fluentbit를 사용하면 Use_Kubelet을 활성화하여 Kubernetes API 서버 대신 로컬 kubelet에서 메타데이터를 가져오고 Kube_Meta_Cache_TTL을 줄이는 숫자로 설정하는 것이 좋습니다. 데이터를 캐시할 수 있을 때 호출을 반복합니다(예: 60).

+

조정 모니터링 및 로깅에는 두 가지 일반 옵션이 있습니다.

+
    +
  • 통합 비활성화
  • +
  • 샘플링 및 필터링
  • +
+

로그 메타데이터가 손실되므로 통합을 비활성화하는 것이 옵션이 아닌 경우가 많습니다. 이렇게 하면 API 확장 문제가 제거되지만 필요할 때 필요한 메타데이터가 없어 다른 문제가 발생합니다.

+

샘플링 및 필터링을 수행하면 수집되는 지표 및 로그 수가 줄어듭니다. 이렇게 하면 Kubernetes API에 대한 요청 양이 줄어들고 수집되는 지표 및 로그에 필요한 스토리지 양이 줄어듭니다. 스토리지 비용을 줄이면 전체 시스템 비용도 낮아집니다.

+

샘플링을 구성하는 기능은 에이전트 소프트웨어에 따라 다르며 다양한 수집 지점에서 구현될 수 있습니다. API 서버 호출이 발생할 가능성이 높기 때문에 에이전트에 최대한 가깝게 샘플링을 추가하는 것이 중요합니다. 샘플링 지원에 대해 자세히 알아보려면 공급자에게 문의하세요.

+

CloudWatch 및 CloudWatch Logs를 사용하는 경우 문서에 설명된 패턴을 사용하여 에이전트 필터링을 추가할 수 있습니다.

+

로그 및 지표 손실을 방지하려면 수신 받는 엔드포인트에서 중단이 발생할 경우 데이터를 버퍼링할 수 있는 시스템으로 데이터를 보내야 합니다. Fluentbit를 사용하면 Amazon Kinesis Data Firehose를 사용하여 데이터를 임시로 보관할 수 있으므로 최종 데이터 저장 위치에 과부하가 걸릴 가능성이 줄어듭니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/control-plane/index.html b/ko/scalability/docs/control-plane/index.html new file mode 100644 index 000000000..9bdc5a5ba --- /dev/null +++ b/ko/scalability/docs/control-plane/index.html @@ -0,0 +1,2684 @@ + + + + + + + + + + + + + + + + + + + + + + + 컨트롤 플레인 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

쿠버네티스 컨트롤 플레인

+

쿠버네티스 컨트롤 플레인은 쿠버네티스 API Server, 쿠버네티스 Controller Manager, Scheduler 및 쿠버네티스가 작동하는 데 필요한 기타 구성 요소로 구성됩니다. 이러한 구성 요소의 확장성 제한은 클러스터에서 실행 중인 항목에 따라 다르지만 확장에 가장 큰 영향을 미치는 영역에는 쿠버네티스 버전, 사용률 및 개별 노드 확장이 포함됩니다.

+

EKS 1.24 이상을 사용하세요

+

EKS 1.24에는 여러 가지 변경 사항이 도입되었으며 컨테이너 런타임을 docker 대신 containerd로 전환했습니다. Containerd는 쿠버네티스의 요구 사항에 긴밀하게 맞춰 컨테이너 런타임 기능을 제한하여 개별 노드 성능을 높여 클러스터 확장을 돕습니다. Containerd는 지원되는 모든 EKS 버전에서 사용할 수 있으며, 1.24 이전 버전에서 Containerd로 전환하려면 --container-runtime bootstrap flag를 사용하세요.

+

워크로드 및 노드 버스팅 제한

+
+

Attention

+

컨트롤 플레인에서 API 한도에 도달하지 않으려면 클러스터 크기를 한 번에 두 자릿수 비율로 늘리는 급격한 확장을 제한해야 합니다(예: 한 번에 1000개 노드에서 1100개 노드로 또는 4000개에서 4500개 파드로).

+
+

EKS 컨트롤 플레인은 클러스터가 성장함에 따라 자동으로 확장되지만 확장 속도에는 제한이 있습니다. EKS 클러스터를 처음 생성할 때 컨트롤 플레인은 즉시 수백 개의 노드 또는 수천 개의 파드로 확장될 수 없습니다. EKS의 스케일링 개선 방법에 대해 자세히 알아보려면 이 블로그 게시물을 참조하세요.

+

대규모 애플리케이션을 확장하려면 인프라가 완벽하게 준비되도록 조정해야 합니다(예: 로드 밸런서 워밍). 확장 속도를 제어하려면 애플리케이션에 적합한 측정 지표를 기반으로 확장하고 있는지 확인합니다. CPU 및 메모리 확장은 애플리케이션 제약 조건을 정확하게 예측하지 못할 수 있으며 쿠버네티스 HPA(Horizontal Pod Autoscaler)에서 사용자 지정 지표(예: 초당 요청)를 사용하는 것이 더 나은 확장 옵션일 수 있습니다.

+

사용자 정의 지표를 사용하려면 쿠버네티스 문서의 예를 참조하세요. 고급 확장이 필요하거나 외부 소스(예: AWS SQS 대기열)를 기반으로 확장해야 하는 경우 이벤트 기반 워크로드 확장을 위해 KEDA를 사용하세요.

+

노드와 파드를 안전하게 축소

+

장기 실행 인스턴스 교체

+

정기적으로 노드를 교체하면 구성 드리프트와 가동 시간이 연장된 후에만 발생하는 문제(예: 느린 메모리 누수)를 방지하여 클러스터를 건강한 상태로 유지할 수 있습니다. 자동 교체는 노드 업그레이드 및 보안 패치에 대한 좋은 프로세스와 사례를 제공합니다. 클러스터의 모든 노드가 정기적으로 교체되면 지속적인 유지 관리를 위해 별도의 프로세스를 유지하는 데 필요한 노력이 줄어듭니다.

+

Karpenter의 Time To Live (TTL) 설정을 통해 지정된 시간 동안 인스턴스가 실행된 후 인스턴스를 교체할 수 있습니다. 자체 관리형 노드 그룹은 max-instance-lifetime 설정을 사용하여 노드를 자동으로 교체할 수 있습니다. 관리형 노드 그룹에는 현재 이 기능이 없지만 여기 GitHub에서 요청을 확인할 수 있습니다.

+

활용도가 낮은 노드 제거

+

--scale-down-utilization-threshold를 통해 쿠버네티스 Cluster Autoscaler의 축소 임계값을 사용하여 실행 중인 워크로드가 없을 때 노드를 제거할 수 있습니다. 또는 Karpenter에서 ttlSecondsAfterEmpty 프로비저너 설정을 활용할 수 있습니다.

+

Pod Distruption Budgets 및 안전한 노드 셧다운 사용

+

쿠버네티스 클러스터에서 파드와 노드를 제거하려면 컨트롤러가 여러 리소스(예: EndpointSlices)를 업데이트해야 합니다. 이 작업을 자주 또는 너무 빠르게 수행하면 변경 사항이 컨트롤러에 전파되면서 API Server 쓰로틀링 및 애플리케이션 중단이 발생할 수 있습니다. Pod Distruption Budgets은 클러스터에서 노드가 제거되거나 스케줄이 조정될 때 변동 속도를 늦추어 워크로드 가용성을 보호하는 모범 사례입니다.

+

Kubectl 실행 시 클라이언트측 캐시 사용

+

kubectl 명령을 비효율적으로 사용하면 쿠버네티스 API Server에 추가 로드가 발생될 수 있습니다. kubectl을 반복적으로(예: for 루프에서) 사용하는 스크립트나 자동화를 실행하거나 로컬 캐시 없이 명령을 실행하는 것을 피해야 합니다.

+

kubectl에는 필요한 API 호출 양을 줄이기 위해 클러스터에서 검색 정보를 캐시하는 클라이언트 측 캐시가 있습니다. 캐시는 기본적으로 활성화되어 있으며 10분마다 새로 고쳐집니다.

+

컨테이너에서 또는 클라이언트 측 캐시 없이 kubectl을 실행하는 경우 API 쓰로틀링 문제가 발생할 수 있습니다. 불필요한 API 호출을 피하기 위해 --cache-dir을 마운트하여 클러스터 캐시를 유지하는 것이 좋습니다.

+

kubectl Compression 비활성화

+

kubeconfig 파일에서 kubectl compression을 비활성화하면 API 및 클라이언트 CPU 사용량을 줄일 수 있습니다. 기본적으로 서버는 클라이언트로 전송된 데이터를 압축하여 네트워크 대역폭을 최적화합니다. 이렇게 하면 요청마다 클라이언트와 서버에 CPU 부하가 가중되며, 대역폭이 충분하다면 압축을 비활성화하면 오버헤드와 지연 시간을 줄일 수 있습니다. 압축을 비활성화하려면 kubeconfig 파일에서 --disable-compression=true 플래그를 사용하거나 disable-compression: true로 설정하면 됩니다.

+
apiVersion: v1
+clusters:
+- cluster:
+    server: serverURL
+    disable-compression: true
+  name: cluster
+
+

Cluster Autoscaler 샤딩

+

쿠버네티스 Cluster Autoscaler는 테스트를 거쳤습니다. 최대 1,000개 노드까지 확장할 수 있습니다. 1000개 이상의 노드가 있는 대규모 클러스터에서는 Cluster AutoScaler를 여러 인스턴스에 샤드 모드로 실행하는 것이 좋습니다. 각 클러스터 오토스케일러 인스턴스는 노드 그룹 세트를 확장하도록 구성되어 있습니다. 다음 예는 각 4개의 노드 그룹에 구성된 2개의 클러스터 오토 스케일링 구성을 보여줍니다.

+

ClusterAutoscaler-1

+
autoscalingGroups:
+- name: eks-core-node-grp-20220823190924690000000011-80c1660e-030d-476d-cb0d-d04d585a8fcb
+  maxSize: 50
+  minSize: 2
+- name: eks-data_m1-20220824130553925600000011-5ec167fa-ca93-8ca4-53a5-003e1ed8d306
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m2-20220824130733258600000015-aac167fb-8bf7-429d-d032-e195af4e25f5
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m3-20220824130553914900000003-18c167fa-ca7f-23c9-0fea-f9edefbda002
+  maxSize: 450
+  minSize: 2
+
+

ClusterAutoscaler-2

+
autoscalingGroups:
+- name: eks-data_m4-2022082413055392550000000f-5ec167fa-ca86-6b83-ae9d-1e07ade3e7c4
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m5-20220824130744542100000017-02c167fb-a1f7-3d9e-a583-43b4975c050c
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m6-2022082413055392430000000d-9cc167fa-ca94-132a-04ad-e43166cef41f
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m7-20220824130553921000000009-96c167fa-ca91-d767-0427-91c879ddf5af
+  maxSize: 450
+  minSize: 2
+
+

API Priority and Fairness

+

+

개요

+ + +

요청이 증가하는 기간 동안 과부하가 발생하지 않도록 보호하기 위해 API 서버는 특정 시간에 처리할 수 있는 진행 중인 요청 수를 제한합니다. 이 제한을 초과하면 API 서버는 요청을 거부하기 시작하고 "Too many requests"에 대한 429 HTTP 응답 코드를 클라이언트에 반환합니다. 서버가 요청을 삭제하고 클라이언트가 나중에 다시 시도하도록 하는 것이 요청 수에 대한 서버 측 제한을 두지 않고 컨트롤 플레인에 과부하를 주어 성능이 저하되거나 가용성이 저하될 수 있는 것보다 더 좋습니다.

+

이러한 진행 중인 요청이 다양한 요청 유형으로 나누어지는 방식을 구성하기 위해 쿠버네티스에서 사용하는 메커니즘을 API Priority and Fairness이라고 합니다. API 서버는 --max-requests-inflight--max-mutating-requests-inflight 플래그로 지정된 값을 합산하여 허용할 수 있는 총 진행 중인 요청 수를 구성합니다. EKS는 이러한 플래그에 대해 기본값인 400개 및 200개 요청을 사용하므로 주어진 시간에 총 600개의 요청을 전달할 수 있습니다. APF는 이러한 600개의 요청을 다양한 요청 유형으로 나누는 방법을 지정합니다. EKS 컨트롤 플레인은 각 클러스터에 최소 2개의 API 서버가 등록되어 있어 가용성이 높습니다. 이렇게 하면 클러스터 전체의 총 진행 중인 요청 수가 1200개로 늘어납니다.

+

PriorityLevelConfigurations 및 FlowSchemas라는 두 종류의 쿠버네티스 객체는 총 요청 수가 다양한 요청 유형 간에 분할되는 방식을 구성합니다. 이러한 객체는 API 서버에 의해 자동으로 유지 관리되며 EKS는 지정된 쿠버네티스 마이너 버전에 대해 이러한 객체의 기본 구성을 사용합니다. PriorityLevelConfigurations는 허용된 총 요청 수의 일부를 나타냅니다. 예를 들어 워크로드가 높은 PriorityLevelConfiguration에는 총 600개의 요청 중 98개가 할당됩니다. 모든 PriorityLevelConfigurations에 할당된 요청의 합계는 600입니다(또는 특정 수준이 요청의 일부만 허용된 경우 API Server가 반올림하므로 600보다 약간 높음). 클러스터의 PriorityLevelConfigurations와 각각에 할당된 요청 수를 확인하려면 다음 명령을 실행할 수 있습니다. EKS 1.24의 기본값은 다음과 같습니다.

+
$ kubectl get --raw /metrics | grep apiserver_flowcontrol_request_concurrency_limit
+apiserver_flowcontrol_request_concurrency_limit{priority_level="catch-all"} 13
+apiserver_flowcontrol_request_concurrency_limit{priority_level="global-default"} 49
+apiserver_flowcontrol_request_concurrency_limit{priority_level="leader-election"} 25
+apiserver_flowcontrol_request_concurrency_limit{priority_level="node-high"} 98
+apiserver_flowcontrol_request_concurrency_limit{priority_level="system"} 74
+apiserver_flowcontrol_request_concurrency_limit{priority_level="workload-high"} 98
+apiserver_flowcontrol_request_concurrency_limit{priority_level="workload-low"} 245
+
+

두 번째 유형의 객체는 FlowSchemas입니다. 특정 속성 세트가 포함된 API 서버 요청은 동일한 FlowSchema로 분류됩니다. 이러한 속성에는 인증된 사용자나 API Group, 네임스페이스 또는 리소스와 같은 요청의 속성이 포함됩니다. FlowSchema는 또한 이 유형의 요청이 매핑되어야 하는 PriorityLevelConfiguration을 지정합니다. 두 개체는 함께 "이 유형의 요청이 이 inflight 요청 비율에 포함되기를 원합니다."라고 말합니다. 요청이 API Server에 도달하면 모든 필수 속성과 일치하는 FlowSchemas를 찾을 때까지 각 FlowSchemas를 확인합니다. 여러 FlowSchemas가 요청과 일치하는 경우 API Server는 개체의 속성으로 지정된 일치 우선 순위가 가장 작은 FlowSchema를 선택합니다.

+

FlowSchemas와 PriorityLevelConfigurations의 매핑은 다음 명령을 사용하여 볼 수 있습니다.

+
$ kubectl get flowschemas
+NAME                           PRIORITYLEVEL     MATCHINGPRECEDENCE   DISTINGUISHERMETHOD   AGE     MISSINGPL
+exempt                         exempt            1                    <none>                7h19m   False
+eks-exempt                     exempt            2                    <none>                7h19m   False
+probes                         exempt            2                    <none>                7h19m   False
+system-leader-election         leader-election   100                  ByUser                7h19m   False
+endpoint-controller            workload-high     150                  ByUser                7h19m   False
+workload-leader-election       leader-election   200                  ByUser                7h19m   False
+system-node-high               node-high         400                  ByUser                7h19m   False
+system-nodes                   system            500                  ByUser                7h19m   False
+kube-controller-manager        workload-high     800                  ByNamespace           7h19m   False
+kube-scheduler                 workload-high     800                  ByNamespace           7h19m   False
+kube-system-service-accounts   workload-high     900                  ByNamespace           7h19m   False
+eks-workload-high              workload-high     1000                 ByUser                7h14m   False
+service-accounts               workload-low      9000                 ByUser                7h19m   False
+global-default                 global-default    9900                 ByUser                7h19m   False
+catch-all                      catch-all         10000                ByUser                7h19m   False
+
+

PriorityLevelConfigurations에는 Queue, Reject 또는 Exempt 유형이 있을 수 있습니다. Queue 및 Reject 유형의 경우 해당 우선순위 수준에 대한 최대 진행 중인 요청 수에 제한이 적용되지만 해당 제한에 도달하면 동작이 달라집니다. 예를 들어 워크로드가 높은 PriorityLevelConfiguration은 Queue 유형을 사용하며 컨트롤러 매니저, 엔드포인트 컨트롤러, 스케줄러 및 kube-system 네임스페이스에서 실행되는 파드에서 사용할 수 있는 요청이 98개 있습니다. Queue 유형이 사용되므로 API Server는 요청을 메모리에 유지하려고 시도하며 이러한 요청이 시간 초과되기 전에 진행 중인 요청 수가 98개 미만으로 떨어지기를 희망합니다. 특정 요청이 대기열에서 시간 초과되거나 너무 많은 요청이 이미 대기열에 있는 경우 API Server는 요청을 삭제하고 클라이언트에 429를 반환할 수밖에 없습니다. 대기열에 있으면 요청이 429를 수신하지 못할 수 있지만 요청의 종단 간 지연 시간이 늘어나는 단점이 있습니다.

+

이제 Reject 유형을 사용하여 포괄적인 PriorityLevelConfiguration에 매핑되는 포괄적인 FlowSchema를 살펴보겠습니다. 클라이언트가 13개의 진행 중인 요청 제한에 도달하면 API Server는 대기열을 실행하지 않고 429 응답 코드를 사용하여 요청을 즉시 삭제합니다. 마지막으로 Exempt 유형을 사용하여 PriorityLevelConfiguration에 매핑하는 요청은 429를 수신하지 않으며 항상 즉시 전달됩니다. 이는 healthz 요청이나 system:masters 그룹에서 오는 요청과 같은 우선순위가 높은 요청에 사용됩니다.

+

APF 및 삭제된 요청 모니터링

+

APF로 인해 삭제된 요청이 있는지 확인하려면 apiserver_flowcontrol_rejected_requests_total에 대한 API Server 지표를 모니터링하여 영향을 받은 FlowSchemas 및 PriorityLevelConfigurations를 확인할 수 있습니다. 예를 들어 이 지표는 워크로드가 낮은 대기열의 요청 시간 초과로 인해 service account FlowSchema의 요청 100개가 삭제되었음을 보여줍니다.

+
% kubectl get --raw /metrics | grep apiserver_flowcontrol_rejected_requests_total
+apiserver_flowcontrol_rejected_requests_total{flow_schema="service-accounts",priority_level="workload-low",reason="time-out"} 100
+
+

지정된 PriorityLevelConfiguration이 429를 수신하거나 큐로 인해 지연 시간이 증가하는 정도를 확인하려면 동시성 제한과 사용 중인 동시성의 차이를 비교할 수 있습니다. 이 예에는 100개의 요청 버퍼가 있습니다.

+
% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_request_concurrency_limit.*workload-low'
+apiserver_flowcontrol_request_concurrency_limit{priority_level="workload-low"} 245
+
+% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_request_concurrency_in_use.*workload-low'
+apiserver_flowcontrol_request_concurrency_in_use{flow_schema="service-accounts",priority_level="workload-low"} 145
+
+

특정 PriorityLevelConfiguration에서 대기열이 발생하지만 반드시 요청이 삭제되는 것은 아닌지 확인하려면 apiserver_flowcontrol_current_inqueue_requests에 대한 측정 지표를 참조할 수 있습니다.

+
% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_current_inqueue_requests.*workload-low'
+apiserver_flowcontrol_current_inqueue_requests{flow_schema="service-accounts",priority_level="workload-low"} 10
+
+

기타 유용한 Prometheus 측정항목은 다음과 같습니다:

+
    +
  • apiserver_flowcontrol_dispatched_requests_total
  • +
  • apiserver_flowcontrol_request_execution_seconds
  • +
  • apiserver_flowcontrol_request_wait_duration_seconds
  • +
+

APF 지표의 전체 목록은 업스트림 문서를 참조하세요.

+

삭제된 요청 방지

+

워크로드를 변경하여 429를 방지

+

APF가 허용된 최대 내부 요청 수를 초과하는 지정된 PriorityLevelConfiguration으로 인해 요청을 삭제하는 경우 영향을 받는 FlowSchemas의 클라이언트는 지정된 시간에 실행되는 요청 수를 줄일 수 있습니다. 이는 429가 발생하는 기간 동안 이루어진 총 요청 수를 줄임으로써 달성할 수 있습니다. 비용이 많이 드는 목록 호출과 같은 장기 실행 요청은 실행되는 전체 기간 동안 진행 중인 요청으로 계산되기 때문에 특히 문제가 됩니다. 비용이 많이 드는 요청 수를 줄이거나 목록 호출의 대기 시간을 최적화하면(예: 요청 당 가져오는 객체 수를 줄이거나 watch 요청을 사용하도록 전환) 해당 워크로드에 필요한 총 동시성을 줄이는 데 도움이 될 수 있습니다.

+

APF 설정을 변경하여 429를 방지

+
+

Warning

+

수행 중인 작업을 알고 있는 경우에만 기본 APF 설정을 변경하십시오. APF 설정이 잘못 구성되면 API Server 요청이 중단되고 워크로드가 크게 중단될 수 있습니다.

+
+

요청 삭제를 방지하기 위한 또 다른 접근 방식은 EKS 클러스터에 설치된 기본 FlowSchemas 또는 PriorityLevelConfigurations를 변경하는 것입니다. EKS는 지정된 쿠버네티스 마이너 버전에 대한 FlowSchemas 및 PriorityLevelConfigurations의 업스트림 기본 설정을 설치합니다. API는 객체에 대한 다음 주석이 false로 설정되지 않은 한 이러한 객체를 기본값으로 자동으로 다시 조정합니다.

+
  metadata:
+    annotations:
+      apf.kubernetes.io/autoupdate-spec: "false"
+
+

높은 수준에서 APF 설정은 다음 중 하나로 수정될 수 있습니다.

+
    +
  • 관심 있는 요청에 더 많은 기내 수용 능력을 할당하세요.
  • +
  • 다른 요청 유형에 대한 용량이 부족할 수 있는 비필수적이거나 비용이 많이 드는 요청을 격리합니다.
  • +
+

이는 기본 FlowSchemas 및 PriorityLevelConfigurations를 변경하거나 이러한 유형의 새 개체를 생성하여 수행할 수 있습니다. 관리자는 관련 PriorityLevelConfigurations 개체에 대한 authenticateConcurrencyShares 값을 늘려 할당되는 진행 중 요청 비율을 늘릴 수 있습니다. 또한 요청이 발송되기 전에 대기열에 추가되어 발생하는 추가 대기 시간을 애플리케이션에서 처리할 수 있는 경우 특정 시간에 대기열에 추가할 수 있는 요청 수도 늘어날 수 있습니다.

+

또는 고객의 워크로드에 맞는 새로운 FlowSchema 및 PriorityLevelConfigurations 개체를 생성할 수 있습니다. 기존 PriorityLevelConfigurations 또는 새로운 PriorityLevelConfigurations에 더 많은 secureConcurrencyShares를 할당하면 전체 한도가 API Server 당 600개로 유지되므로 다른 버킷에서 처리할 수 있는 요청 수가 줄어듭니다.

+

APF 기본값을 변경할 때 설정 변경으로 인해 의도하지 않은 429가 발생하지 않도록 비프로덕션 클러스터에서 이러한 측정항목을 모니터링해야 합니다.

+
    +
  1. 버킷이 요청 삭제를 시작하지 않도록 모든 FlowSchemas에 대해 apiserver_flowcontrol_rejected_requests_total에 대한 측정 지표를 모니터링해야 합니다.
  2. +
  3. apiserver_flowcontrol_request_concurrency_limitapiserver_flowcontrol_request_concurrency_in_use 값을 비교하여 사용 중인 동시성이 해당 우선순위 수준의 제한을 위반할 위험이 없는지 확인해야 합니다.
  4. +
+

새로운 FlowSchema 및 PriorityLevelConfiguration을 정의하는 일반적인 사용 사례 중 하나는 격리입니다. 파드에서 장기 실행 목록 이벤트 호출을 자체 요청 공유로 분리한다고 가정해 보겠습니다. 이렇게 하면 기존 서비스 계정 FlowSchema를 사용하는 파드의 중요한 요청이 429를 수신하여 요청 용량이 부족해지는 것을 방지할 수 있습니다. 진행 중인 요청의 총 개수는 유한하지만 이 예에서는 APF 설정을 수정하여 지정된 워크로드에 대한 요청 용량을 더 잘 나눌 수 있음을 보여줍니다.

+

List 이벤트 요청을 분리하기 위한 FlowSchema 객체의 예:

+
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
+kind: FlowSchema
+metadata:
+  name: list-events-default-service-accounts
+spec:
+  distinguisherMethod:
+    type: ByUser
+  matchingPrecedence: 8000
+  priorityLevelConfiguration:
+    name: catch-all
+  rules:
+  - resourceRules:
+    - apiGroups:
+      - '*'
+      namespaces:
+      - default
+      resources:
+      - events
+      verbs:
+      - list
+    subjects:
+    - kind: ServiceAccount
+      serviceAccount:
+        name: default
+        namespace: default
+
+
    +
  • 이 FlowSchema는 기본 네임스페이스의 Service Account에서 수행된 모든 List 이벤트 호출을 캡처합니다.
  • +
  • 일치 우선 순위 8000은 기존 Service Account FlowSchema에서 사용하는 값 9000보다 낮으므로 이러한 List 이벤트 호출은 Service Account가 아닌 list-events-default-service-account와 일치합니다.
  • +
  • 이러한 요청을 격리하기 위해 포괄적인 PriorityLevelConfiguration을 사용하고 있습니다. 이 버킷은 장기 실행되는 list 이벤트 호출에서 13개의 진행 중인 요청만 사용할 수 있도록 허용합니다. 파드는 동시에 13개 이상의 요청을 발행하려고 시도하지만 즉시 429를 응답받기 시작합니다.
  • +
+

API Server에서 리소스 검색

+

API Server에서 정보를 가져오는 것은 모든 규모의 클러스터에서 예상되는 동작입니다. 클러스터의 리소스 수를 확장하면 요청 빈도와 데이터 볼륨이 빠르게 컨트롤 플레인의 병목 현상이 되어 API 쓰로틀링 및 속도 저하로 이어질 수 있습니다. 지연 시간의 심각도에 따라 주의하지 않으면 예상치 못한 다운타임이 발생할 수 있습니다.

+

이러한 유형의 문제를 방지하기 위한 첫 번째 단계는 무엇을 요청하고 얼마나 자주 요청하는지 파악하는 것입니다. 다음은 규모 조정 모범 사례에 따라 쿼리 양을 제한하는 지침입니다. 이 섹션의 제안 사항은 확장성이 가장 좋은 것으로 알려진 옵션부터 순서대로 제공됩니다.

+

Shared Informers 사용

+

쿠버네티스 API와 통합되는 컨트롤러 및 자동화를 구축할 때 쿠버네티스 리소스에서 정보를 가져와야 하는 경우가 많습니다. 이러한 리소스를 정기적으로 폴링하면 API Server에 상당한 부하가 발생할 수 있습니다.

+

client-go 라이브러리의 informer 를 사용하면 변경 사항을 폴링하는 대신 이벤트를 기반으로 리소스의 변경 사항을 관찰할 수 있다는 이점이 있습니다. Shared Informer는 이벤트 및 변경 사항에 공유 캐시를 사용하여 부하를 더욱 줄이므로 동일한 리소스를 감시하는 여러 컨트롤러로 인해 추가 부하가 가중되지 않습니다.

+

컨트롤러는 특히 대규모 클러스터의 경우 label 및 field selectors 없이 클러스터 전체 리소스를 폴링하지 않아야 합니다. 필터링되지 않은 각 폴링에는 클라이언트가 필터링하기 위해 etcd에서 API Server를 통해 많은 양의 불필요한 데이터를 전송해야 합니다. 레이블과 네임스페이스를 기반으로 필터링하면 API 서버가 요청을 처리하고 클라이언트에 전송되는 데이터를 처리하기 위해 수행해야 하는 작업량을 줄일 수 있습니다.

+

쿠버네티스 API 사용 최적화

+

사용자 정의 컨트롤러 또는 자동화를 사용하여 쿠버네티스 API를 호출할 때 필요한 리소스로만 호출을 제한하는 것이 중요합니다. 제한이 없으면 API Server 및 etcd에 불필요한 로드가 발생할 수 있습니다.

+

가능하면 watch 인자를 사용하는 것이 좋습니다. 인자가 없으면 기본 동작은 객체를 나열하는 것입니다. list 대신 watch를 사용하려면 API 요청 끝에 ?watch=true를 추가하면 됩니다. 예를 들어 watch를 사용하여 기본 네임스페이스의 모든 파드를 가져오려면 다음을 사용하세요.

+
/api/v1/namespaces/default/pods?watch=true
+
+

객체를 나열하는 경우 나열하는 항목의 범위와 반환되는 데이터의 양을 제한해야 합니다. 요청에 limit=500 인수를 추가하여 반환되는 데이터를 제한할 수 있습니다. fieldSelector 인수와 /namespace/ 경로는 목록의 범위를 필요에 따라 좁게 지정하는 데 유용할 수 있습니다. 예를 들어 기본 네임스페이스에서 실행 중인 파드만 나열하려면 다음 API 경로와 인수를 사용합니다.

+
/api/v1/namespaces/default/pods?fieldSelector=status.phase=Running&limit=500
+
+

또는 다음을 사용하여 실행 중인 모든 파드를 나열합니다.

+
/api/v1/pods?fieldSelector=status.phase=Running&limit=500
+
+

나열된 오브젝트를 제한하는 또 다른 옵션은 ResourceVersions (쿠버네티스 설명서 참조)를 사용하는 것 입니다. ResourceVersion 인자가 없으면 사용 가능한 최신 버전을 받게 되며, 이 경우 데이터베이스에서 가장 비용이 많이 들고 가장 느린 etcd 쿼럼 읽기가 필요합니다. resourceVersion은 쿼리하려는 리소스에 따라 달라지며, Metadata.Resourceversion 필드에서 찾을 수 있습니다.

+

API Server 캐시에서 결과를 반환하는 특별한 ResourceVersion=0이 있습니다. 이렇게 하면 etcd 부하를 줄일 수 있지만 Pagination은 지원하지 않습니다.

+
/api/v1/namespaces/default/pods?resourceVersion=0
+
+

인자 없이 API를 호출하면 API Server 및 etcd에서 리소스를 가장 많이 사용하게 됩니다.이 호출을 사용하면 Pagination이나 범위 제한 없이 모든 네임스페이스의 모든 파드를 가져오고 etcd에서 쿼럼을 읽어야 합니다.

+
/api/v1/pods
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/data-plane/index.html b/ko/scalability/docs/data-plane/index.html new file mode 100644 index 000000000..f8e0779e6 --- /dev/null +++ b/ko/scalability/docs/data-plane/index.html @@ -0,0 +1,2439 @@ + + + + + + + + + + + + + + + + + + + + + + + 데이터 플레인 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

쿠버네티스 데이터 플레인

+

쿠버네티스 데이터 플레인은 쿠버네티스 컨트롤 플레인이 사용하는 EC2 인스턴스, 로드 밸런서, 스토리지 및 기타 API를 포함합니다. 조직화를 위해 클러스터 서비스를 별도의 페이지로 그룹화했으며 로드 밸런서 확장은 워크로드 섹션에서 찾을 수 있습니다. 이 섹션에서는 컴퓨팅 리소스 확장에 중점을 둡니다.

+

여러 워크로드가 있는 클러스터에서는 EC2 인스턴스 유형을 선택하는 것이 고객이 직면하는 가장 어려운 결정 중 하나일 수 있습니다. 모든 상황에 맞는 단일 솔루션은 없습니다. 다음은 컴퓨팅 확장과 관련된 일반적인 위험을 방지하는 데 도움이 되는 몇 가지 팁입니다.

+

자동 노드 오토 스케일링

+

수고를 줄이고 쿠버네티스와 긴밀하게 통합되는 노드 오토 스케일링을 사용하는 것이 좋습니다. 대규모 클러스터에는 관리형 노드 그룹Karpenter 를 사용하는 것이 좋습니다.

+

관리형 노드 그룹은 관리형 업그레이드 및 구성에 대한 추가 이점과 함께 Amazon EC2 Auto Scaling 그룹의 유연성을 제공합니다. 쿠버네티스 Cluster Autoscaler를 사용하여 확장할 수 있으며 다양한 컴퓨팅 요구 사항이 있는 클러스터에 대한 일반적인 옵션입니다.

+

Karpenter는 AWS에서 만든 오픈 소스 워크로드 네이티브 노드 오토 스케일러입니다. 노드 그룹을 관리하지 않고도 리소스 (예: GPU) 와 taint 및 tolerations (예: zone spread)에 대한 워크로드 요구 사항을 기반으로 클러스터의 노드를 확장합니다. 노드는 EC2로부터 직접 생성되므로 기본 노드 그룹 할당량 (그룹당 450개 노드)이 필요 없으며 운영 오버헤드를 줄이면서 인스턴스 선택 유연성이 향상됩니다. 고객은 가능하면 Karpenter를 사용하는 것이 좋습니다.

+

다양한 EC2 인스턴스 유형 사용

+

각 AWS 리전에는 인스턴스 유형별로 사용 ​​가능한 인스턴스 수가 제한되어 있습니다. 하나의 인스턴스 유형만 사용하는 클러스터를 생성하고 리전의 용량을 초과하여 노드 수를 확장하면 사용 가능한 인스턴스가 없다는 오류가 발생합니다. 이 문제를 방지하려면 클러스터에서 사용할 수 있는 인스턴스 유형을 임의로 제한해서는 안 됩니다.

+

Karpenter는 기본적으로 호환되는 다양한 인스턴스 유형을 사용하며 보류 중인 워크로드 요구 사항, 가용성 및 비용을 기반으로 프로비저닝 시 인스턴스를 선택합니다. NodePoolskarpenter.k8s.aws/instance-category 키에 사용되는 인스턴스 유형 목록을 정의할 수 있습니다.

+

쿠버네티스 Cluster Autoscaler를 사용하려면 노드 그룹이 일관되게 확장될 수 있도록 유사한 크기의 유형을 필요로 합니다. CPU 및 메모리 크기를 기준으로 여러 그룹을 생성하고 독립적으로 확장해야 합니다. ec2-instance-selector를 사용하여 노드 그룹과 비슷한 크기의 인스턴스를 식별하세요.

+
ec2-instance-selector --service eks --vcpus-min 8 --memory-min 16
+a1.2xlarge
+a1.4xlarge
+a1.metal
+c4.4xlarge
+c4.8xlarge
+c5.12xlarge
+c5.18xlarge
+c5.24xlarge
+c5.2xlarge
+c5.4xlarge
+c5.9xlarge
+c5.metal
+
+

API 서버 부하를 줄이기 위해 더 큰 노드를 선호

+

어떤 인스턴스 유형을 사용할지 결정할 때 노드 수가 적고 크면 쿠버네티스 컨트롤 플레인에 걸리는 부하가 줄어듭니다. 실행 중인 kubelet과 데몬셋의 수가 줄어들기 때문입니다. 그러나 큰 노드는 작은 노드처럼 충분히 활용되지 않을 수 있습니다. 노드 크기는 워크로드의 가용성 및 확장성을 기반으로 평가해야 합니다.

+

u-24tb1.metal 인스턴스 3개 (24TB Memory 및 448 cores)가 있는 클러스터에는 3개의 kubelets가 있으며 기본적으로 노드당 110개의 파드로 제한됩니다. 파드가 각각 4개의 코어를 사용하는 경우 이는 예상할 수 있습니다 (4코어 x 110 = 노드당 440코어). 클러스터에 3개의 노드를 사용하면 인스턴스 1개가 중단될 때 클러스터의 1/3에 영향을 미칠 수 있으므로 인스턴스 인시던트를 처리하는 능력이 떨어집니다. 쿠버네티스 스케줄러가 워크로드를 적절하게 배치할 수 있도록 워크로드에 노드 요구 사항과 pod spread를 지정해야 합니다.

+

워크로드는 taint, tolerations 및 PodTopologySpread를 통해 필요한 리소스와 필요한 가용성을 정의해야 합니다. 이들은 컨트롤 플레인의 부하 감소, 운영 감소, 비용 절감이라는 가용성 목표를 충분히 활용할 수 있고 가용성 목표를 충족할 수 있는 가장 큰 노드를 선호해야 합니다.

+

쿠버네티스 Scheduler는 리소스를 사용할 수 있는 경우 가용 영역과 호스트에 워크로드를 자동으로 분산하려고 합니다. 사용 가능한 용량이 없는 경우 쿠버네티스 Cluster Autoscaler는 각 가용 영역에 노드를 균등하게 추가하려고 시도합니다. 워크로드에 다른 요구 사항이 지정되어 있지 않는 한 Karpenter는 가능한 한 빠르고 저렴하게 노드를 추가하려고 시도합니다.

+

스케줄러를 통해 워크로드를 분산시키고 가용 영역 전체에 새 노드를 생성하도록 하려면 TopologySpreadConstraints (topologySpreadConstraints) 를 사용해야 합니다.

+
spec:
+  topologySpreadConstraints:
+    - maxSkew: 3
+      topologyKey: "topology.kubernetes.io/zone"
+      whenUnsatisfiable: ScheduleAnyway
+      labelSelector:
+        matchLabels:
+          dev: my-deployment
+    - maxSkew: 2
+      topologyKey: "kubernetes.io/hostname"
+      whenUnsatisfiable: ScheduleAnyway
+      labelSelector:
+        matchLabels:
+          dev: my-deployment
+
+

일관된 워크로드 성능을 위해 유사한 노드 크기 사용

+

워크로드는 일관된 성능과 예측 가능한 확장을 허용하기 위해 실행해야 하는 노드 크기를 정의해야 합니다. 500m CPU를 요청하는 워크로드는 4 cores 인스턴스와 16 cores 인스턴스에서 다르게 수행됩니다. T 시리즈 인스턴스와 같이 버스트 가능한 CPU를 사용하는 인스턴스 유형은 피하세요.

+

워크로드가 일관된 성능을 얻을 수 있도록 워크로드는 지원되는 Karpenter 레이블을 사용하여 특정 인스턴스 크기를 대상으로 할 수 있습니다.

+
kind: deployment
+...
+spec:
+  template:
+    spec:
+    containers:
+    nodeSelector:
+      karpenter.k8s.aws/instance-size: 8xlarge
+
+

쿠버네티스 Cluster Autoscaler를 사용하여 클러스터에서 스케줄링되는 워크로드는 레이블 매칭을 기반으로 node selector를 노드 그룹과 일치시켜야 합니다.

+
spec:
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: eks.amazonaws.com/nodegroup
+            operator: In
+            values:
+            - 8-core-node-group    # match your node group name
+
+

컴퓨팅 리소스를 효율적으로 사용

+

컴퓨팅 리소스에는 EC2 인스턴스 및 가용 영역이 포함됩니다. 컴퓨팅 리소스를 효과적으로 사용하면 확장성, 가용성, 성능이 향상되고 총 비용이 절감됩니다. 오토 스케일링 환경에서 여러 애플리케이션은 효율적인 리소스 사용량을 예측하기가 극히 어렵습니다. Karpenter는 워크로드 요구 사항에 따라 온디맨드로 인스턴스를 프로비저닝하여 활용도와 유연성을 극대화하기 위해 만들어졌습니다.

+

Karpenter를 사용하면 먼저 노드 그룹을 생성하거나 특정 노드에 대한 label taint를 구성하지 않고도 워크로드에 필요한 컴퓨팅 리소스 유형을 선언할 수 있습니다. 자세한 내용은 Karpenter best practices를 참조하세요. Karpenter provisioner에서 consolidation을 활성화하여 활용도가 낮은 노드를 교체해 보세요.

+

Amazon Machine Image (AMI) 업데이트 자동화

+

Worker 노드 구성 요소를 최신 상태로 유지하면 최신 보안 패치와 쿠버네티스 API와 호환되는 기능을 사용할 수 있습니다. kublet 업데이트는 쿠버네티스 기능의 가장 중요한 구성 요소이지만 OS, 커널 및 로컬에 설치된 애플리케이션 패치를 자동화하면 확장에 따른 유지 관리 비용을 줄일 수 있습니다.

+

노드 이미지에는 최신 Amazon EKS optimized Amazon Linux 2 또는 Amazon EKS optimized Bottlerocket AMI를 사용하는 것이 좋습니다. Karpenter는 사용 가능한 최신 AMI 를 자동으로 사용하여 클러스터에 새 노드를 프로비저닝합니다. 관리형 노드 그룹은 노드 그룹 업데이트 중에 AMI를 업데이트하지만 노드 프로비저닝 시에는 AMI ID를 업데이트하지 않습니다.

+

관리형 노드 그룹의 경우 패치 릴리스에 사용할 수 있게 되면 Auto Scaling Group (ASG) 시작 템플릿을 새 AMI ID로 업데이트해야 합니다. AMI 마이너 버전 (예: 1.23.5~1.24.3)은 EKS 콘솔 및 API에서 노드 그룹 업그레이드로 제공됩니다. 패치 릴리스 버전 (예: 1.23.5 ~ 1.23.6)은 노드 그룹에 대한 업그레이드로 제공되지 않습니다. AMI 패치 릴리스를 통해 노드 그룹을 최신 상태로 유지하려면 새 시작 템플릿 버전을 생성하고 노드 그룹이 인스턴스를 새 AMI 릴리스로 교체하도록 해야 합니다.

+

이 페이지 에서 사용 가능한 최신 AMI를 찾거나 AWS CLI를 사용할 수 있습니다.

+
aws ssm get-parameter \
+  --name /aws/service/eks/optimized-ami/1.24/amazon-linux-2/recommended/image_id \
+  --query "Parameter.Value" \
+  --output text
+
+

컨테이너에 여러 EBS 볼륨 사용

+

EBS 볼륨에는 볼륨 유형 (예: gp3) 및 디스크 크기에 따른 입/출력 (I/O) 할당량이 있습니다. 애플리케이션이 호스트와 단일 EBS 루트 볼륨을 공유하는 경우 전체 호스트의 디스크 할당량이 고갈되고 다른 애플리케이션이 가용 용량을 기다리게 될 수 있습니다.응 용 프로그램은 오버레이 파티션에 파일을 쓰고, 호스트에서 로컬 볼륨을 마운트하고, 사용된 로깅 에이전트에 따라 표준 출력 (STDOUT)에 로그온할 때도 디스크에 기록합니다.

+

Disk I/O 소모를 방지하려면 두 번째 볼륨을 컨테이너 state 폴더 (예: /run/containerd)에 마운트하고, 워크로드 스토리지용으로 별도의 EBS 볼륨을 사용하고, 불필요한 로컬 로깅을 비활성화해야 합니다.

+

eksctl을 사용하여 EC2 인스턴스에 두 번째 볼륨을 마운트하려면 다음과 같은 구성의 노드 그룹을 사용할 수 있습니다.

+
managedNodeGroups:
+  - name: al2-workers
+    amiFamily: AmazonLinux2
+    desiredCapacity: 2
+    volumeSize: 80
+    additionalVolumes:
+      - volumeName: '/dev/sdz'
+        volumeSize: 100
+    preBootstrapCommands:
+    - |
+      "systemctl stop containerd"
+      "mkfs -t ext4 /dev/nvme1n1"
+      "rm -rf /var/lib/containerd/*"
+      "mount /dev/nvme1n1 /var/lib/containerd/"
+      "systemctl start containerd"
+
+

Terraform을 사용하여 노드 그룹을 프로비저닝하는 경우 EKS Blueprints for terraform의 예를 참조하세요. Karpenter를 사용하여 노드를 프로비저닝하는 경우 노드 사용자 데이터와 함께 blockDeviceMappings를 사용하여 추가 볼륨을 추가할 수 있습니다.

+

EBS 볼륨을 파드에 직접 탑재하려면 AWS EBS CSI 드라이버를 사용하고 스토리지 클래스가 있는 볼륨을 사용해야 합니다.

+
---
+apiVersion: storage.k8s.io/v1
+kind: StorageClass
+metadata:
+  name: ebs-sc
+provisioner: ebs.csi.aws.com
+volumeBindingMode: WaitForFirstConsumer
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: ebs-claim
+spec:
+  accessModes:
+    - ReadWriteOnce
+  storageClassName: ebs-sc
+  resources:
+    requests:
+      storage: 4Gi
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: app
+spec:
+  containers:
+  - name: app
+    image: public.ecr.aws/docker/library/nginx
+    volumeMounts:
+    - name: persistent-storage
+      mountPath: /data
+  volumes:
+  - name: persistent-storage
+    persistentVolumeClaim:
+      claimName: ebs-claim
+
+

워크로드에서 EBS 볼륨을 사용하는 경우 EBS 연결 제한이 낮은 인스턴스를 피하세요.

+

EBS는 워크로드가 영구 스토리지를 확보하는 가장 쉬운 방법 중 하나이지만 확장성 제한도 있습니다. 각 인스턴스 유형에는 연결할 수 있는 최대 EBS 볼륨 수가 있습니다. 워크로드는 실행해야 하는 인스턴스 유형을 선언하고 쿠버네티스 taints가 있는 단일 인스턴스의 복제본 수를 제한해야 합니다.

+

디스크에 불필요한 로깅을 비활성화

+

프로덕션 환경에서 디버그 로깅을 사용하여 애플리케이션을 실행하지 않고 디스크를 자주 읽고 쓰는 로깅을 비활성화하여 불필요한 로컬 로깅을 피하세요. Journald는 로그 버퍼를 메모리에 유지하고 주기적으로 디스크에 플러시하는 로컬 로깅 서비스입니다. Journald는 모든 행을 즉시 디스크에 기록하는 syslog보다 선호됩니다. syslog를 비활성화하면 필요한 총 스토리지 용량도 줄어들고 복잡한 로그 순환 규칙이 필요하지 않습니다. syslog를 비활성화하려면 cloud-init 구성에 다음 코드 조각을 추가하면 됩니다.

+
runcmd:
+  - [ systemctl, disable, --now, syslog.service ]
+
+

OS 업데이트 속도가 필요할 때 in place 방식으로 인스턴스 패치

+
+

Attention

+

인스턴스 패치는 필요한 경우에만 수행해야 합니다. Amazon에서는 인프라를 변경할 수 없는 것으로 취급하고 애플리케이션과 동일한 방식으로 하위 환경을 통해 승격되는 업데이트를 철저히 테스트할 것을 권장합니다. 이 섹션은 이것이 불가능할 때 적용됩니다.

+
+

컨테이너화된 워크로드를 중단하지 않고 기존 Linux 호스트에 패키지를 설치하는 데 몇 초 밖에 걸리지 않습니다. 인스턴스를 차단(cordoning), 드레이닝(draining) 또는 교체(replacing)하지 않고도 패키지를 설치하고 검증할 수 있습니다.

+

인스턴스를 교체하려면 먼저 새 AMI를 생성, 검증 및 배포해야 합니다. 인스턴스에는 대체 인스턴스가 생성되어야 하며, 이전 인스턴스는 차단 및 제거되어야 합니다. 그런 다음 새 인스턴스에 워크로드를 생성하고 확인하고 패치가 필요한 모든 인스턴스에 대해 반복해야 합니다. 워크로드를 중단하지 않고 인스턴스를 안전하게 교체하려면 몇 시간, 며칠 또는 몇 주가 걸립니다.

+

Amazon에서는 자동화된 선언적 시스템에서 구축, 테스트 및 승격되는 불변 인프라를 사용할 것을 권장합니다. 하지만 시스템에 신속하게 패치를 적용해야 하는 경우 시스템을 패치하고 새 AMI가 출시되면 교체해야 합니다. 시스템 패치와 교체 사이의 시간 차이가 크기 때문에 AWS Systems Manager Patch Manager를 사용하는 것이 좋습니다. 필요한 경우 노드 패치를 자동화합니다.

+

노드 패치를 사용하면 보안 업데이트를 신속하게 출시하고 AMI가 업데이트된 후 정기적인 일정에 따라 인스턴스를 교체할 수 있습니다. Flatcar Container Linux 또는 Bottlerocket OS와 같은 읽기 전용 루트 파일 시스템이 있는 운영 체제를 사용하는 경우 해당 운영 체제에서 작동하는 업데이트 연산자를 사용하는 것이 좋습니다. Flatcar Linux update operatorBottlerocket update operator는 인스턴스를 재부팅하여 노드를 자동으로 최신 상태로 유지합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/index.html b/ko/scalability/docs/index.html new file mode 100644 index 000000000..a696913ba --- /dev/null +++ b/ko/scalability/docs/index.html @@ -0,0 +1,2176 @@ + + + + + + + + + + + + + + + + + + + + + + + 홈 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

EKS 확장성 모범사례

+

이 가이드에서는 EKS 클러스터 확장에 대한 조언을 제공합니다. EKS 클러스터를 확장하는 목적은 단일 클러스터가 수행할 수 있는 작업량을 최대화하는 것입니다. 하나의 대규모 EKS 클러스터를 사용하면 여러 클러스터를 사용하는 것에 비해 운영 부하를 줄일 수 있지만 멀티 리전 배포, 테넌트 격리, 클러스터 업그레이드 등의 측면에서 단점이 있습니다. 이 문서에서는 단일 클러스터로 확장성을 극대화하는 방법에 초점을 맞출 것입니다.

+

가이드 사용법

+

해당 가이드는 AWS에서 EKS 클러스터를 생성하고 관리하는 개발자 및 관리자를 대상으로 합니다. 이 백서는 몇 가지 일반적인 쿠버네티스 확장 사례를 중점적으로 다루지만, 자체 관리형 쿠버네티스 클러스터 또는 EKS Anywhere를 사용하여 AWS 리전 외부에서 실행되는 클러스터에 대한 구체적인 내용은 디루지 않습니다.

+

각 주제에는 간략한 개요가 포함되며, EKS 클러스터를 대규모로 운영하기 위한 권장 사항 및 모범 사례가 이어집니다. 주제를 특정 순서로 읽을 필요는 없으며 클러스터에서 작동하는지 테스트하고 검증하지 않고는 권장 사항을 적용해서는 안 됩니다.

+

크기 조정 요소에 대한 이해

+

확장성은 성능 및 안정성과 다르므로 클러스터 및 워크로드 요구 사항을 계획할 때는 이 세 가지를 모두 고려해야 합니다. 클러스터가 확장되면 모니터링이 필요하지만 이 가이드에서는 모니터링 모범 사례를 다루지 않습니다. EKS는 대규모로 확장할 수 있지만, 클러스터를 300개 노드 또는 5000개 이상의 파드로 확장할 방법을 계획해야 합니다. 절대적인 수치는 아니지만 이 가이드를 여러 사용자, 엔지니어 및 서포트 전문가와 공동으로 작업한 결과입니다.

+

쿠버네티스의 확장은 다차원적이며 모든 상황에 맞는 특정 설정이나 권장 사항은 없습니다. 확장에 대한 지침을 제공할 수 있는 주요 영역은 다음과 같습니다.

+ +

쿠버네티스 컨트롤 플레인은 EKS 클러스터에는 AWS가 실행하고 사용자를 위해 자동으로 확장되는 모든 서비스 (예: 쿠버네티스 API Server) 가 포함됩니다. 컨트롤 플레인을 확장하는 것은 AWS의 책임이지만 컨트롤 플레인을 책임감 있게 사용하는 것은 사용자의 책임입니다.

+

쿠버네티스 데이터 플레인 규모 조정은 클러스터 및 워크로드에 필요한 AWS 리소스를 다루지만 EKS 컨트롤 플레인을 벗어납니다. EC2 인스턴스, kublet, 스토리지를 비롯한 모든 리소스는 클러스터 확장에 따라 확장해야 합니다.

+

클러스터 서비스는 클러스터 내에서 실행되며 클러스터 및 워크로드에 기능을 제공하는 쿠버네티스 컨트롤러 및 애플리케이션입니다. 여기에는 EKS 애드온이 포함될 수 있으며 규정 준수 및 통합을 위해 설치하는 기타 서비스 또는 헬름 차트도 포함될 수 있습니다. 이런 서비스는 워크로드에 따라 달라지는 경우가 많으며 워크로드가 확장됨에 따라 클러스터 서비스도 함께 확장해야 합니다.

+

워크로드는 클러스터가 있는 이유이며 클러스터와 함께 수평으로 확장되어야 합니다. 클러스터 확장에 도움이 될 수 있는 쿠버네티스의 워크로드 통합 및 설정이 있습니다. 네임스페이스 및 서비스와 같은 쿠버네티스 추상화에 대한 아키텍처 고려 사항도 있습니다.

+

초대형 스케일링

+

단일 클러스터를 1,000개 노드 또는 50,000개 이상의 파드로 확장하려는 경우 문의하고 싶습니다. 지원 팀이나 기술 계정 관리자에게 문의하여 이 가이드에 제공된 정보 이상으로 계획하고 확장하는 데 도움을 줄 수 있는 전문가에게 문의하는 것이 좋습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/kcp_monitoring/index.html b/ko/scalability/docs/kcp_monitoring/index.html new file mode 100644 index 000000000..6f17ffeca --- /dev/null +++ b/ko/scalability/docs/kcp_monitoring/index.html @@ -0,0 +1,2508 @@ + + + + + + + + + + + + + + + + + + + + + + + 컨트롤 플레인 모니터링 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

컨트롤 플레인 모니터링

+

API Server

+

API Server를 살펴볼 때 그 기능 중 하나가 컨트롤 플레인의 과부하를 방지하기 위해 인바운드 요청을 조절하는 것임을 기억하는 것이 중요합니다. API Server 수준에서 병목 현상이 발생하는 것처럼 보일 수 있는 것은 실제로 더 심각한 문제로부터 이를 보호하는 것일 수도 있습니다. 시스템을 통해 이동하는 요청량 증가의 장단점을 고려해야 합니다. API Server 값을 늘려야 하는지 결정하기 위해 염두에 두어야 할 사항에 대한 작은 샘플링은 다음과 같습니다.

+
    +
  1. 시스템을 통해 이동하는 요청의 대기 시간은 얼마나 됩니까?
  2. +
  3. 지연 시간은 API Server 자체입니까, 아니면 etcd와 같은 "다운스트림"입니까?
  4. +
  5. API 서버 대기열 깊이가 이 지연 시간의 요인입니까?
  6. +
  7. API 우선 순위 및 공정성(APF) 대기열이 우리가 원하는 API 호출 패턴에 맞게 올바르게 설정되어 있습니까?
  8. +
+

문제가 있는 곳은 어디입니까?

+

먼저 API 지연 시간 측정 지표를 사용하여 API Server가 요청을 처리하는 데 걸리는 시간을 파악할 수 있습니다. 아래 PromQL 및 Grafana 히트맵을 사용하여 이 데이터를 표시해 보겠습니다.

+
max(increase(apiserver_request_duration_seconds_bucket{subresource!="status",subresource!="token",subresource!="scale",subresource!="/healthz",subresource!="binding",subresource!="proxy",verb!="WATCH"}[$__rate_interval])) by (le)
+
+
+

Tip

+

이 문서에 사용된 API 대시보드로 API 서버를 모니터링하는 방법에 대한 자세한 내용은 다음 blog를 참조하세요.

+
+

API 요청 지연 히트맵

+

이러한 요청은 모두 1초 아래에 있습니다. 이는 컨트롤 플레인이 적시에 요청을 처리하고 있음을 나타내는 좋은 표시입니다. 하지만 그렇지 않다면 어떨까요?

+

위의 API 요청 기간에서 사용하는 형식은 히트맵입니다. 히트맵 형식의 좋은 점은 기본적으로 API의 제한 시간 값(60초)을 알려준다는 것입니다. 그러나 실제로 알아야 할 것은 시간 초과 임계값에 도달하기 전에 이 값이 어떤 임계값에 관심을 가져야 하는가입니다. 허용 가능한 임계값에 대한 대략적인 지침을 보려면 여기에서 찾을 수 있는 업스트림 쿠버네티스 SLO를 사용할 수 있습니다.

+
+

Tip

+

이 명령문의 max 함수를 확인하세요. 여러 서버(기본적으로 EKS의 두 API Server)를 집계하는 지표를 사용할 때 해당 서버의 평균을 구하지 않는 것이 중요합니다.

+
+

비대칭 트래픽 패턴

+

한 API Server [pod]는 로드가 약하고 다른 하나는 과부하가 걸리면 어떻게 될까요?이 두 숫자의 평균을 구하면 무슨 일이 벌어졌는지 잘못 해석할 수 있습니다. 예를 들어, 여기에는 세 개의 API Server가 있지만 모든 로드는 이 API Server 중 하나에 있습니다. 일반적으로 etcd 및 API 서버와 같이 여러 서버가 있는 모든 것은 규모 및 성능 문제에 투자할 때 분리되어야 합니다.

+

Total inflight requests

+

API 우선순위 및 공정성(APF)으로 전환하면서 시스템의 총 요청 수는 API Server가 초과 구독되었는지 확인하는 하나의 요소일 뿐입니다. 이제 시스템에서 일련의 대기열을 사용하므로 대기열이 꽉 찼는지, 해당 대기열의 트래픽이 삭제되고 있는지 확인해야 합니다.

+

다음 쿼리를 사용하여 이러한 대기열을 살펴보겠습니다.

+
max without(instance)(apiserver_flowcontrol_request_concurrency_limit{})
+
+
+

Note

+

API A&F 작동 방식에 대한 자세한 내용은 다음 모범 사례 가이드를 참조하세요.

+
+

여기에서는 클러스터에 기본적으로 제공되는 7개의 서로 다른 우선순위 그룹을 볼 수 있습니다.

+

Shared concurrency

+

다음으로 특정 우선순위 수준이 포화 상태인지 파악하기 위해 해당 우선순위 그룹이 몇 퍼센트의 비율로 사용되고 있는지 확인하고자 합니다. 워크로드가 낮은 수준에서는 요청을 스로틀링하는 것이 바람직할 수 있지만 리더 선출 수준에서는 그렇지 않을 수 있습니다.

+

API 우선 순위 및 공정성(APF) 시스템에는 여러 가지 복잡한 옵션이 있으며, 이러한 옵션 중 일부는 의도하지 않은 결과를 초래할 수 있습니다. 워크로드에서 일반적으로 볼 수 있는 문제는 불필요한 대기 시간이 추가되기 시작하는 지점까지 대기열 깊이를 늘리는 것입니다. apiserver_flowcontrol_current_inqueue_request 지표를 사용하여 이 문제를 모니터링할 수 있습니다. apiserver_flowcontrol_rejected_requests_total을 사용하여 삭제를 확인할 수 있습니다. 버킷이 동시성을 초과하는 경우 이러한 지표는 0이 아닌 값이 됩니다.

+

Requests in use

+

대기열 깊이를 늘리면 API Server가 지연 시간의 중요한 원인이 될 수 있으므로 주의해서 수행해야 합니다. 생성된 대기열 수를 신중하게 결정하는 것이 좋습니다. 예를 들어 EKS 시스템의 공유 수는 600개입니다. 너무 많은 대기열을 생성하면 리더 선택 대기열이나 시스템 대기열과 같이 처리량이 필요한 중요한 대기열의 공유가 줄어들 수 있습니다. 추가 대기열을 너무 많이 생성하면 이러한 대기열의 크기를 올바르게 지정하기가 더 어려워질 수 있습니다.

+

APF에서 수행할 수 있는 간단하고 영향력 있는 변경에 초점을 맞추기 위해 활용도가 낮은 버킷에서 공유를 가져와 최대 사용량에 있는 버킷의 크기를 늘립니다. 이러한 버킷 간에 공유를 지능적으로 재분배함으로써 삭제 가능성을 줄일 수 있습니다.

+

자세한 내용은 EKS 모범 사례 가이드 API 우선순위 및 공정성 설정을 참조하세요.

+

API vs. etcd 지연 시간

+

API Server의 메트릭/로그를 사용하여 API Server에 문제가 있는지, API Server의 업스트림/다운스트림 또는 이 둘의 조합에 문제가 있는지 판단하려면 어떻게 해야 합니까? 이를 더 잘 이해하기 위해 API Server와 etcd가 어떤 관련이 있는지, 그리고 잘못된 시스템을 해결하는 것이 얼마나 쉬운지 살펴보겠습니다.

+

아래 차트에서는 API Server의 지연 시간을 볼 수 있지만, etcd 수준에서 대부분의 지연 시간을 보여주는 그래프의 막대로 인해 이 지연 시간의 상당 부분이 etcd 서버와 연관되어 있음을 알 수 있습니다. etcd 대기 시간이 15초이고 동시에 API 서버 대기 시간이 20초인 경우 대기 시간의 대부분은 실제로 etcd 수준에 있습니다

+

전체 흐름을 살펴보면 API Server에만 집중하지 않고 etcd가 압박을 받고 있음을 나타내는 신호(예: slow apply counters 증가)를 찾는 것이 현명하다는 것을 알 수 있습니다.

+
+

Tip

+

The dashboard in section can be found at https://github.com/RiskyAdventure/Troubleshooting-Dashboards/blob/main/api-troubleshooter.json

+
+

ETCD duress

+

컨트롤 플레인과 클라이언트 측 문제

+

이 차트에서는 해당 기간 동안 완료하는 데 가장 많은 시간이 걸린 API 호출을 찾고 있습니다. 이 경우 사용자 정의 리소스(CRD)가 05:40 시간 프레임 동안 가장 오래걸린 호출이 APPLY 함수라는 것을 볼 수 있습니다.

+

Slowest requests

+

이 데이터를 바탕으로 Ad-Hoc PromQL 또는 CloudWatch Insights 쿼리를 사용하여 해당 기간 동안 감사 로그에서 LIST 요청을 가져와서 어떤 애플리케이션인지 확인할 수 있습니다.

+

CloudWatch로 소스 찾기

+

메트릭은 살펴보고자 하는 문제 영역을 찾고 문제의 기간과 검색 매개변수를 모두 좁히는 데 가장 잘 사용됩니다. 이 데이터가 확보되면 더 자세한 시간과 오류에 대한 로그로 전환하려고 합니다. 이를 위해 CloudWatch Logs Insights를 사용하여 로그를 메트릭으로 전환합니다.

+

예를 들어, 위의 문제를 조사하기 위해 다음 CloudWatch Logs Insights 쿼리를 사용하여 사용자 에이전트 및 requestURI를 가져와서 어떤 애플리케이션이 이 지연을 일으키는지 정확히 파악할 수 있습니다.

+
+

Tip

+

Watch에서 정상적인 List/Resync 동작을 가져오지 않으려면 적절한 개수를 사용해야 합니다.

+
+
fields *@timestamp*, *@message*
+| filter *@logStream* like "kube-apiserver-audit"
+| filter ispresent(requestURI)
+| filter verb = "list"
+| parse requestReceivedTimestamp /\d+-\d+-(?<StartDay>\d+)T(?<StartHour>\d+):(?<StartMinute>\d+):(?<StartSec>\d+).(?<StartMsec>\d+)Z/
+| parse stageTimestamp /\d+-\d+-(?<EndDay>\d+)T(?<EndHour>\d+):(?<EndMinute>\d+):(?<EndSec>\d+).(?<EndMsec>\d+)Z/
+| fields (StartHour * 3600 + StartMinute * 60 + StartSec + StartMsec / 1000000) as StartTime, (EndHour * 3600 + EndMinute * 60 + EndSec + EndMsec / 1000000) as EndTime, (EndTime - StartTime) as DeltaTime
+| stats avg(DeltaTime) as AverageDeltaTime, count(*) as CountTime by requestURI, userAgent
+| filter CountTime >=50
+| sort AverageDeltaTime desc
+
+

이 쿼리를 사용하여 대기 시간이 긴 list 작업을 대량으로 실행하는 두 개의 서로 다른 에이전트(Splunk 및 CloudWatch Agent)를 발견했습니다. 데이터를 바탕으로 이 컨트롤러를 제거, 업데이트하거나 다른 프로젝트로 교체하기로 결정할 수 있습니다.

+

Query results

+
+

Tip

+

이 주제에 대한 자세한 내용은 다음 블로그를 참조하세요.

+
+

Scheduler

+

EKS 컨트롤 플레인 인스턴스는 별도의 AWS 계정에서 실행되므로 측정 지표에 대한 해당 구성 요소를 수집할 수 없습니다(API Server는 예외임). 그러나 이러한 구성 요소에 대한 감사 로그에 액세스할 수 있으므로 해당 로그를 지표로 변환하여 확장 시 병목 현상을 일으키는 하위 시스템이 있는지 확인할 수 있습니다. CloudWatch Logs Insights를 사용하여 스케줄러 대기열에 예약되지 않은 Pod가 몇 개 있는지 확인해 보겠습니다.

+

스케줄러 로그에서 예약되지 않은 파드

+

자체 관리형 쿠버네티스(예: Kops)에서 직접 스케줄러 지표를 스크랩할 수 있는 액세스 권한이 있는 경우 다음 PromQL을 사용하여 스케줄러 백로그를 이해합니다.

+
max without(instance)(scheduler_pending_pods)
+
+

EKS에서는 위 지표에 액세스할 수 없으므로 아래 CloudWatch Logs Insights 쿼리를 사용하여 특정 기간 동안 예약을 취소할 수 없었던 파드 수를 확인하여 백로그를 확인할 것입니다. 그러면 피크 타임의 메시지를 더 자세히 분석하여 병목 현상의 특성을 이해할 수 있습니다. 노드가 충분히 빠르게 교체되지 않거나 스케줄러 자체의 rate limiter를 예로 들 수 있습니다.

+
fields timestamp, pod, err, *@message*
+| filter *@logStream* like "scheduler"
+| filter *@message* like "Unable to schedule pod"
+| parse *@message*  /^.(?<date>\d{4})\s+(?<timestamp>\d+:\d+:\d+\.\d+)\s+\S*\s+\S+\]\s\"(.*?)\"\s+pod=(?<pod>\"(.*?)\")\s+err=(?<err>\"(.*?)\")/
+| count(*) as count by pod, err
+| sort count desc
+
+

여기서는 스토리지 PVC를 사용할 수 없어 파드가 배포되지 않았다는 스케줄러의 오류를 볼 수 있습니다.

+

CloudWatch Logs query

+
+

Note

+

이 기능을 활성화하려면 컨트롤 플레인에서 감사 로깅을 켜야 합니다. 시간이 지남에 따라 불필요하게 비용이 증가하지 않도록 로그 보존을 제한하는 것도 가장 좋은 방법입니다. 다음은 EKSCTL 도구를 사용하여 모든 로깅 기능을 켜는 예제입니다.

+
+
cloudWatch:
+  clusterLogging:
+    enableTypes: ["*"]
+    logRetentionInDays: 10
+
+

Kube Controller Manager

+

다른 모든 컨트롤러와 마찬가지로 Kube Controller Manager는 한 번에 수행할 수 있는 작업 수에 제한이 있습니다. 이러한 파라미터를 설정할 수 있는 KOPS 구성을 살펴보면서 이러한 플래그 중 일부가 무엇인지 살펴보겠습니다.

+
  kubeControllerManager:
+    concurrentEndpointSyncs: 5
+    concurrentReplicasetSyncs: 5
+    concurrentNamespaceSyncs: 10
+    concurrentServiceaccountTokenSyncs: 5
+    concurrentServiceSyncs: 5
+    concurrentResourceQuotaSyncs: 5
+    concurrentGcSyncs: 20
+    kubeAPIBurst: 20
+    kubeAPIQPS: "30"
+
+

이러한 컨트롤러에는 클러스터에서 변동이 심할 때 대기열이 꽉 차게 됩니다. 이 경우 replicaset controller의 대기열에 대규모 백로그가 있는 것을 확인할 수 있습니다.

+

Queues

+

이러한 상황을 해결하는 방법에는 두 가지가 있습니다. 자체 관리를 실행하는 경우 동시 고루틴을 늘릴 수 있지만 이는 KCM에서 더 많은 데이터를 처리하여 etcd에 영향을 미칠 수 있습니다. 다른 옵션은 배포에서 .spec.revisionHistoryLimit을 사용하여 replicaset 개체 수를 줄이고 롤백할 수 있는 replicaset 개체 수를 줄여 해당 컨트롤러에 대한 부담을 줄이는 것입니다.

+
spec:
+  revisionHistoryLimit: 2
+
+

다른 쿠버네티스 기능을 조정하거나 해제하여 이탈률이 높은 시스템의 압력을 줄일 수 있습니다. 예를 들어, 파드의 애플리케이션이 k8s API와 직접 통신할 필요가 없는 경우 해당 파드에 적용된 시크릿을 끄면 ServiceAccountTokenSync의 부하를 줄일 수 있습니다. 가능하면 이런 문제를 해결할 수 있는 더 바람직한 방법입니다.

+
kind: Pod
+spec:
+  automountServiceAccountToken: false
+
+

지표에 액세스할 수 없는 시스템에서는 로그를 다시 검토하여 경합을 감지할 수 있습니다. 컨트롤러당 또는 집계 수준에서 처리되는 요청의 수를 확인하려면 다음과 같은 CloudWatch Logs Insights 쿼리를 사용하면 됩니다.

+

Total Volume Processed by the KCM

+
# Query to count API qps coming from kube-controller-manager, split by controller type.
+# If you're seeing values close to 20/sec for any particular controller, it's most likely seeing client-side API throttling.
+fields @timestamp, @logStream, @message
+| filter @logStream like /kube-apiserver-audit/
+| filter userAgent like /kube-controller-manager/
+# Exclude lease-related calls (not counted under kcm qps)
+| filter requestURI not like "apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager"
+# Exclude API discovery calls (not counted under kcm qps)
+| filter requestURI not like "?timeout=32s"
+# Exclude watch calls (not counted under kcm qps)
+| filter verb != "watch"
+# If you want to get counts of API calls coming from a specific controller, uncomment the appropriate line below:
+# | filter user.username like "system:serviceaccount:kube-system:job-controller"
+# | filter user.username like "system:serviceaccount:kube-system:cronjob-controller"
+# | filter user.username like "system:serviceaccount:kube-system:deployment-controller"
+# | filter user.username like "system:serviceaccount:kube-system:replicaset-controller"
+# | filter user.username like "system:serviceaccount:kube-system:horizontal-pod-autoscaler"
+# | filter user.username like "system:serviceaccount:kube-system:persistent-volume-binder"
+# | filter user.username like "system:serviceaccount:kube-system:endpointslice-controller"
+# | filter user.username like "system:serviceaccount:kube-system:endpoint-controller"
+# | filter user.username like "system:serviceaccount:kube-system:generic-garbage-controller"
+| stats count(*) as count by user.username
+| sort count desc
+
+

여기서 중요한 점은 확장성 문제를 조사할 때 자세한 문제 해결 단계로 이동하기 전에 경로의 모든 단계(API, 스케줄러, KCM 등)를 살펴보는 것입니다. 프로덕션에서는 시스템이 최고의 성능으로 작동할 수 있도록 쿠버네티스의 두 부분 이상을 조정해야 하는 경우가 종종 있습니다. 훨씬 더 큰 병목 현상의 단순한 증상(예: 노드 시간 초과)을 실수로 해결하는 것은 쉽습니다.

+

ETCD

+

etcd는 메모리 매핑 파일을 사용하여 키 값 쌍을 효율적으로 저장합니다. 일반적으로 2, 4, 8GB 제한으로 설정된 사용 가능한 메모리 공간의 크기를 설정하는 보호 메커니즘이 있습니다. 데이터베이스의 개체 수가 적다는 것은 개체가 업데이트되고 이전 버전을 정리해야 할 때 etcd에서 수행해야 하는 정리 작업이 줄어든다는 것을 의미합니다. 객체의 이전 버전을 정리하는 이러한 프로세스를 압축이라고 합니다. 여러 번의 압축 작업 후에는 특정 임계값 이상 또는 고정된 시간 일정에 따라 발생하는 조각 모음(defragging)이라는 사용 가능한 공간을 복구하는 후속 프로세스가 있습니다.

+

쿠버네티스의 개체 수를 제한하여 압축 및 조각 모음 프로세스의 영향을 줄이기 위해 수행할 수 있는 몇 가지 사용자 관련 항목이 있습니다. 예를 들어 Helm은 높은 revisionHistoryLimit을 유지합니다. 이렇게 하면 ReplicaSet와 같은 이전 개체가 시스템에 유지되어 롤백을 수행할 수 있습니다. 기록 제한을 2로 설정하면 개체(예: ReplicaSets) 수를 10에서 2로 줄일 수 있으며 결과적으로 시스템에 대한 로드가 줄어듭니다.

+
apiVersion: apps/v1
+kind: Deployment
+spec:
+  revisionHistoryLimit: 2
+
+

모니터링 관점에서 시스템 지연 시간 급증이 시간 단위로 구분된 설정된 패턴으로 발생하는 경우 이 조각 모음 프로세스가 원인인지 확인하는 것이 도움이 될 수 있습니다. CloudWatch Logs를 사용하면 이를 확인할 수 있습니다.

+

조각 모음의 시작/종료 시간을 보려면 다음 쿼리를 사용하십시오.

+
fields *@timestamp*, *@message*
+| filter *@logStream* like /etcd-manager/
+| filter *@message* like /defraging|defraged/
+| sort *@timestamp* asc
+
+

Defrag query

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/kubernetes_slos/index.html b/ko/scalability/docs/kubernetes_slos/index.html new file mode 100644 index 000000000..2d219dfa7 --- /dev/null +++ b/ko/scalability/docs/kubernetes_slos/index.html @@ -0,0 +1,2213 @@ + + + + + + + + + + + + + + + + + + + + + + + 쿠버네티스 SLO - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

쿠버네티스 업스트림 SLO

+

Amazon EKS는 업스트림 쿠버네티스 릴리스와 동일한 코드를 실행하고 EKS 클러스터가 쿠버네티스 커뮤니티에서 정의한 SLO 내에서 작동하도록 합니다. 쿠버네티스 확장성 SIG는 확장성 목표를 정의하고, 정의된 SLI(Service Level Indicator)와 SLO(Service Level Objective)를 통해 성능 병목 현상을 조사합니다.

+

SLI는 시스템이 얼마나 “잘” 실행되고 있는지 판단하는 데 사용할 수 있는 메트릭이나 측정값과 같이 시스템을 측정하는 방법(예: 요청 지연 시간 또는 개수)입니다. SLO는 시스템이 '정상' 실행될 때 예상되는 값을 정의합니다. 예를 들어 요청 지연 시간이 3초 미만으로 유지됩니다. 쿠버네티스 SLO와 SLI는 쿠버네티스 구성 요소의 성능에 초점을 맞추고 EKS 클러스터 엔드포인트의 가용성에 초점을 맞춘 Amazon EKS 서비스 SLA와는 완전히 독립적입니다.

+

쿠버네티스는 사용자가 CSI 드라이버, 어드미션 웹훅, 자동 스케일러와 같은 커스텀 애드온 또는 드라이버로 시스템을 확장할 수 있는 많은 기능을 제공합니다. 이러한 확장은 다양한 방식으로 쿠버네티스 클러스터의 성능에 큰 영향을 미칠 수 있다. 예를 들어 FailurePolicy=Ignore가 포함된 어드미션 웹훅은 웹훅 타겟을 사용할 수 없는 경우 K8s API 요청에 지연 시간을 추가할 수 있다. 쿠버네티스 확장성 SIG는 "you promise, we promise" 프레임워크를 사용하여 확장성을 정의합니다.

+
+

사용자가 다음과 같이 약속하는 경우 (You promise):
+ - 클러스터를 올바르게 구성하세요
+ - 확장성 기능을 “합리적으로” 사용
+ - 클러스터의 부하를 권장 리밋 이내로 유지

+

그러면 클러스터가 확장될 것을 약속합니다. (We promise):
+ - 모든 SLO가 만족합니다.

+
+

쿠버네티스 SLO

+

쿠버네티스 SLO는 워커 노드 스케일링이나 어드미션 웹훅과 같이 클러스터에 영향을 미칠 수 있는 모든 플러그인과 외부 제한을 고려하지 않습니다. 이러한 SLO는 쿠버네티스 컴포넌트에 초점을 맞추고 쿠버네티스 액션과 리소스가 기대 범위 내에서 작동하도록 합니다. SLO는 쿠버네티스 개발자가 쿠버네티스 코드를 변경해도 전체 시스템의 성능이 저하되지 않도록 하는 데 도움이 됩니다.

+

쿠버네티스 확장성 SIG는 다음과 같은 공식 SLO/SLI를 정의합니다. Amazon EKS 팀은 이러한 SLO/SLI에 대해 EKS 클러스터에서 정기적으로 확장성 테스트를 실행하여 변경 및 새 버전 출시에 따른 성능 저하를 모니터링합니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ObjectiveDefinitionSLO
API request latency (mutating)모든 (리소스, 동사) 쌍의 단일 객체에 대한 변경 API 호출 처리 지연 (지난 5분 동안 백분위 99로 측정)기본 Kubernetes 설치에서 모든 (리소스, 동사) 쌍에 대해 (가상 및 집계 리소스 정의 제외), 클러스터당 백분위 99 <= 1초
API request latency (read-only)모든 (리소스, 범위) 쌍에 대한 비스트리밍 읽기 전용 API 호출 처리 대기 시간 (지난 5분 동안 백분위 99로 측정)기본 Kubernetes 설치에서 모든 (리소스, 범위) 쌍에 대해 (가상 및 집계 리소스 및 사용자 지정 리소스 정의 제외), 클러스터당 백분위 99: (a) <= scope=resource인 경우 1초 (b) << = 30초 (scope=namespace 또는 scope=cluster인 경우)
Pod startup latency예약 가능한 상태 비저장 파드의 시작 지연 시간 (이미지를 가져오고 초기화 컨테이너를 실행하는 데 걸리는 시간 제외), 파드 생성 타임스탬프부터 모든 컨테이너가 시작 및 시계를 통해 관찰된 것으로 보고되는 시점까지 측정 (지난 5분 동안 백분위 99로 측정)기본 쿠버네티스 설치에서 클러스터당 백분위 99 <= 5초
+ + +

API 요청 지연 시간

+

kube-apiserver에는 기본적으로 --request-timeout1m0s로 정의되어 있습니다. 즉, 요청을 최대 1분 (60초) 동안 실행한 후 제한 시간을 초과하여 취소할 수 있습니다. 지연 시간에 대해 정의된 SLO는 실행 중인 요청 유형별로 구분되며, 변경되거나 읽기 전용일 수 있습니다.

+

뮤테이팅 (Mutating)

+

쿠버네티스에서 요청을 변경하면 생성, 삭제 또는 업데이트와 같은 리소스가 변경됩니다. 이러한 요청은 업데이트된 오브젝트가 반환되기 전에 변경 사항을 etcd 백엔드에 기록해야 하기 때문에 비용이 많이 든다. Etcd는 모든 쿠버네티스 클러스터 데이터에 사용되는 분산 키-밸류 저장소입니다.

+

이 지연 시간은 쿠버네티스 리소스의 (resource, verb) 쌍에 대한 5분 이상의 99번째 백분위수로 측정됩니다. 예를 들어 이 지연 시간은 파드 생성 요청과 업데이트 노드 요청의 지연 시간을 측정합니다. SLO를 충족하려면 요청 지연 시간이 1초 미만이어야 합니다.

+

읽기 전용 (read-only)

+

읽기 전용 요청은 단일 리소스 (예: Pod X 정보 가져오기) 또는 컬렉션 (예: “네임스페이스 X에서 모든 파드 정보 가져오기”) 을 검색한다. kube-apiserver는 오브젝트 캐시를 유지하므로 요청된 리소스가 캐시에서 반환될 수도 있고, 먼저 etcd에서 검색해야 할 수도 있다. +이러한 지연 시간은 5분 동안의 99번째 백분위수로도 측정되지만, 읽기 전용 요청은 별도의 범위를 가질 수 있습니다.SLO는 두 가지 다른 목표를 정의합니다.

+
    +
  • 단일 리소스(예: kubectl get pod -n mynamespace my-controller-xxx)에 대한 요청의 경우 요청 지연 시간은 1초 미만으로 유지되어야 합니다.
  • +
  • 네임스페이스 또는 클러스터의 여러 리소스에 대해 요청한 경우 (예: kubectl get pods -A) 지연 시간은 30초 미만으로 유지되어야 합니다.
  • +
+

Kubernetes 리소스 목록에 대한 요청은 요청에 포함된 모든 오브젝트의 세부 정보가 SLO 내에 반환될 것으로 예상하기 때문에 SLO는 요청 범위에 따라 다른 목표 값을 가집니다. 대규모 클러스터 또는 대규모 리소스 컬렉션에서는 응답 크기가 커져 반환하는 데 다소 시간이 걸릴 수 있습니다. 예를 들어 수만 개의 파드를 실행하는 클러스터에서 JSON으로 인코딩할 때 각 파드가 대략 1KiB인 경우 클러스터의 모든 파드를 반환하는 데 10MB 이상이 된다. 쿠버네티스 클라이언트는 이러한 응답 크기를 줄이는 데 도움이 될 수 있다 APIListChunking을 사용하여 대규모 리소스 컬렉션을 검색.

+

파드 시작 지연

+

이 SLO는 주로 파드 생성부터 해당 파드의 컨테이너가 실제로 실행을 시작할 때까지 걸리는 시간과 관련이 있다. 이를 측정하기 위해 파드에 기록된 생성 타임스탬프와 파드 WATCH요청에서 보고된 컨테이너가 시작된 시점 (컨테이너 이미지 풀링 및 초기화 컨테이너 실행 시간 제외)과의 차이를 계산합니다. SLO를 충족하려면 이 파드 시작 지연 시간의 클러스터 일당 99번째 백분위수를 5초 미만으로 유지해야 한다.

+

참고로, 이 SLO에서는 워커 노드가 이 클러스터에 이미 존재하며 파드를 스케줄링할 준비가 된 상태인 것으로 가정한다. 이 SLO는 이미지 풀이나 초기화 컨테이너 실행을 고려하지 않으며, 영구 스토리지 플러그인을 활용하지 않는 “스테이트리스(stateless) 파드"로만 테스트를 제한한다.

+

쿠버네티스 SLI 메트릭스

+

또한 쿠버네티스는 시간이 지남에 따라 이러한 SLI를 추적하는 쿠버네티스 컴포넌트에 프로메테우스 메트릭을 추가하여 SLI에 대한 옵저버빌리티를 개선하고 있습니다. 프로메테우스 쿼리 언어 (PromQL)를 사용하여 Prometheus 또는 Grafana 대시보드와 같은 도구에서 시간 경과에 따른 SLI 성능을 표시하는 쿼리를 작성할 수 있습니다. 아래는 위의 SLO에 대한 몇 가지 예입니다.

+

API 서버 요청 레이턴시

+ + + + + + + + + + + + + + + + + +
MetricDefinition
apiserver_request_sli_duration_seconds각 verb, 그룹, 버전, 리소스, 하위 리소스, 범위 및 구성 요소에 대한 응답 지연 시간 분포 (웹훅 지속 시간, 우선 순위 및 공정성 대기열 대기 시간 제외)
apiserver_request_duration_seconds각 verb, 테스트 실행 값, 그룹, 버전, 리소스, 하위 리소스, 범위 및 구성 요소에 대한 응답 지연 시간 분포 (초)
+

참고: apiserver_request_sli_duration_seconds 메트릭은 쿠버네티스 1.27 버전부터 사용할 수 있다.

+

이러한 메트릭을 사용하여 API 서버 응답 시간과 Kubernetes 구성 요소 또는 기타 플러그인/구성 요소에 병목 현상이 있는지 조사할 수 있습니다. 아래 쿼리는 커뮤니티 SLO 대시보드를 기반으로 합니다.

+

API 요청 레이턴시 SLI (mutating) - 해당 시간은 웹훅 실행 또는 대기열 대기 시간을 포함하지 않습니다.
+histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"CREATE|DELETE|PATCH|POST|PUT", subresource!~"proxy|attach|log|exec|portforward"}[5m])) by (resource, subresource, verb, scope, le)) > 0

+

API 요청 레이턴시 시간 합계 (mutating) - API 서버에서 요청이 소요된 총 시간입니다. 이 시간은 웹훅 실행, API 우선 순위 및 공정성 대기 시간을 포함하므로 SLI 시간보다 길 수 있습니다.
+histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"CREATE|DELETE|PATCH|POST|PUT", subresource!~"proxy|attach|log|exec|portforward"}[5m])) by (resource, subresource, verb, scope, le)) > 0

+

이 쿼리에서는 kubectl port-forward 또는 kubectl exec 요청과 같이 즉시 반환되지 않는 스트리밍 API 요청을 제외합니다. (subresource!~"proxy|attach|log|exec|portforward"). 그리고 객체를 수정하는 쿠버네티스 verb에 대해서만 필터링하고 있습니다 (verb=~"Create|Delete|Patch|Post|put").그런 다음 지난 5분 동안의 해당 지연 시간의 99번째 백분위수를 계산합니다.

+

읽기 전용 API 요청에도 비슷한 쿼리를 사용할 수 있습니다. 필터링 대상 verb에 읽기 전용 작업 LISTGET이 포함되도록 수정하기만 하면 됩니다. 또한 요청 범위(예: 단일 리소스를 가져오거나 여러 리소스를 나열하는 경우)에 따라 SLO 임계값도 다릅니다.

+

API 요청 레이턴시 시간 SLI (읽기 전용) - 이번에는 웹훅 실행 또는 대기열 대기 시간을 포함하지 않습니다. +단일 리소스의 경우 (범위=리소스, 임계값=1s)
+histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"GET", scope=~"resource"}[5m])) by (resource, subresource, verb, scope, le))

+

동일한 네임스페이스에 있는 리소스 컬렉션의 경우 (범위=네임스페이스, 임계값=5s)
+histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"LIST", scope=~"namespace"}[5m])) by (resource, subresource, verb, scope, le))

+

전체 클러스터의 리소스 컬렉션의 경우 (범위=클러스터, 임계값=30초)
+histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"LIST", scope=~"cluster"}[5m])) by (resource, subresource, verb, scope, le))

+

**API 요청 레이턴시 시간 합계 (읽기 전용) ** - API 서버에서 요청이 소요된 총 시간입니다. 이 시간은 웹훅 실행 및 대기 시간을 포함하므로 SLI 시간보다 길 수 있습니다. +단일 리소스의 경우 (범위=리소스, 임계값=1초)
+histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"GET", scope=~"resource"}[5m])) by (resource, subresource, verb, scope, le))

+

동일한 네임스페이스에 있는 리소스 컬렉션의 경우 (범위=네임스페이스, 임계값=5s)
+histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"LIST", scope=~"namespace"}[5m])) by (resource, subresource, verb, scope, le))

+

전체 클러스터의 리소스 모음의 경우 (범위=클러스터, 임계값=30초)
+histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"LIST", scope=~"cluster"}[5m])) by (resource, subresource, verb, scope, le))

+

SLI 메트릭은 요청이 API Priority 및 Fairness 대기열에서 대기하거나, 승인 웹훅 또는 기타 쿠버네티스 확장을 통해 작업하는 데 걸리는 시간을 제외함으로써 쿠버네티스 구성 요소의 성능에 대한 통찰력을 제공합니다. 전체 지표는 애플리케이션이 API 서버의 응답을 기다리는 시간을 반영하므로 보다 총체적인 시각을 제공합니다. 이러한 지표를 비교하면 요청 처리 지연이 발생하는 위치를 파악할 수 있습니다.

+

파드 시작 레이턴시

+ + + + + + + + + + + + + + + + + +
MetricDefinition
kubelet_pod_start_sli_duration_seconds파드를 시작하는 데 걸리는 시간 (초) (이미지를 가져오고 초기화 컨테이너를 실행하는 데 걸리는 시간 제외), 파드 생성 타임스탬프부터 모든 컨테이너가 시계를 통해 시작 및 관찰된 것으로 보고될 때까지의 시간
kubelet_pod_start_duration_secondskubelet이 파드를 처음 본 시점부터 파드가 실행되기 시작할 때까지의 시간(초). 여기에는 파드를 스케줄링하거나 워커 노드 용량을 확장하는 시간은 포함되지 않는다.
+

참고: kubelet_pod_start_sli_duration_seconds는 쿠버네티스 1.27부터 사용할 수 있다.

+

위의 쿼리와 마찬가지로 이러한 메트릭을 사용하여 노드 스케일링, 이미지 풀 및 초기화 컨테이너가 Kubelet 작업과 비교하여 파드 출시를 얼마나 지연시키는지 파악할 수 있습니다.

+

파드 시작 레이턴시 시간 SLI - 이것은 파드 생성부터 애플리케이션 컨테이너가 실행 중인 것으로 보고된 시점까지의 시간입니다. 여기에는 워커 노드 용량을 사용할 수 있고 파드를 스케줄링하는 데 걸리는 시간이 포함되지만, 이미지를 가져오거나 초기화 컨테이너를 실행하는 데 걸리는 시간은 포함되지 않습니다.
+histogram_quantile(0.99, sum(rate(kubelet_pod_start_sli_duration_seconds_bucket[5m])) by (le))

+

파드 시작 레이턴시 시간 합계 - kubelet이 처음으로 파드를 시작하는 데 걸리는 시간입니다. 이는 kubelet이 WATCH를 통해 파드를 수신한 시점부터 측정되며, 워커 노드 스케일링 또는 스케줄링에 걸리는 시간은 포함되지 않는다. 여기에는 이미지를 가져오고 실행할 컨테이너를 초기화하는 데 걸리는 시간이 포함됩니다.
+histogram_quantile(0.99, sum(rate(kubelet_pod_start_duration_seconds_bucket[5m])) by (le))

+

클러스터의 SLO

+

EKS 클러스터의 쿠버네티스 리소스에서 Prometheus 메트릭을 수집하면 쿠버네티스 컨트롤 플레인 구성 요소의 성능에 대한 심층적인 통찰력을 얻을 수 있습니다.

+

perf-test repo에는 테스트 중 클러스터의 지연 시간 및 중요 성능 메트릭을 표시하는 Grafana 대시보드가 포함되어 있습니다. perf 테스트 구성은 쿠버네티스 메트릭을 수집하도록 구성된 오픈 소스 프로젝트인 kube-prometheus-stack을 활용하지만 Amazon Managed Prometheus 및 Amazon Managed Grafana 사용도 가능합니다.

+

kube-prometheus-stack 또는 유사한 프로메테우스 솔루션을 사용하는 경우 동일한 대시보드를 설치하여 클러스터의 SLO를 실시간으로 관찰할 수 있습니다.

+
    +
  1. 먼저 kubectl apply -f prometheus-rules.yaml로 대시보드에서 사용되는 프로메테우스 규칙을 설치해야 한다. 여기에서 규칙 사본을 다운로드할 수 있습니다: https://github.com/kubernetes/perf-tests/blob/master/clusterloader2/pkg/prometheus/manifests/prometheus-rules.yaml
      +
    1. 파일의 네임스페이스가 사용자 환경과 일치하는지 확인하세요.
    2. +
    3. kube-prometheus-stack을 사용하는 경우 레이블이 Prometheus.PrometheusSpec.RuleSelector 헬름 값과 일치하는지 확인하세요.
    4. +
    +
  2. +
  3. 그런 다음 Grafana에 대시보드를 설치할 수 있습니다. 이를 생성하는 json 대시보드와 파이썬 스크립트는 다음에서 확인할 수 있습니다: https://github.com/kubernetes/perf-tests/tree/master/clusterloader2/pkg/prometheus/manifests/dashboards
      +
    1. slo.json 대시보드는 쿠버네티스 SLO와 관련된 클러스터의 성능을 보여줍니다.
    2. +
    +
  4. +
+

SLO는 클러스터의 Kubernetes 구성 요소 성능에 초점을 맞추고 있지만 클러스터에 대한 다양한 관점이나 통찰력을 제공하는 추가 메트릭을 검토할 수 있습니다. Kube-State-Metrics와 같은 쿠버네티스 커뮤니티 프로젝트는 클러스터의 추세를 빠르게 분석하는 데 도움이 될 수 있습니다. 쿠버네티스 커뮤니티에서 가장 많이 사용되는 플러그인과 드라이버도 Prometheus 메트릭을 내보내므로 오토스케일러 또는 사용자 지정 스케줄러와 같은 사항을 조사할 수 있습니다.

+

옵저버빌리티 모범 사례 가이드에는 추가 통찰력을 얻는 데 사용할 수 있는 다른 쿠버네티스 메트릭의 예가 나와 있습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/node_efficiency/index.html b/ko/scalability/docs/node_efficiency/index.html new file mode 100644 index 000000000..8f8aa4541 --- /dev/null +++ b/ko/scalability/docs/node_efficiency/index.html @@ -0,0 +1,2540 @@ + + + + + + + + + + + + + + + + + + + + + + + 노드 및 워크로드 효율성 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

노드 및 워크로드 효율성

+

워크로드와 노드를 효율적으로 사용하면 복잡성과 비용을 줄이는 동시에 성능과 규모를 높일 수 있습니다. 이런 효율성을 계획할 때는 여러 가지 요소를 고려해야 하며, 각 기능에 대한 하나의 모범 사례 설정과 절충점을 기준으로 생각하는 것이 가장 쉽습니다. 다음 섹션에서 이런 장단점을 자세히 살펴보겠습니다.

+

노드 선택

+

약간 큰 노드 크기(4-12xlarge) 를 사용하면 시스템 구성 요소의 데몬셋(DaemonSets)Reserves와 같은 "오버헤드"에 사용되는 노드의 비율이 줄어들기 때문에 파드를 실행하는 데 사용할 수 있는 공간이 늘어납니다. 아래 다이어그램에서 2xlarge 시스템의 사용 가능한 공간과 적당한 수의 데몬셋을 포함하는 8xlarge 시스템의 사용 가능한 공간 차이를 확인할 수 있습니다.

+
+

Note

+

K8s는 일반적으로 수평으로 확장되므로 대부분의 애플리케이션에서 NUMA 크기 노드의 성능 영향을 고려하는 것이 합리적이지 않으므로 해당 노드 크기보다 낮은 범위를 권장합니다.

+
+

Node size

+

노드 크기가 크면 노드당 사용 가능한 공간 비율을 높일 수 있습니다. 하지만 이 모델은 노드를 너무 많이 파드로 가득 채워 오류를 일으키거나 노드를 포화 상태로 만들면 극단적인 상황까지 갈 수 있습니다. 더 큰 노드 크기를 성공적으로 사용하려면 노드 포화 상태를 모니터링하는 것이 중요합니다.

+

노드 선택은 모든 경우에 적용되는 것은 아닙니다. 이탈률이 극적으로 다른 워크로드를 서로 다른 노드 그룹으로 분할하는 것이 가장 좋은 경우가 많습니다. 이탈률이 높은 소규모 배치 워크로드에는 4xlarge 인스턴스 패밀리가 가장 적합하고, 8vCPU를 사용하고 이탈률이 낮은 Kafka와 같은 대규모 애플리케이션은 12xlarge 제품군이 더 적합합니다.

+

Churn rate

+
+

Tip

+

노드 크기가 매우 클 경우 고려해야 할 또 다른 요소는 CGROUPS가 컨테이너화된 애플리케이션에서 vCPU의 총 수를 숨기지 않기 때문입니다. 동적 런타임으로 인해 의도하지 않은 수의 OS 스레드가 생성되어 지연 시간이 발생하여 문제를 해결하기 어려운 경우가 많습니다. 이런 애플리케이션의 경우 CPU 피닝(pinning)을 사용하는 것이 좋습니다. 주제에 대해 더 자세히 알아보려면 다음 비디오를 참고하세요. https://www.youtube.com/watch?v=NqtfDy_KAqg

+
+

노드 빈패킹(Bin-packing)

+

쿠버네티스 vs. 리눅스 규칙

+

쿠버네티스에서 워크로드를 다룰 때 염두에 두어야 할 두 가지 규칙 세트가 있습니다. 요청 값을 사용하여 노드에서 파드를 스케줄링하는 쿠버네티스 스케줄러의 규칙과 파드가 스케줄링된 후 일어나는 일은 쿠버네티스가 아닌 리눅스의 영역입니다.

+

쿠버네티스 스케줄러가 완료된 후에는 새로운 규칙 집합이 인계되는데, 이는 리눅스 Completely Fair Scheduler (CFS)입니다. 주요 포인트는 리눅스 CFS가 코어라는 개념을 가지고 있지 않다는 것입니다. 우리는 코어로 생각하는 것이 스케일에 최적화된 작업 부하를 주요 문제로 이어질 수 있는 이유에 대해 논의할 것입니다.

+

코어로 생각하기

+

쿠버네티스 스케줄러가 코어라는 개념을 가지고 있기 때문에 혼란이 시작됩니다. 쿠버네티스 스케줄러 관점에서 코어 요청이 1로 설정된 4개의 NGINX 파드가 있는 노드를 살펴보면, 노드는 다음과 같이 보일 것입니다.

+

+

그러나, 리눅스 CFS 관점에서 이것이 얼마나 다르게 보이는지에 대한 생각 실험을 해보겠습니다. 리눅스 CFS 시스템을 사용할 때 기억해야 할 가장 중요한 것은 바쁜 컨테이너들(CGROUPS)만이 공유 시스템에 포함되는 유일한 컨테이너들이라는 것입니다. 이 경우, 첫 번째 컨테이너만이 바쁘므로 노드의 모든 4개의 코어를 사용할 수 있습니다.

+

+

이것이 왜 중요한가요? 개발 클러스터에서 성능 테스팅을 실행하였고, NGINX 애플리케이션이 해당 노드에서 유일하게 바쁜 컨테이너였다고 가정해봅시다. 우리가 앱을 프로덕션으로 옮기면 다음과 같은 일이 발생할 것입니다. NGINX 애플리케이션은 4 vCPU의 자원을 원하지만, 노드의 다른 모든 파드가 바쁜 상태이기 때문에 앱의 성능이 제한됩니다.

+

+

이런 상황은 우리 애플리케이션의 "스위트 스팟(sweet spot)"까지 스케일을 허용하지 않기 때문에 불필요하게 더 많은 컨테이너를 추가하게 될 것입니다. 이 "스위트 스팟"이라는 중요한 개념을 좀 더 자세히 살펴보겠습니다.

+

애플리케이션 적정 크기

+

각 응용 프로그램에는 더 이상 트래픽을 처리할 수 없는 특정 지점이 있습니다. 이 지점을 초과하면 처리 시간이 늘어나고 이 시점을 훨씬 넘으면 트래픽이 감소할 수도 있습니다. 이를 애플리케이션의 포화 지점이라고 합니다. 크기 조정 문제를 방지하려면 응용 프로그램이 포화 지점에 도달하기 이전에 응용 프로그램 크기를 조정해야 합니다. 이 지점을 스위트 스팟이라고 부르겠습니다.

+

The sweet spot

+

스위트 스팟을 이해하려면 각 애플리케이션을 테스트해야 합니다.애플리케이션마다 다르기 때문에 여기서는 보편적인 지침을 제공할 수 없습니다. 이 테스트에서는 애플리케이션 포화점을 보여주는 최상의 지표를 파악하기 위해 노력하고 있습니다. 사용률 지표는 애플리케이션이 포화 상태임을 나타내는 데 사용되는 경우가 많지만, 이로 인해 규모 조정 문제가 빠르게 발생할 수 있습니다(이 주제에 대해서는 이후 섹션에서 자세히 설명하겠습니다). 이 "스위트 스팟"을 확보하면 이를 사용하여 워크로드를 효율적으로 확장할 수 있습니다.

+

반대로, 스위트 스팟보다 훨씬 먼저 규모를 확장하여 불필요한 파드를 만들면 어떻게 될까요?다음 섹션에서 살펴보도록 하겠습니다.

+

Pod 확산

+

불필요한 파드를 만들면 어떻게 빠르게 제어가 힘들어질 수 있는지 보기 위해, 왼쪽의 첫 번째 예제를 살펴보겠습니다. 이 컨테이너의 올바른 수직 스케일은 초당 100개의 요청을 처리할 때 약 2개의 vCPU 사용량을 차지합니다. 하지만 요청을 하프 코어로 설정하여 요청 값을 과소 프로비저닝하려면 이제 실제로 필요한 파드 하나당 파드 4개가 필요합니다. 이 문제를 더욱 악화시키는 것은 HPA를 기본값인 50% CPU로 설정하면 해당 파드가 절반만 비어 있는 상태로 확장되어 8:1 비율이 된다는 점입니다.

+

+

이 문제를 확대해보면 이 문제가 어떻게 해결될 수 있는지 금방 알 수 있습니다. 스위트 스팟이 잘못 설정된 10개의 파드를 배포하면 빠르게 80개의 파드로 늘어나고 이를 실행하는 데 필요한 추가 인프라가 생길 수 있습니다.

+

+

애플리케이션이 최적의 위치에서 작동하지 않도록 하는 것이 미치는 영향을 이해했으니 이제 노드 수준으로 돌아가서 쿠버네티스 스케줄러와 리눅스 CFS 간의 이런 차이가 왜 그렇게 중요한지 물어보도록 하겠습니다.

+

HPA로 스케일링을 확장하고 축소할 때, 더 많은 파드를 할당할 많은 공간이 있는 시나리오가 있을 수 있습니다. 이것은 좋지 않은 결정일 것입니다. 왼쪽에 그려진 노드는 이미 100% CPU 사용률에 있기 때문입니다. 현실적이지는 않지만 이론적으로 가능한 시나리오에서는 우리 노드가 완전히 가득 차 있지만, CPU 사용률은 0%일 수 있습니다.

+

+

Requests 설정

+

request 값을 해당 애플리케이션의 "스위트 스팟" 값으로 설정하고 싶을 수도 있지만, 이렇게 하면 아래 다이어그램과 같이 비효율성이 발생할 수 있습니다. 여기서는 요청 값을 2vCPU로 설정했지만 이 파드들의 평균 사용률은 대부분의 시간 동안 1 CPU만 실행됩니다. 이 설정은 CPU 주기의 50%를 낭비하게 되어, 이는 허용할 수 없습니다.

+

+

이것은 문제에 대한 복잡한 답변을 가져옵니다. 컨테이너 활용은 진공 상태에서는 생각할 수 없습니다. 노드에서 실행되는 다른 애플리케이션을 고려해야 합니다. 음 예에서는 성질상 버스티한 컨테이너들이 메모리에 제약이 있을 수 있는 두 개의 낮은 CPU 사용 컨테이너와 섞여 있습니다. 이렇게 하면 노드에 부담을 주지 않고도 컨테이너가 스위트 스팟에 도달할 수 있습니다.

+

+

이 모든 것에서 얻을 수 있는 중요한 개념은, 리눅스 컨테이너 성능을 이해하기 위해 쿠버네티스 스케줄러의 코어 개념을 사용하는 것이 그들 사이에 관련이 없기 때문에 잘못된 결정을 내릴 수 있다는 것입니다.

+
+

Tip

+

리눅스 CFS는 그 자체로 강점이 있습니다. 이는 I/O 기반 작업 부하에 대해 특히 그렇습니다. 그러나 애플리케이션이 사이드카 없이 전체 코어를 사용하고 I/O 요구사항이 없다면, CPU 피닝은 이 과정에서 많은 복잡성을 제거할 수 있으며, 그런 사항들로 인해 권장됩니다.

+
+

이용률(Utilization) vs. 포화도(Saturation)

+

애플리케이션 스케일링에서 흔히 발생하는 실수는 스케일링 지표에 CPU 사용률만 사용하는 것입니다. 복잡한 애플리케이션에서 이는 애플리케이션이 실제로 요청으로 가득 차 있다는 잘못된 지표인 경우가 거의 대부분입니다. 왼쪽 예에서는 모든 요청이 실제로 웹 서버에 도달하고 있는 것을 볼 수 있으므로 CPU 사용률이 포화 상태에서도 잘 추적되고 있습니다.

+

실제 응용 프로그램에서는 이런 요청 중 일부가 데이터베이스 계층이나 인증 계층 등에서 처리될 가능성이 높지만, 이 보다 일반적인 경우에는 다른 엔티티에서 요청을 처리하고 있기 때문에 CPU가 포화 상태로 추적되지 않습니다. 이 경우 CPU는 포화도를 제대로 나타내지 못합니다.

+

+

쿠버네티스에서 불필요하고 예측할 수 없는 스케일링이 발생하는 가장 큰 이유는 애플리케이션 성능에서 잘못된 지표를 사용하는 것입니다.사용 중인 애플리케이션 유형에 맞는 올바른 채도 지표를 선택할 때는 각별한 주의를 기울여야 합니다.한 가지 주의할 점은 모든 사람에게 맞는 한 가지 권장 사항이 없다는 것입니다.사용되는 언어와 해당 애플리케이션 유형에 따라 채도에 대한 다양한 지표가 있습니다.

+

이 문제는 CPU 사용률에만 국한된 것으로 생각할 수도 있지만, 초당 요청 수와 같은 다른 일반적인 지표도 위에서 설명한 것과 똑같은 문제에 빠질 수 있습니다. 참고로 요청은 웹 서버에서 직접 처리되지 않는 DB 계층, 인증 계층에도 전달될 수 있으므로 웹 서버 자체의 실제 채도를 평가하는 지표로는 부족합니다.

+

+

안타깝게도 올바른 채도 측정법을 선택하는 데 있어 쉬운 해답은 없습니다. 고려해야 할 몇 가지 지침은 다음과 같습니다.

+
    +
  • 언어 런타임을 이해하세요. 여러 OS 스레드가 있는 언어는 단일 스레드 응용 프로그램과 다르게 반응하므로 노드에 미치는 영향이 다릅니다.
  • +
  • 올바른 수직 스케일을 이해하세요. 새 파드를 스케일링하기 전에 애플리케이션의 수직 스케일에 얼마나 많은 버퍼를 적용하길 원하시나요?
  • +
  • 애플리케이션의 채도를 실제로 반영하는 지표는 무엇인가요? - Kafka Producer의 포화 지표는 복잡한 웹 애플리케이션과는 상당히 다릅니다.
  • +
  • 노드의 다른 모든 애플리케이션은 서로 어떤 영향을 미치나요? - 애플리케이션 성능은 진공 상태에서 이루어지지 않으므로 노드의 다른 워크로드가 큰 영향을 미칩니다.
  • +
+

이 섹션을 마무리하며, 위의 내용을 과도하게 복잡하고 불필요하다고 생각하기 쉽습니다. 종종 우리는 문제를 겪고 있지만, 잘못된 지표를 보고 있기 때문에 문제의 진정한 성격을 모르고 있을 수 있습니다. 다음 섹션에서는 어떻게 이런 일이 일어날 수 있는지 살펴보겠습니다.

+

노드 포화도

+

이제 애플리케이션 포화에 대해 살펴보았으니 노드 관점에서 이와 동일한 개념을 살펴보겠습니다. 사용률이 100% 인 두 개의 CPU를 예로 들어 사용률과 포화도 간의 차이를 확인해 보겠습니다.

+

왼쪽의 vCPU는 100% 사용되지만 이 vCPU에서 실행되기를 기다리는 다른 작업은 없습니다. 따라서 순전히 이론적인 의미에서는 상당히 효율적입니다. 한편, 두 번째 예에서는 20개의 단일 스레드 애플리케이션이 vCPU에서 처리되기를 기다리고 있습니다. 이제 20개 애플리케이션 모두 vCPU에서 순서가 처리되기를 기다리는 동안 어느 정도의 지연 시간이 발생합니다. 즉, 오른쪽에 있는 vCPU가 포화 상태입니다.

+

사용률만 보면 이 문제가 발생하지 않을 뿐만 아니라 네트워킹과 같이 관련이 없는 문제가 지연되어 잘못된 길로 가게 될 수도 있습니다.

+

+

특정 시점에 노드에서 실행되는 총 파드 수를 늘릴 때는 사용률 지표뿐만 아니라 포화 지표를 보는 것이 중요합니다. 노드가 과포화 상태라는 사실을 쉽게 놓칠 수 있기 때문입니다. 이 작업에는 아래 차트에서 볼 수 있는 것처럼 압력 포화 정보 지표를 사용할 수 있습니다.

+

PromQL - Stalled I/O

+
topk(3, ((irate(node_pressure_io_stalled_seconds_total[1m])) * 100))
+
+

+
+

Note

+

압력 정체 지표에 대한 자세한 내용은 https://facebookmicrosites.github.io/psi/docs/overview *을 참조하십시오.

+
+

이 측정치를 통해 스레드가 CPU에서 대기 중인지, 아니면 모든 스레드가 메모리 또는 I/O와 같은 리소스를 기다리는 동안 중단되었는지를 알 수 있습니다. 예를 들어 인스턴스의 모든 스레드가 1분 동안 I/O를 기다리는 동안 중단된 비율을 확인할 수 있습니다.

+
topk(3, ((irate(node_pressure_io_stalled_seconds_total[1m])) * 100))
+
+

이 지표를 사용하면 위의 차트에서 박스상의 모든 스레드가 하이 워터마크에서 I/O를 기다릴 때 45% 의 시간 동안 중단된 것을 확인할 수 있습니다. 즉, 1분 동안 CPU 사이클을 모두 낭비하고 있다는 뜻입니다. 이런 일이 일어나고 있다는 것을 이해하면 상당한 양의 vCPU 시간을 회수하여 스케일링의 효율성을 높일 수 있습니다.

+

HPA V2

+

HPA API의 autoscaling/v2 버전을 사용하는 것이 좋습니다. 이전 버전의 HPA API는 특정 엣지 케이스에서 크기 조정이 중단될 수 있습니다. 또한 각 확장 단계에서 파드가 두 배만 증가하는 것으로 제한되었으므로 소규모 배포에서는 빠르게 확장해야 하는 문제가 발생했습니다.

+

autoscaling/v2를 사용하면 규모를 확대하기 위한 여러 기준을 더 유연하게 포함할 수 있으며, 사용자 지정 및 외부 지표 (K8s 지표가 아님) 을 사용할 때 유연성이 크게 향상되었습니다.

+

예를 들어 세 가지 값 중 가장 높은 값을 기준으로 확장할 수 있습니다(아래 참조). 모든 파드의 평균 사용률이 50% 를 넘거나, 커스텀 지표인 경우 인그레스 초당 패킷이 평균 1,000개를 초과하거나, 인그레스 오브젝트가 초당 요청 10,000건을 초과할 경우 규모를 조정합니다.

+
+

Note

+

이는 Auto Scaling API의 유연성을 보여주기 위한 것이므로 프로덕션 환경에서 문제를 해결하기 어려울 수 있는 지나치게 복잡한 규칙은 피하는 것이 좋습니다.

+
+
apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+  name: php-apache
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: php-apache
+  minReplicas: 1
+  maxReplicas: 10
+  metrics:
+  - type: Resource
+    resource:
+      name: cpu
+      target:
+        type: Utilization
+        averageUtilization: 50
+  - type: Pods
+    pods:
+      metric:
+        name: packets-per-second
+      target:
+        type: AverageValue
+        averageValue: 1k
+  - type: Object
+    object:
+      metric:
+        name: requests-per-second
+      describedObject:
+        apiVersion: networking.k8s.io/v1
+        kind: Ingress
+        name: main-route
+      target:
+        type: Value
+        value: 10k
+
+

하지만 복잡한 웹 애플리케이션에 이런 지표를 사용하는 것이 위험하다는 것을 알게 되었습니다. 이 경우 애플리케이션의 포화도와 사용률을 정확하게 반영하는 사용자 지정 또는 외부 지표를 사용하는 것이 더 나은 서비스를 제공할 수 있습니다. HPAV2는 모든 지표에 따라 확장할 수 있어 이를 가능하게 하지만, 사용하려면 해당 지표를 찾아서 쿠버네티스로 익스포트해야 합니다.

+

예를 들어, Apache에서 활성 스레드 대기열 수를 살펴볼 수 있습니다. 이렇게 하면 스케일링 프로파일이 "더 매끄럽게" 만들어지는 경우가 많습니다(이 용어에 대해서는 곧 설명하겠습니다). 스레드가 활성 상태이면 해당 스레드가 데이터베이스 계층에서 대기 중이든 로컬에서 요청을 처리하든 상관 없습니다. 모든 애플리케이션 스레드가 사용되고 있다면 애플리케이션이 포화 상태임을 나타내는 좋은 지표입니다.

+

이 스레드 고갈을 신호로 사용하여 완전히 사용 가능한 스레드 풀을 갖춘 새 파드를 만들 수 있습니다. 또한 트래픽이 많은 시기에 애플리케이션에서 흡수하려는 버퍼 크기를 제어할 수 있습니다. 예를 들어 총 스레드 풀이 10개인 경우 사용된 스레드 4개와 사용된 스레드 8개로 확장하면 애플리케이션을 확장할 때 사용할 수 있는 버퍼에 큰 영향을 미칠 수 있습니다. 부하가 심한 상태에서 빠르게 확장해야 하는 애플리케이션에는 4로 설정하는 것이 좋습니다. 시간이 지남에 따라 요청 수가 느리게 증가하는 대신 급격히 증가하므로 확장할 시간이 충분하다면 8로 설정하는 것이 리소스 활용에 더 효율적입니다.

+

+

스케일링과 관련하여 "매끄럽다"라는 용어는 무엇을 의미할까요? 아래 차트를 보면 CPU를 지표로 사용하고 있습니다 .이 디플로이먼트의 파드 수가 50개에서 최대 250개까지 짧은 기간에 급증하다가 다시 즉시 축소됩니다.이는 매우 비효율적인 스케일링이 클러스터 이탈의 주요 원인이라는 점입니다.

+

+

애플리케이션의 올바른 스위트 스팟(차트 중간 부분)을 반영하는 지표로 변경한 후 원활하게 확장할 수 있다는 점에 주목하세요. 이제 스케일링이 효율적이었으며 요청 설정을 조정하여 제공된 여유 공간에 맞게 파드를 완전히 확장할 수 있습니다. 이전에는 수백 개의 파드가 하던 작업을 이제는 소규모 파드 그룹이 수행하고 있습니다. 실제 데이터에 따르면 이것이 쿠버네티스 클러스터의 확장성을 결정하는 가장 중요한 요소입니다.

+

+

중요한 점은 CPU 사용률이 애플리케이션과 노드 성능의 한 차원에 불과하다는 것입니다. CPU 사용률을 노드와 애플리케이션의 유일한 상태 지표로 사용하면 확장성, 성능 및 비용 측면에서 문제가 발생하는데, 이는 모두 긴밀하게 연결된 개념입니다. 애플리케이션과 노드의 성능이 높을수록 확장해야 하는 양이 줄어들어 비용이 절감됩니다.

+

또한 특정 애플리케이션을 확장하기 위한 올바른 포화도 지표를 찾아 사용하면 해당 애플리케이션의 실제 병목 현상을 모니터링하고 경보를 설정할 수 있습니다. 이 중요한 단계를 건너뛰면 성능 문제에 대한 보고서를 이해하기가 불가능하지는 않더라도 이해하기 어려울 것입니다.

+

CPU 제한 설정

+

잘못 이해되고 있는 주제에 대해 이 섹션을 마무리하기 위해 CPU 제한에 대해 설명하겠습니다. 간단히 말해 한도는 100ms마다 재설정하는 카운터가 있는 컨테이너와 관련된 메타데이터입니다. 이를 통해 Linux는 100ms 기간 동안 특정 컨테이너가 노드 전체에서 사용한 CPU 리소스 수를 추적할 수 있습니다.

+

CPU limits

+

제한을 설정할 때 흔한 오류는 애플리케이션이 단일 스레드이며 할당된 vCPU에서만 실행된다고 가정하는 것입니다. 위 섹션에서 CFS는 코어를 할당하지 않으며, 실제로 대규모 스레드 풀을 실행하는 컨테이너는 기본적으로 사용 가능한 모든 vCPU를 스케줄링한다는 것을 배웠습니다.

+

64개의 OS 스레드가 64개의 사용 가능한 코어(리눅스 노드 관점에서)에서 실행되고 있다면, 그 64개의 코어에서 모두 실행된 시간을 합한 후 100ms 기간 동안 사용된 CPU 시간의 총계는 상당히 크게 될 것입니다. 이것은 가비지 수집 프로세스 중에만 발생할 수 있기 때문에 이런 것을 놓치기가 상당히 쉽습니다. 이것이 제한을 설정하기 전에 시간 동안 올바른 사용량을 보장하기 위해 지표를 사용해야 하는 이유입니다.

+

다행히도, 애플리케이션의 모든 스레드에서 얼마나 많은 vCPU가 사용되고 있는지 정확히 알 수 있는 방법이 있습니다. 이 목적으로 container_cpu_usage_seconds_total 지표를 사용할 것입니다.

+

스로틀링 로직이 매 100ms마다 발생하고 이 지표가 초당 지표이므로, 이 100ms 기간에 맞게 PromQL을 사용할 것입니다. 이 PromQL 명령문 작업에 대해 자세히 알아보려면 다음 블로그를 참조하십시오.

+

PromQL 쿼리:

+
topk(3, max by (pod, container)(rate(container_cpu_usage_seconds_total{image!="", instance="$instance"}[$__rate_interval]))) / 10
+
+

+

일단 우리가 올바른 가치를 가지고 있다고 생각되면, 프로덕션에서 제한을 설정할 수 있습니다. 그런 다음 예상치 못한 문제로 인해 애플리케이션이 병목 현상을 겪고 있는지 확인해야 합니다 container_cpu_throttled_seconds_total을 살펴보면 이 작업을 수행할 수 있습니다.

+
topk(3, max by (pod, container)(rate(container_cpu_cfs_throttled_seconds_total{image!=``""``, instance=``"$instance"``}[$__rate_interval]))) / 10
+
+

+

메모리

+

메모리 할당은 쿠버네티스 스케줄링 동작과 리눅스 CGroup 동작을 혼동하기 쉬운 또 다른 예입니다. CGroup v2가 리눅스에서 메모리를 처리하는 방식이 크게 변경되었고 쿠버네티스가 이를 반영하여 구문을 변경했기 때문에 이 주제는 좀 더 미묘한 주제입니다. 자세한 내용은 이 블로그를 참조하십시오.

+

CPU 요청과 달리 메모리 요청은 스케줄링 프로세스가 완료된 후 사용되지 않습니다. 이는 CGroup v1에서 CPU와 같은 방식으로 메모리를 압축할 수 없기 때문입니다. 그 결과 우리는 메모리 제한만을 가지게 되며, 이는 메모리 누수에 대한 실패로부터 파드를 완전히 종료시키는 것으로 설계되었습니다. 이는 전부 또는 아무것도 없는 스타일의 제안이지만, 이 문제를 해결하기 위한 새로운 방법이 제시되었습니다.

+

첫째, 컨테이너에 올바른 양의 메모리를 설정하는 것은 보이는 것만큼 간단하지 않다는 것을 이해하는 것이 중요합니다. 리눅스의 파일 시스템은 성능 향상을 위해 메모리를 캐시로 사용합니다. 이 캐시는 시간이 지남에 따라 성장하며, 얼마나 많은 메모리가 캐시에 좋지만 응용 프로그램 성능에 큰 영향을 미치지 않고 되찾을 수 있는지 알기 어렵습니다. 이는 종종 메모리 사용량을 잘못 해석하는 결과를 가져옵니다.

+

메모리를 "압축"하는 능력을 갖게 되었던 것은 CGroup v2의 주요 동기 중 하나였습니다. CGroup V2가 필요했던 이유에 대한 더 많은 역사를 알고 싶다면, LISA21에서 Chris Down의 발표를 참조하십시오. 그는 최소 메모리를 올바르게 설정할 수 없는 문제가 CGroup v2와 압력 정체 지표를 만드는 데에 이르게 한 원인 중 하나였다고 설명합니다.

+

다행히 쿠버네티스는 이제 requests.memory에서 memory.minmemory.high라는 개념을 갖게 되었습니다. 이렇게 하면 캐시된 메모리를 적극적으로 해제하여 다른 컨테이너가 사용할 수 있도록 할 수 있습니다. 컨테이너가 메모리 상한에 도달하면 커널은 해당 컨테이너의 메모리를 memory.min으로 설정된 값까지 적극적으로 회수할 수 있습니다. 따라서 노드에 메모리 압력이 가해질 때 유연성이 향상됩니다.

+

핵심 질문은 memory.min을 어떤 값으로 설정해야 하는가입니다. 여기서 메모리 압력 정체 지표가 작용합니다. 이런 지표를 사용하여 컨테이너 수준에서 메모리 "스래싱"을 감지할 수 있습니다. 그러면 fbtax와 같은 컨트롤러를 사용하여 이 메모리 스래싱을 찾아 memory.min의 올바른 값을 탐지하고, memory.min 값을 동적으로 이 설정으로 설정할 수 있습니다.

+

정리하기

+

이 섹션을 요약하면, 다음과 같은 개념을 혼동하기 쉽습니다:

+
    +
  • 이용률(Utilization)와 포화도(Saturation)
  • +
  • 리눅스 성능 규칙과 쿠버네티스 스케줄러 로직
  • +
+

이런 개념들을 구분하도록 많은 주의를 기울여야 합니다. 성능과 스케일은 깊은 수준에서 연결되어 있습니다. 불필요한 스케일링은 성능 문제를 일으키며, 그 결과 스케일링 문제를 초래합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/quotas/index.html b/ko/scalability/docs/quotas/index.html new file mode 100644 index 000000000..00b00ac8f --- /dev/null +++ b/ko/scalability/docs/quotas/index.html @@ -0,0 +1,2458 @@ + + + + + + + + + + + + + + + + + + + + + + + 알려진 제한 및 서비스 할당량 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

알려진 제한 및 서비스 할당량

+

Amazon EKS는 다양한 워크로드에 사용할 수 있고 광범위한 AWS 서비스와 상호 작용할 수 있습니다. 고객 워크로드에서도 비슷한 범위의 AWS 서비스 할당량(Quota) 및 확장성을 저하하는 기타 문제가 발생할 수 있습니다.

+

AWS 계정에는 기본 할당량(팀에서 요청할 수 있는 각 AWS 리소스 수의 상한선)이 있습니다. 각 AWS 서비스는 자체 할당량을 정의하며 할당량은 일반적으로 리전별로 다릅니다. 일부 할당량(Soft Limit)에 대해서는 증가를 요청할 수 있지만 다른 할당량(Hard Limit)은 늘릴 수 없습니다. 애플리케이션을 설계할 때는 이러한 값을 고려해야 합니다. 이러한 서비스 제한을 정기적으로 검토하고 애플리케이션 설계에 반영하는 것을 고려해 보십시오.

+

AWS 서비스 할당량 콘솔 또는 AWS CLI를 사용하여 계정의 사용량을 검토하고 할당량 증가 요청을 열 수 있습니다. 서비스 할당량에 대한 자세한 내용과 서비스 할당량 증가에 대한 추가 제한 또는 공지는 해당 AWS 서비스의 AWS 설명서를 참조하십시오.

+
+

Note

+

Amazon EKS 서비스 할당량에는 서비스 할당량이 나열되어 있으며 가능한 경우 증가를 요청할 수 있는 링크가 있습니다.

+
+

기타 AWS 서비스 할당량

+

EKS 고객이 다른 AWS 서비스에 대해 아래 나열된 할당량의 영향을 받는 것을 확인했습니다. 이들 중 일부는 특정 사용 사례 또는 구성에만 적용될 수 있지만, 솔루션이 확장됨에 따라 이러한 문제가 발생할 수 있는지 고려해 볼 수 있습니다. 할당량은 서비스별로 정리되어 있으며 각 할당량에는 L-XXXXXXXX 형식의 ID가 있습니다. 이 ID를 사용하여 AWS 서비스 할당량 콘솔에서 조회할 수 있습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ServiceQuota (L-xxxxx)ImpactID (L-xxxxx)default
IAMRoles per account계정의 클러스터 또는 IRSA 역할 수를 제한할 수 있습니다.L-FE177D641,000
IAMOpenId connect providers per account계정당 클러스터 수를 제한할 수 있습니다. IRSA는 OpenID Connect를 사용합니다.L-858F3967100
IAMRole trust policy lengthIRSA 사용 시 IAM 역할이 연결되는 클러스터 수를 제한할 수 있습니다.L-C07B4B0D2,048
VPCSecurity groups per network interface클러스터의 네트워킹 제어 또는 연결을 제한할 수 있습니다.L-2AFB92585
VPCIPv4 CIDR blocks per VPCEKS 워커 노드 수를 제한할 수 있습니다.L-83CA0A9D5
VPCRoutes per route table클러스터의 네트워킹 제어 또는 연결을 제한할 수 있습니다.L-93826ACB50
VPCActive VPC peering connections per VPC클러스터의 네트워킹 제어 또는 연결을 제한할 수 있습니다.L-7E9ECCDB50
VPCInbound or outbound rules per security group.클러스터의 네트워킹 제어 또는 연결을 제한할 수 있습니다. EKS의 일부 컨트롤러는 새 규칙을 생성합니다.L-0EA8095F50
VPCVPCs per Region계정당 클러스터 수 또는 클러스터의 네트워킹 제어 또는 연결을 제한할 수 있습니다.L-F678F1CE5
VPCInternet gateways per Region계정당 클러스터 수 또는 클러스터의 네트워킹 제어 또는 연결을 제한할 수 있습니다.L-A4707A725
VPCNetwork interfaces per RegionEKS 워커 노드 수를 제한하거나 또는 Impact EKS 컨트롤 플레인 스케일링/업데이트 활동에 영향을 줍니다.L-DF5E4CA35,000
VPCNetwork Address Usage계정당 클러스터 수 또는 클러스터의 네트워킹 제어 또는 연결을 제한할 수 있습니다.L-BB24F6E564,000
VPCPeered Network Address Usage계정당 클러스터 수 또는 클러스터의 네트워킹 제어 또는 연결을 제한할 수 있습니다.L-CD17FD4B128,000
ELBListeners per Network Load Balancer클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-57A373D650
ELBTarget Groups per Region클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-B22855CB3,000
ELBTargets per Application Load Balancer클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-7E6692B21,000
ELBTargets per Network Load Balancer클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-EEF1AD043,000
ELBTargets per Availability Zone per Network Load Balancer클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-B211E961500
ELBTargets per Target Group per Region클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-A0D0B8631,000
ELBApplication Load Balancers per Region클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-53DA6B9750
ELBClassic Load Balancers per Region클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-E9E9831D20
ELBNetwork Load Balancers per Region클러스터로의 트래픽 수신 제어를 제한할 수 있습니다.L-69A177A250
EC2Running On-Demand Standard (A, C, D, H, I, M, R, T, Z) instances (as a maximum vCPU count)EKS 워커 노드 수를 제한할 수 있습니다.L-1216C47A5
EC2All Standard (A, C, D, H, I, M, R, T, Z) Spot Instance Requests (as a maximum vCPU count)EKS 워커 노드 수를 제한할 수 있습니다.L-34B43A085
EC2EC2-VPC Elastic IPsNAT GW (및 VPC) 수를 제한할 수 있으며, 이로 인해 한 지역의 클러스터 수가 제한될 수 있습니다.L-0263D0A35
EBSSnapshots per Region스테이트풀 워크로드의 백업 전략을 제한할 수 있습니다.L-309BACF6100,000
EBSStorage for General Purpose SSD (gp3) volumes, in TiBEKS 워커 노드 또는 퍼시스턴트볼륨 스토리지의 수를 제한할 수 있습니다.L-7A658B7650
EBSStorage for General Purpose SSD (gp2) volumes, in TiBEKS 워커 노드 또는 퍼시스턴트볼륨 스토리지의 수를 제한할 수 있습니다.L-D18FCD1D50
ECRRegistered repositories클러스터의 워크로드 수를 제한할 수 있습니다.L-CFEB8E8D10,000
ECRImages per repository클러스터의 워크로드 수를 제한할 수 있습니다.L-03A36CE110,000
SecretsManagerSecrets per Region클러스터의 워크로드 수를 제한할 수 있습니다.L-2F66C23C500,000
+

AWS 요청 스로틀링

+

또한 AWS 서비스는 모든 고객이 성능을 유지하고 사용할 수 있도록 요청 조절을 구현합니다. 서비스 할당량과 마찬가지로 각 AWS 서비스는 자체 요청 제한 임계값을 유지합니다. 워크로드에서 대량의 API 호출을 빠르게 실행해야 하거나 애플리케이션에서 요청 제한 오류가 발견되면 해당 AWS 서비스 설명서를 검토하는 것이 좋습니다.

+

대규모 클러스터에서 또는 클러스터가 크게 확장되는 경우 EC2 네트워크 인터페이스 또는 IP 주소 프로비저닝과 관련된 EC2 API 요청에서 요청 조절이 발생할 수 있습니다. 아래 표에는 고객이 요청 스로틀링으로 인해 겪었던 몇 가지 API 작업이 나와 있습니다. +EC2 속도 제한 기본값 및 속도 제한 인상 요청 단계는 쓰로틀링에 관한 EC2 문서 에서 확인할 수 있습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Mutating ActionsRead-only Actions
AssignPrivateIpAddressesDescribeDhcpOptions
AttachNetworkInterfaceDescribeInstances
CreateNetworkInterfaceDescribeNetworkInterfaces
DeleteNetworkInterfaceDescribeSecurityGroups
DeleteTagsDescribeTags
DetachNetworkInterfaceDescribeVpcs
ModifyNetworkInterfaceAttributeDescribeVolumes
UnassignPrivateIpAddresses
+

기타 알려진 제한

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/scaling_theory/index.html b/ko/scalability/docs/scaling_theory/index.html new file mode 100644 index 000000000..77d7064e9 --- /dev/null +++ b/ko/scalability/docs/scaling_theory/index.html @@ -0,0 +1,2382 @@ + + + + + + + + + + + + + + + + + + + + + + + 확장에 대한 이론 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

쿠버네티스 스케일링 이론

+

노드 vs. 이탈률

+

흔히 쿠버네티스의 확장성을 논의할 때 우리는 단일 클러스터에 있는 노드 수로 이야기합니다. 흥미롭게도, 이것은 확장성을 이해하기 위한 가장 유용한 지표는 아닙니다. 예를 들어, 많지만 고정된 수의 파드가 있는 5,000노드 클러스터는 초기 설정 이후 컨트롤 플레인에 큰 부담을 주지 않을 것입니다. 하지만 1,000개의 노드 클러스터를 사용하여 1분 이내에 수명이 짧은 작업 10,000개를 만들려고 시도한다면 컨트롤 플레인에 지속적인 압박을 가할 것입니다.

+

단순히 노드 수를 사용하여 스케일링을 이해하는 것은 오해의 소지가 있습니다. 특정 기간 내에 발생하는 변화율의 관점에서 생각하는 것이 좋습니다(프로메테우스 쿼리가 일반적으로 기본적으로 사용하는 간격이므로 여기서는 5분 간격을 설명하겠습니다). 변화율의 관점에서 문제를 구성하면 원하는 규모에 도달하기 위해 무엇을 조정해야 할지 더 잘 알 수 있는 이유를 살펴보겠습니다.

+

초당 쿼리 수 생각하기

+

쿠버네티스는 쿠버네티스 체인의 다음 링크에 과부하가 걸리지 않도록 Kubelet, 스케쥴러, 컨트롤러 매니저, API 서버 등 각 구성 요소에 대해 여러 가지 보호 메커니즘을 갖추고 있습니다. 예를 들어, Kubelet에는 API 서버에 대한 호출을 일정 속도로 제한하는 플래그가 있습니다. 이런 보호 메커니즘은 항상 그런 것은 아니지만 일반적으로 초당 허용 쿼리 또는 QPS로 표현됩니다.

+

이런 QPS 설정을 변경할 때는 각별한 주의를 기울여야 합니다. Kubelet의 초당 쿼리와 같은 하나의 병목 현상을 제거하면 다른 다운스트림 구성 요소에도 영향을 미칩니다. 이로 인해 시스템이 일정 속도 이상으로 과부하를 겪을 수 있고 또 그렇게 될 것이므로, 서비스 체인의 각 부분을 이해하고 모니터링하는 것이 쿠버네티스에서 워크로드를 성공적으로 확장하는 데 중요합니다.

+
+

Note

+

API 서버는 API Priority 및 Fairness 도입으로 더 복잡한 시스템을 가지고 있으며, 이에 대해 별도로 논의할 것입니다.

+
+
+

Note

+

주의: 일부 지표는 적합한 것처럼 보이지만 사실 다른 것을 측정하는 경우가 있습니다. 예를 들어 kubelet_http_inflight_requests는 Kubelet의 지표 서버에만 관련이 있으며, Kubelet에서 API 서버 요청의 수가 아닙니다. 이로 인해 Kubelet의 QPS 플래그를 잘못 구성할 수 있습니다. 특정 Kubelet에 대한 감사 로그 쿼리가 지표를 확인하는 더 신뢰할 수 있는 방법일 수 있습니다.

+
+

분산 컴포넌트 스케일링

+

EKS는 관리형 서비스이므로 쿠버네티스 구성 요소를 두 범주로 나누겠습니다. 하나는 etcd, Kube 컨트롤러 매니저 및 스케쥴러(다이어그램 왼쪽 부분)를 포함하는 AWS 관리형 구성 요소와 Kubelet, 컨테이너 런타임과 같은 고객이 구성할 수 있는 구성 요소, 네트워킹 및 스토리지 드라이버와 같이 AWS API를 호출하는 다양한 오퍼레이터(다이어그램 오른쪽)입니다. API 우선 순위 및 공정성 설정은 고객이 구성할 수 있으므로 AWS에서 관리하더라도 API 서버는 중간에 둡니다.

+

쿠버네티스 컴포넌트

+

업스트림 및 다운스트림 병목 현상

+

각 서비스를 모니터링할 때는 지표를 양방향으로 검토하여 병목 현상을 찾는 것이 중요합니다. Kubelet을 예로 들어 이 작업을 수행하는 방법을 알아보겠습니다. Kubelet은 API 서버와 컨테이너 런타임 모두에 대해 통신합니다. 구성 요소 중 하나에 문제가 있는지 여부를 감지하려면 어떻게, 무엇을 모니터링해야 할까요?

+

노드당 파드 수는 몇 개인가요?

+

한 노드에서 실행할 수 있는 파드 수와 같은 확장 수치를 살펴보면 업스트림이 지원하는 노드당 110개의 파드를 액면 그대로 사용할 수 있습니다.

+
+

Note

+

https://kubernetes.io/docs/setup/best-practices/cluster-large/

+
+

하지만 워크로드는 업스트림의 확장성 테스트에서 테스트한 것보다 더 복잡할 수 있습니다. 프로덕션에서 실행하려는 파드의 개수만큼 서비스를 제공할 수 있도록 Kubelet이 Containerd 런타임을 "따라잡고" 있는지 확인해봅니다.

+

Keeping up

+

지나치게 단순화하자면, Kubelet은 컨테이너 런타임 (우리의 경우 Containerd) 에서 파드의 상태를 가져오고 있습니다. 상태를 너무 빨리 변경하는 파드가 너무 많으면 어떻게 될까요? 변경률이 너무 높으면 [컨테이너 런타임에 대한] 요청이 타임아웃될 수 있습니다.

+
+

Note

+

쿠버네티스는 끊임없이 진화하고 있으며, 이 서브 시스템은 현재 변화를 겪고 있습니다. https://github.com/kubernetes/enhancements/issues/3386

+
+

Flow +PLEG duration

+

위 그래프에서 파드 라이프사이클 이벤트 생성 기간 지표의 타임아웃 값에 도달했음을 나타내는 납작한 선을 볼 수 있습니다. 자체 클러스터에서 이를 확인하려면 다음 PromQL 구문을 사용할 수 있습니다.

+
increase(kubelet_pleg_relist_duration_seconds_bucket{instance="$instance"}[$__rate_interval])
+
+

이런 타임아웃 동작을 목격하면 노드가 가능한 한도를 초과했다는 것을 알 수 있습니다. 계속 진행하기 전에 타임아웃의 원인을 수정해야 합니다. 이는 노드당 파드 수를 줄이거나 많은 양의 재시도를 유발하여 이탈률에 영향을 줄 수 있는 오류를 찾아냄으로써 달성할 수 있습니다. 중요한 점은 노드가 고정된 수 대신 할당된 파드의 회전율을 처리할 수 있는지 여부를 이해하는 가장 좋은 방법은 지표를 사용하는 것입니다.

+

지표별 확장

+

지표를 사용하여 시스템을 최적화한다는 개념은 오래되었지만 사람들이 쿠버네티스 여정을 시작하면서 간과되는 경우가 많습니다. 특정 숫자 (예: 노드당 110개의 파드) 에 초점을 맞추는 대신 시스템의 병목 지점을 찾는 데 도움이 되는 지표를 찾는 데 집중합니다. 이런 지표에 대한 올바른 임계값을 이해하면 시스템이 최적으로 구성되어 있다는 확신을 가질 수 있습니다.

+

변경으로 인한 영향

+

문제를 일으킬 수 있는 일반적인 패턴은 의심스러워 보이는 첫 번째 메트릭이나 로그 오류에 집중하는 것입니다. Kubelet의 전송 시간이 더 일찍 초과된 것을 확인했을 때 Kubelet이 전송할 수 있는 초당 속도를 높이는 등 무작위로 시도할 수 있지만, 먼저 발견된 오류의 다운스트림에서 모든 것을 전체적으로 살펴보는 것이 좋습니다.목적에 맞게 변경하고 데이터를 바탕으로 변경하세요.

+

Kubelet의 다운스트림은 컨테이너 런타임 (파드 오류), EC2 API와 통신하는 스토리지 드라이버(CSI), 네트워크 드라이버(CNI) 와 같은 데몬셋 등입니다.

+

Flow add-ons

+

Kubelet이 런타임을 따라가지 못하는 이전 예를 계속해 보겠습니다. 노드를 너무 조밀하게 빈팩하면 오류가 발생하는 경우가 많습니다.

+

Bottlenecks

+

워크로드에 적합한 노드 크기를 설계할 때 이런 신호는 시스템에 불필요한 압력을 가하여 규모와 성능을 모두 제한할 수 있으므로 간과하기 쉬운 신호입니다.

+

불필요한 오류로 인한 비용

+

쿠버네티스 컨트롤러는 오류 상황이 발생할 때 재시도하는 데 탁월하지만, 여기에는 비용이 따릅니다. 이런 재시도는 Kube 컨트롤러 매니저와 같은 구성 요소에 대한 압력을 가중시킬 수 있습니다. 규모 테스트의 중요한 부분은 이런 오류를 모니터링하는 것입니다.

+

발생하는 오류가 적을수록 시스템에서 문제를 더 쉽게 찾아낼 수 있습니다. 주요 작업 (예: 업그레이드) 전에 클러스터에 오류가 없는지 정기적으로 확인함으로써 예상치 못한 이벤트 발생 시 로그 문제 해결을 간소화할 수 있습니다.

+

시야 넓히기

+

1,000개의 노드가 있는 대규모 클러스터에서는 병목 현상을 개별적으로 찾아보고 싶지 않습니다. PromQL에서는 topk라는 함수를 사용하여 데이터 집합에서 가장 높은 값을 찾을 수 있습니다. K는 변수로, 원하는 항목 수를 배치합니다.여기서는 세 개의 노드를 사용하여 클러스터의 모든 Kubelet이 포화 상태인지 여부를 파악합니다.지금까지 레이턴시를 살펴보았는데, 이제 Kubelet이 이벤트를 삭제하는지 살펴보겠습니다.

+
topk(3, increase(kubelet_pleg_discard_events{}[$__rate_interval]))
+
+

이 문장을 분해해 보겠습니다.

+
    +
  • 그라파나 변수 $__rate_interval을 사용하여 필요한 네 개의 샘플을 얻을 수 있도록 합니다. 이렇게 하면 복잡한 모니터링 주제를 간단한 변수로 처리할 수 있습니다.
  • +
  • topk는 상위 결과만 표시하며 숫자 3은 결과를 세 개로 제한합니다. 이는 클러스터 전체 지표에 유용한 함수입니다.
  • +
  • {}는 필터가 없음을 나타냅니다. 일반적으로 스크래핑 규칙의 작업 이름을 입력하겠지만, 이런 이름이 다양하기 때문에 비워둡니다.
  • +
+

문제를 반으로 나누기

+

시스템의 병목 현상을 해결하기 위해 업스트림 또는 다운스트림에 문제가 있음을 보여주는 지표를 찾는 접근 방식을 취합니다. 이렇게 하면 문제를 반으로 나눌 수 있습니다.또한 이는 지표 데이터를 표시하는 방식의 핵심 원칙이기도 합니다.

+

이 프로세스부터 시작하는 것이 좋습니다. API 서버를 사용하면 클라이언트 애플리케이션이나 컨트롤 플레인에 문제가 있는지 확인할 수 있기 때문입니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/scalability/docs/workloads/index.html b/ko/scalability/docs/workloads/index.html new file mode 100644 index 000000000..48df44336 --- /dev/null +++ b/ko/scalability/docs/workloads/index.html @@ -0,0 +1,2320 @@ + + + + + + + + + + + + + + + + + + + + + + + 워크로드 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

워크로드

+

워크로드는 클러스터를 확장할 수 있는 규모에 영향을 미칩니다. 쿠버네티스 API를 많이 사용하는 워크로드는 단일 클러스터에서 보유할 수 있는 워크로드의 총량을 제한하지만, 부하를 줄이기 위해 변경할 수 있는 몇 가지 기본값이 있습니다.

+

쿠버네티스 클러스터의 워크로드는 쿠버네티스 API와 통합되는 기능(예: Secrets 및 ServiceAccount)에 액세스할 수 있지만, 이런 기능이 항상 필요한 것은 아니므로 사용하지 않는 경우 비활성화해야 합니다.워크로드 액세스와 쿠버네티스 컨트롤 플레인에 대한 종속성을 제한하면 클러스터에서 실행할 수 있는 워크로드 수가 증가하고 워크로드에 대한 불필요한 액세스를 제거하고 최소 권한 관행을 구현하여 클러스터의 보안을 개선할 수 있습니다. 자세한 내용은 보안 모범 사례를 참고하세요.

+

파드 네트워킹에 IPv6 사용하기

+

VPC를 IPv4에서 IPv6으로 전환할 수 없으므로 클러스터를 프로비저닝하기 전에 IPv6를 활성화하는 것이 중요합니다. 다만 VPC에서 IPv6를 활성화한다고 해서 반드시 사용해야 하는 것은 아니며, 파드 및 서비스가 IPv6를 사용하는 경우에도 IPv4 주소를 오가는 트래픽을 라우팅할 수 있습니다. 자세한 내용은 EKS 네트워킹 모범 사례를 참고하세요.

+

클러스터에서 IPv6 사용하기 튜토리얼를 사용하면 가장 일반적인 클러스터 및 워크로드 확장 제한을 피할 수 있습니다. IPv6는 사용 가능한 IP 주소가 없어 파드와 노드를 생성할 수 없는 IP 주소 고갈을 방지합니다. 또한 노드당 ENI 어태치먼트 수를 줄여 파드가 IP 주소를 더 빠르게 수신하므로 노드당 성능도 개선되었습니다. VPC CNI의 IPv4 Prefix 모드를 사용하여 유사한 노드 성능을 얻을 수 있지만, 여전히 VPC에서 사용할 수 있는 IP 주소가 충분한지 확인해야 합니다.

+

네임스페이스당 서비스 수 제한

+

네임스페이스의 최대 서비스 수는 5,000개이고 클러스터의 최대 서비스 수는 10,000개 입니다. 워크로드와 서비스를 구성하고, 성능을 높이고, 네임스페이스 범위 리소스가 연쇄적으로 영향을 받지 않도록 하려면 네임스페이스당 서비스 수를 500개로 제한하는 것이 좋습니다.

+

kube-proxy를 사용하여 노드당 생성되는 IP 테이블 규칙의 수는 클러스터의 총 서비스 수에 따라 증가합니다.수천 개의 IP 테이블 규칙을 생성하고 이런 규칙을 통해 패킷을 라우팅하면 노드의 성능이 저하되고 네트워크 지연 시간이 늘어납니다.

+

네임스페이스당 서비스 수가 500개 미만인 경우 단일 애플리케이션 환경을 포함하는 쿠버네티스 네임스페이스를 생성하십시오. 이렇게 하면 서비스 검색 제한을 피할 수 있을 만큼 서비스 검색 크기가 작아지고 서비스 이름 충돌을 방지하는 데도 도움이 됩니다. 애플리케이션 환경(예: dev, test, prod) 은 네임스페이스 대신 별도의 EKS 클러스터를 사용해야 합니다.

+

Elastic Load Balancer 할당량 이해

+

서비스를 생성할 때 사용할 로드 밸런싱 유형(예: 네트워크 로드밸런서 (NLB) 또는 애플리케이션 로드밸런서 (ALB)) 를 고려하세요. 각 로드밸런서 유형은 서로 다른 기능을 제공하며 할당량이 다릅니다. 기본 할당량 중 일부는 조정할 수 있지만 일부 할당량 최대값은 변경할 수 없습니다. 계정 할당량 및 사용량을 보려면 AWS 콘솔의 서비스 할당량 대시보드를 참조하십시오.

+

예를 들어, 기본 ALB 목표는 1000입니다. 엔드포인트가 1,000개가 넘는 서비스가 있는 경우 할당량을 늘리거나 서비스를 여러 ALB로 분할하거나 쿠버네티스 인그레스(Ingress)를 사용해야 합니다. 기본 NLB 대상은 3000이지만 AZ당 500개 대상으로 제한됩니다. 클러스터에서 NLB 서비스에 대해 500개 이상의 파드를 실행하는 경우 여러 AZ를 사용하거나 할당량 한도 증가를 요청해야 합니다.

+

서비스에 연결된 로드밸런서를 사용하는 대신 인그레스 컨트롤러 를 사용할 수 있습니다. AWS Load Balancer Controller는 수신 리소스용 ALB를 생성할 수 있지만, 클러스터에서 전용 컨트롤러를 실행하는 것도 고려해 볼 수 있습니다.클러스터 내 수신 컨트롤러를 사용하면 클러스터 내에서 역방향 프록시를 실행하여 단일 로드밸런서에서 여러 쿠버네티스 서비스를 노출할 수 있습니다. 컨트롤러는 Gateway API 지원과 같은 다양한 기능을 제공하므로 워크로드의 수와 규모에 따라 이점이 있을 수 있습니다.

+

Route 53, Global Accelerator, 또는 CloudFront 사용하기

+

여러 로드밸런서를 사용하는 서비스를 단일 엔드포인트로 사용하려면 Amazon CloudFront, AWS Global Accelerator 또는 Amazon Route 53를 사용하여 모든 로드밸런서를 단일 고객 대상 엔드포인트로 노출해야 합니다. 각 옵션에는 서로 다른 이점이 있으며 필요에 따라 개별적으로 또는 함께 사용할 수 있습니다.

+

Route 53은 공통 이름으로 여러 로드밸런서를 노출할 수 있으며 할당된 가중치에 따라 각 로드밸런서에 트래픽을 전송할 수 있습니다. DNS 가중치설명서에 자세한 내용을 확인할 수 있으며 쿠버네티스 외부 DNS 컨트롤러를 사용하여 이를 구현하는 방법은 AWS Load Balancer Controller 설명서에서 확인할 수 있습니다.

+

Global Accelerator터는 요청 IP 주소를 기반으로 가장 가까운 지역으로 워크로드를 라우팅할 수 있습니다. 이는 여러 지역에 배포되는 워크로드에 유용할 수 있지만 단일 지역의 단일 클러스터로의 라우팅을 개선하지는 않습니다. Route 53을 Global Accelerator터와 함께 사용하면 가용영역을 사용할 수 없는 경우 상태 점검 및 자동 장애 조치와 같은 추가적인 이점이 있습니다. Route 53과 함께 Global Accelerator터를 사용하는 예는 이 블로그 게시물에서 확인할 수 있습니다.

+

CloudFront는 Route 53 및 Global Accelerator와 함께 사용하거나 단독으로 트래픽을 여러 목적지로 라우팅하는 데 사용할 수 있습니다. CloudFront는 오리진 소스에서 제공되는 자산을 캐시하므로 제공하는 대상에 따라 대역폭 요구 사항을 줄일 수 있습니다.

+

엔드포인트(Endpoints) 대신에 엔드포인트 슬라이스(EndpointSlices) 사용하기

+

서비스 레이블과 일치하는 파드를 발견할 때는 엔드포인트 대신 엔드포인트 슬라이스를 사용해야 합니다. 엔드포인트는 서비스를 소규모로 노출할 수 있는 간단한 방법이었지만, 대규모 서비스가 자동으로 확장되거나 업데이트되면 쿠버네티스 컨트롤 플레인에서 많은 트래픽이 발생합니다. 엔드포인트슬라이스에는 토폴로지 인식 힌트와 같은 기능을 사용할 수 있는 자동 그룹화 기능이 있습니다.

+

모든 컨트롤러가 기본적으로 엔드포인트슬라이스를 사용하는 것은 아닙니다. 컨트롤러 설정을 확인하고 필요한 경우 활성화해야 합니다. AWS Load Balancer Controller의 경우 엔드포인트슬라이스를 사용하려면 --enable-endpoint-slices 선택적 플래그를 활성화해야 합니다.

+

가능하다면 변경 불가(immutable)하고 외부(external) 시크릿 사용하기

+

kubelet은 해당 노드의 파드에 대한 볼륨에서 사용되는 시크릿의 현재 키와 값을 캐시에 보관한다. kubelet은 시크릿을 감시하여 변경 사항을 탐지합니다. 클러스터가 확장됨에 따라 시계의 수가 증가하면 API 서버 성능에 부정적인 영향을 미칠 수 있습니다.

+

시크릿의 감시 수를 줄이는 두 가지 전략이 있습니다.

+
    +
  • 쿠버네티스 리소스에 액세스할 필요가 없는 애플리케이션의 경우 AutoMountServiceAccountToken: false를 설정하여 서비스 어카운트 시크릿 자동 탑재를 비활성화할 수 있습니다.
  • +
  • 애플리케이션 암호가 정적이어서 향후 수정되지 않을 경우 암호를 변경 불가능으로 표시하십시오. kubelet은 변경 불가능한 비밀에 대한 API 감시 기능을 유지하지 않습니다.
  • +
+

서비스 어카운트을 파드에 자동으로 마운트하는 것을 비활성화하려면 워크로드에서 다음 설정을 사용할 수 있습니다. 특정 워크로드에 서비스 어카운트이 필요한 경우 이런 설정을 재정의할 수 있습니다.

+
apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: app
+automountServiceAccountToken: true
+
+

클러스터의 암호 수가 제한인 10,000개를 초과하기 전에 모니터링하세요. 다음 명령을 사용하여 클러스터의 총 암호 수를 확인할 수 있습니다. 클러스터 모니터링 도구를 통해 이 한도를 모니터링해야 합니다.

+
kubectl get secrets -A | wc -l
+
+

이 한도에 도달하기 전에 클러스터 관리자에게 알리도록 모니터링을 설정해야 합니다.Secrets Store CSI 드라이버와 함께 AWS Key Management Service (AWS KMS) 또는 Hashicorp Vault와 같은 외부 비밀 관리 옵션을 사용하는 것을 고려해 보십시오.

+

배포 이력 제한

+

클러스터에서 이전 객체가 계속 추적되므로 파드를 생성, 업데이트 또는 삭제할 때 속도가 느려질 수 있습니다. 디플로이먼트therevisionHistoryLimit을 줄이면 구형 레플리카셋을 정리하여 쿠버네티스 컨트롤러 매니저가 추적하는 오브젝트의 총량을 줄일 수 있습니다. 디플로이먼트의 기본 기록 한도는 10개입니다.

+

클러스터가 CronJobs나 다른 메커니즘을 통해 많은 수의 작업 개체를 생성하는 경우, TTLSecondsFinished 설정을 사용하여 클러스터의 오래된 파드를 자동으로 정리해야 합니다. 이렇게 하면 지정된 시간이 지나면 성공적으로 실행된 작업이 작업 기록에서 제거됩니다.

+ +

파드가 노드에서 실행될 때, kubelet은 각 활성 서비스에 대한 환경 변수 세트를 추가합니다. 리눅스 프로세스에는 환경에 맞는 최대 크기가 있으며 네임스페이스에 서비스가 너무 많으면 이 크기에 도달할 수 있습니다. 네임스페이스당 서비스 수는 5,000개를 초과할 수 없습니다. 그 이후에는 서비스 환경 변수 수가 셸 한도를 초과하여 시작 시 파드가 크래시를 일으키게 됩니다.

+

파드가 서비스 검색에 서비스 환경 변수를 사용하지 않아야 하는 다른 이유도 있습니다. 환경 변수 이름 충돌, 서비스 이름 유출, 전체 환경 크기 등이 있습니다. 서비스 엔드포인트를 검색하려면 CoreDNS를 사용해야 합니다.

+

리소스당 동적 어드미션 웹훅(Webhook) 제한하기

+

Dynamic Admission Webhooks에는 어드미션 웹훅과 뮤테이팅(Mutating) 웹훅이 포함됩니다. 쿠버네티스 컨트롤 플레인에 속하지 않는 API 엔드포인트로, 리소스가 쿠버네티스 API로 전송될 때 순서대로 호출됩니다. 각 웹훅의 기본 제한 시간은 10초이며, 웹훅이 여러 개 있거나 제한 시간이 초과된 경우 API 요청에 걸리는 시간이 늘어날 수 있습니다.

+

특히 가용영역 장애 발생 시 웹훅의 가용성이 높은지 확인하고 FailurePolicy가 리소스를 거부하거나 실패를 무시하도록 적절하게 설정되어 있는지 확인하세요. --dry-run kubectl 명령이 웹훅을 우회하도록 허용하여 필요하지 않을 때는 웹훅을 호출하지 마십시오.

+
apiVersion: admission.k8s.io/v1
+kind: AdmissionReview
+request:
+  dryRun: False
+
+

웹훅를 변경하면 리소스를 자주 연속적으로 수정할 수 있습니다. 뮤테이팅 웹훅이 5개 있고 리소스 50개를 배포하면 수정된 리소스의 이전 버전을 제거하기 위해 5분마다 컴팩션이 실행될 때까지 etcd는 각 리소스의 모든 버전을 저장합니다. 이 시나리오에서 etcd가 대체된 리소스를 제거하면 etcd에서 200개의 리소스 버전이 제거되며 리소스 크기에 따라 15분마다 조각 모음이 실행될 때까지 etcd 호스트에서 상당한 공간을 사용할 수 있습니다.

+

이런 조각 모음(defragmentation)으로 인해 etcd가 일시 중지되어 쿠버네티스 API 및 컨트롤러에 다른 영향을 미칠 수 있습니다. 대규모 리소스를 자주 수정하거나 수백 개의 리소스를 순식간에 연속해서 수정하는 것은 피해야 합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/compliance/index.html b/ko/security/docs/compliance/index.html new file mode 100644 index 000000000..607164002 --- /dev/null +++ b/ko/security/docs/compliance/index.html @@ -0,0 +1,2362 @@ + + + + + + + + + + + + + + + + + + + + + + + 컴플라이언스 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

컴플라이언스

+

컴플라이언스는 AWS와 해당 서비스 소비자 간의 공동 책임입니다. 일반적으로 AWS는 "클라우드의 보안"을 책임지고, 사용자는 "클라우드에서의 보안"을 담당합니다. AWS와 해당 사용자가 책임져야 할 책임을 설명하는 선은 서비스에 따라 달라집니다. 예를 들어 Fargate를 통해 AWS는 데이터 센터, 하드웨어, 가상 인프라(Amazon EC2) 및 컨테이너 런타임(Docker)의 물리적 보안을 관리할 책임이 있습니다. Fargate 사용자는 컨테이너 이미지와 해당 애플리케이션을 보호할 책임이 있습니다. 컴플라이언스 표준을 준수해야 하는 워크로드를 실행할 때 고려해야 할 중요한 사항은 누구의 책임자인지 파악하는 것입니다.

+

다음 표는 다양한 컨테이너 서비스가 준수하는 규정 준수 프로그램을 보여줍니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
컴플라이언스 프로그램Amazon ECS 오케스트레이터Amazon EKS 오케스트레이터ECS Fargete아마존 ECR
PCI DSS Level 11111
HIPAA Eligible1111
SOC I1111
SOC II1111
SOC III1111
ISO 27001:20131111
ISO 9001:20151111
ISO 27017:20151111
ISO 27018:20191111
IRAP1111
FedRAMP Moderate (East/West)1101
FedRAMP High (GovCloud)1101
DOD CC SRG1DISA Review (IL5)01
HIPAA BAA1111
MTCS1101
C51101
K-ISMS1101
ENS High1101
OSPAR1101
HITRUST CSF1111
+

컴플라이언스 상태는 시간이 지남에 따라 변경됩니다. 최신 상태는 항상 https://aws.amazon.com/compliance/services-in-scope/를 참조하십시오.

+

클라우드 인증 모델 및 모범 사례에 대한 자세한 내용은 AWS 백서인 안전한 클라우드 채택을 위한 인증 모델을 참조하십시오.

+

시프트 레프트(Shift Left)

+

시프트 레프트 개념은 소프트웨어 개발 수명 주기 초기에 정책 위반 및 오류를 포착하는 것이 포함됩니다. 보안 관점에서 이것은 매우 유익할 수 있습니다. 예를 들어 개발자는 애플리케이션을 클러스터에 배포하기 전에 구성 문제를 수정할 수 있습니다. 이와 같은 실수를 조기에 포착하면 정책을 위반하는 구성이 배포되는 것을 방지할 수 있습니다.

+

코드로서의 정책 (PaC, Policy as Code)

+

정책은 행동, 즉 허용된 행동 또는 금지된 행동을 규제하기 위한 일련의 규칙으로 생각할 수 있습니다. 예를 들어 모든 Dockerfile에는 컨테이너를 루트 사용자가 아닌 사용자로 실행하도록 하는 USER 지시문이 포함되어야 한다는 정책이 있을 수 있습니다. 문서로서 이와 같은 정책은 발견하고 적용하기 어려울 수 있습니다. 요구 사항이 변경됨에 따라 이 정책이 시대에 뒤처질 수도 있습니다. PaC 솔루션을 사용하면 알려진 위협과 지속적인 위협을 탐지, 예방, 감소 및 대응하는 보안, 규정 준수 및 개인 정보 보호 제어를 자동화할 수 있습니다. 또한 정책을 체계화하고 다른 코드 아티팩트와 마찬가지로 관리할 수 있는 메커니즘을 제공합니다. 이 접근 방식의 이점은 DevOps 및 GitOps 전략을 재사용하여 Kubernetes 클러스터 전체에 정책을 관리하고 일관되게 적용할 수 있다는 것입니다. PAC 옵션과 PSP의 미래에 대한 자세한 내용은 파드 보안을 참조하십시오.

+

파이프라인에서 PaC 도구를 사용하여 배포 전에 위반 감지

+
    +
  • OPA는 CNCF의 일부인 오픈 소스 정책 엔진입니다. 정책 결정을 내리는 데 사용되며 언어 라이브러리 또는 서비스 등 다양한 방식으로 실행할 수 있습니다. OPA 정책은 Rego라는 도메인 특정 언어(DSL)로 작성됩니다. OPA는 쿠버네티스 동적 어드미션 컨트롤러의 일부로 Gatekeeper 프로젝트로 실행되는 경우가 많지만, OPA는 CI/CD 파이프라인에 통합될 수도 있습니다. 이를 통해 개발자는 릴리스 주기 초기에 구성에 대한 피드백을 받을 수 있으며, 이를 통해 프로덕션에 들어가기 전에 문제를 해결하는 데 도움이 될 수 있습니다. 일반적인 OPA 정책 모음은 이 프로젝트의 GitHub 리포지토리에서 찾을 수 있습니다.
  • +
  • Conftest는 OPA를 기반으로 구축되었으며 쿠버네티스 구성을 테스트하기 위한 개발자 중심의 경험을 제공합니다.
  • +
  • Kyverno는 쿠버네티스용으로 설계된 정책 엔진입니다.Kyverno를 사용하면 정책이 쿠버네티스 리소스로 관리되므로 정책을 작성하는 데 새로운 언어가 필요하지 않습니다. 이를 통해 kubectl, git, kustomize와 같은 친숙한 도구를 사용하여 정책을 관리할 수 있습니다. Kyverno 정책은 Kubernetes 리소스를 검증, 변경 및 생성하고 OCI 이미지 공급망 보안을 보장할 수 있습니다. Kyverno CLI는 CI/CD 파이프라인의 일부로 정책을 테스트하고 리소스를 검증하는 데 사용할 수 있습니다. 모든 Kyverno 커뮤니티 정책은 Kyverno 웹 사이트에서 확인할 수 있으며, Kyverno CLI를 사용하여 파이프라인에서 테스트를 작성하는 예는 정책 리포지토리를 참조하십시오.
  • +
+

도구 및 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/data/index.html b/ko/security/docs/data/index.html new file mode 100644 index 000000000..f0033cb75 --- /dev/null +++ b/ko/security/docs/data/index.html @@ -0,0 +1,2419 @@ + + + + + + + + + + + + + + + + + + + + + + + 데이터 암호화 및 시크릿 관리 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

데이터 암호화 및 시크릿 관리

+

저장 시 암호화

+

쿠버네티스와 함께 사용할 수 있는 AWS 네이티브 스토리지 옵션은 EBS, EFS, FSx for Lustre등 세 가지가 있습니다. 세 가지 모두 서비스 관리 키 또는 고객 관리 키 (CMK)를 사용하여 저장 시 암호화를 제공합니다. EBS의 경우 인트리 스토리지 드라이버 또는 EBS CSI드라이버를 사용할 수 있습니다.둘 다 볼륨 암호화 및 CMK 제공을 위한 파라미터를 포함합니다. EFS의 경우 EFS CSI 드라이버를 사용할 수 있지만 EBS와 달리 EFS CSI 드라이버는 동적 프로비저닝을 지원하지 않습니다. EKS와 함께 EFS를 사용하려면 PV를 생성하기 전에 파일 시스템에 대한 저장 중 암호화를 프로비저닝하고 구성해야 합니다. EFS 파일 암호화에 대한 자세한 내용은 저장 데이터 암호화를 참조합니다. EFS와 FSx for Lustre에는 저장 시 암호화를 제공하는 것 외에도 전송 데이터를 암호화하는 옵션이 포함되어 있습니다. FSx for Luster는 기본적으로 이 작업을 수행합니다. EFS의 경우 다음 예와 같이 PV의 MountOptionstls 파라미터를 추가하여 전송 암호화를 추가할 수 있습니다:

+
apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: efs-pv
+spec:
+  capacity:
+    storage: 5Gi
+  volumeMode: Filesystem
+  accessModes:
+    - ReadWriteOnce
+  persistentVolumeReclaimPolicy: Retain
+  storageClassName: efs-sc
+  mountOptions:
+    - tls
+  csi:
+    driver: efs.csi.aws.com
+    volumeHandle: <file_system_id>
+
+

FSx CSI 드라이버는 Lustre 파일 시스템의 동적 프로비저닝을 지원합니다. 기본적으로 서비스 관리 키를 사용하여 데이터를 암호화하지만, 다음 예와 같이 자체 CMK를 제공하는 옵션이 있습니다:

+
kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+  name: fsx-sc
+provisioner: fsx.csi.aws.com
+parameters:
+  subnetId: subnet-056da83524edbe641
+  securityGroupIds: sg-086f61ea73388fb6b
+  deploymentType: PERSISTENT_1
+  kmsKeyId: <kms_arn>
+
+
+

Attention

+

2020년 5월 28일부터 EKS Fargate 파드의 임시 볼륨에 기록되는 모든 데이터는 업계 표준 AES-256 암호화 알고리즘을 사용하여 기본적으로 암호화됩니다. 서비스에서 암호화 및 복호화를 원활하게 처리하므로 애플리케이션을 수정할 필요가 없습니다.

+
+

저장된 데이터 암호화

+

저장된 데이터를 암호화하는 것은 모범 사례로 간주됩니다. 암호화가 필요한지 확실하지 않은 경우 데이터를 암호화하세요.

+

CMK를 주기적으로 교체하세요

+

CMK를 자동으로 교체하도록 KMS를 구성합니다. 이렇게 하면 1년에 한 번 키가 교체되고 이전 키는 무기한 저장되므로 데이터를 계속 해독할 수 있습니다. 자세한 내용은 고객 마스터 키 교체 문서를 참조합니다.

+

EFS 액세스 포인트를 사용하여 공유 데이터세트에 대한 액세스를 간소화합니다

+

서로 다른 POSIX 파일 권한으로 데이터 세트를 공유했거나 다른 마운트 지점을 생성하여 공유 파일 시스템의 일부에 대한 액세스를 제한하려는 경우 EFS 액세스 포인트를 사용하는 것이 좋습니다. 액세스 포인트 사용에 대한 자세한 내용은 AWS 문서를 참조합니다. 현재 액세스 포인트(AP)를 사용하려면 PV의 VolumeHandle 파라미터에서 AP를 참조해야 합니다.

+
+

Attention

+

2021년 3월 23일부터 EFS CSI 드라이버는 EFS 액세스 포인트의 동적 프로비저닝을 지원합니다. 액세스 포인트는 여러 파드 간에 파일 시스템을 쉽게 공유할 수 있게 해주는 EFS 파일 시스템의 애플리케이션 별 진입점입니다. 각 EFS 파일 시스템에는 최대 120개의 PV가 있을 수 있습니다. 자세한 내용은 Amazon EFS CSI 동적 프로비저닝 소개를 참조하십시오.

+
+

시크릿 관리

+

쿠버네티스 시크릿은 사용자 인증서, 암호 또는 API 키와 같은 민감한 정보를 저장하는 데 사용됩니다. 이들은 etcd에 base64로 인코딩된 문자열로 유지됩니다. EKS에서는 etcd 노드의 EBS 볼륨이 EBS 암호화로 암호화됩니다. 파드는 PodSpec의 시크릿을 참조하여 쿠버네티스 시크릿 객체를 검색할 수 있습니다. 이런 시크릿은 환경 변수에 매핑하거나 볼륨으로 마운트할 수 있습니다. 시크릿 생성에 대한 자세한 내용은 쿠버네티스 문서를 참조하십시오.

+
+

Caution

+

특정 네임스페이스의 시크릿은 네임스페이스의 모든 파드에서 참조할 수 있습니다.

+
+
+

Caution

+

노드 권한 부여자는 Kubelet이 노드에 마운트된 모든 시크릿을 읽을 수 있도록 허용합니다.

+
+

쿠버네티스 시크릿 봉투 암호화에 AWS KMS 사용

+

이를 통해 고유한 DEK(데이터 암호화 키)으로 시크릿을 암호화할 수 있습니다. 그런 다음 DEK는 AWS KMS의 KEK (키 암호화 키) 를 사용하여 암호화되며, 이 KEK (키 암호화 키) 는 반복 일정에 따라 자동으로 교체될 수 있습니다. 쿠버네티스용 KMS 플러그인을 사용하면 모든 쿠버네티스 암호가 일반 텍스트 대신 암호문의 etcd에 저장되며 쿠버네티스 API 서버에서만 해독할 수 있습니다. +자세한 내용은 심층 방어를 위한 EKS 암호화 공급자 지원 사용 블로그을 참조하십시오.

+

쿠버네티스 시크릿 사용 감사

+

EKS에서 감사 로깅을 켜고 CloudWatch 지표 필터 및 알람을 생성하여 시크릿이 사용될 때 알림을 보냅니다 (선택 사항). 다음은 쿠버네티스 감사 로그에 대한 메트릭 필터의 예시입니다, {($.verb="get") && ($.ObjectRef.resource="Secret")}. CloudWatch Log Insights에서는 다음 쿼리를 사용할 수도 있습니다:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| stats count(*) by objectRef.name as secret
+| filter verb="get" and objectRef.resource="secrets"
+
+

위 쿼리는 특정 기간 내에 시크릿에 액세스한 횟수를 표시합니다.

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter verb="get" and objectRef.resource="secrets"
+| display objectRef.namespace, objectRef.name, user.username, responseStatus.code
+
+

이 쿼리에는 시크릿에 액세스하려고 시도한 사용자의 네임스페이스 및 사용자 이름 및 응답 코드와 함께 시크릿이 표시됩니다.

+

주기적으로 시크릿 교체하기

+

쿠버네티스는 시크릿을 자동으로 교체하지 않습니다. 암호를 교체해야 하는 경우 Vault 또는 AWS Secrets Manager와 같은 외부 암호 저장소를 사용하는 것이 좋습니다.

+

다른 애플리케이션으로부터 시크릿을 분리하는 방법으로 별도의 네임스페이스를 사용하십시오

+

네임스페이스의 애플리케이션 간에 공유할 수 없는 시크릿이 있는 경우 해당 애플리케이션에 대해 별도의 네임스페이스를 생성하십시오.

+

환경 변수 대신 볼륨 마운트 사용

+

환경 변수 값이 실수로 로그에 나타날 수 있습니다. 볼륨으로 마운트된 시크릿은 tmpfs 볼륨(RAM 백업 파일 시스템)으로 인스턴스화되며, 파드가 삭제되면 노드에서 자동으로 제거됩니다.

+

외부 시크릿 제공자 사용

+

AWS Secret Manager와 Hishcorp의 Vault를 포함하여 쿠버네티스 시크릿을 사용할 수 있는 몇 가지 실행 가능한 대안이 있습니다. 이런 서비스는 쿠버네티스 시크릿에서는 사용할 수 없는 세밀한 액세스 제어, 강력한 암호화, 암호 자동 교체 등의 기능을 제공합니다. Bitnami의 Sealed Secrets는 비대칭 암호화를 사용하여 "봉인된 시크릿"을 생성하는 또 다른 접근 방식입니다. 공개 키는 시크릿을 암호화하는 데 사용되는 반면 암호 해독에 사용된 개인 키는 클러스터 내에 보관되므로 Git과 같은 소스 제어 시스템에 봉인된 시크릿을 안전하게 저장할 수 있습니다. 자세한 내용은 실드 시크릿을 사용한 쿠버네티스의 시크릿 배포 관리를 참조합니다.

+

외부 시크릿 스토어의 사용이 증가함에 따라 이를 쿠버네티스와 통합해야 할 필요성도 커졌습니다. Secret Store CSI 드라이버는 CSI 드라이버 모델을 사용하여 외부 시크릿 스토어로부터 시크릿을 가져오는 커뮤니티 프로젝트입니다. 현재 이 드라이버는 AWS Secret Manager, Azure, Vault 및 GCP를 지원합니다. AWS 공급자는 AWS 시크릿 관리자 AWS 파라미터 스토어를 모두 지원합니다. 또한 암호가 만료되면 암호가 교체되도록 구성할 수 있으며, AWS Secrets Manager 암호를 쿠버네티스 암호와 동기화할 수 있습니다. 암호의 동기화는 볼륨에서 암호를 읽는 대신 암호를 환경 변수로 참조해야 할 때 유용할 수 있습니다.

+
+

Note

+

시크릿 스토어 CSI 드라이버는 시크릿을 가져와야 하는 경우 시크릿을 참조하는 파드에 할당된 IRSA 역할을 사용합니다. 이 작업의 코드는 Github에서 찾을 수 있습니다.

+
+

AWS 보안 및 설정 공급자(ASCP) 에 대한 추가 정보는 다음 리소스를 참조하십시오:

+ +

external-secrets는 쿠버네티스와 함께 외부 시크릿 저장소를 사용하는 또 다른 방법입니다. CSI 드라이버와 마찬가지로 외부 시크릿은 AWS Secrets Manager를 비롯한 다양한 백엔드에서 작동합니다. 차이점은 외부 시크릿이 외부 시크릿 스토어에서 시크릿을 검색하는 대신 이런 백엔드의 시크릿을 시크릿으로 Kubernetes에 복사한다는 점입니다. 이를 통해 선호하는 시크릿 스토어를 사용하여 시크릿을 관리하고 쿠버네티스 네이티브 방식으로 시크릿과 상호작용할 수 있다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/detective/index.html b/ko/security/docs/detective/index.html new file mode 100644 index 000000000..37384d380 --- /dev/null +++ b/ko/security/docs/detective/index.html @@ -0,0 +1,2512 @@ + + + + + + + + + + + + + + + + + + + + + + + 탐지 관리 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

감사(Audit) 및 로깅

+

[감사] 로그를 수집하고 분석하는 것은 여러 가지 이유로 유용합니다. 로그는 근본 원인 분석(RCA) 및 책임 분석(예: 특정 변경에 대한 사용자 추적)에 도움이 될 수 있습니다. 로그가 충분히 수집되면 이를 사용하여 이상 행동을 탐지할 수도 있습니다. EKS에서는 감사 로그가 Amazon Cloudwatch 로그로 전송됩니다. EKS의 감사 정책은 다음과 같습니다.

+
apiVersion: audit.k8s.io/v1beta1
+kind: Policy
+rules:
+  # Log aws-auth configmap changes
+  - level: RequestResponse
+    namespaces: ["kube-system"]
+    verbs: ["update", "patch", "delete"]
+    resources:
+      - group: "" # core
+        resources: ["configmaps"]
+        resourceNames: ["aws-auth"]
+    omitStages:
+      - "RequestReceived"
+  - level: None
+    users: ["system:kube-proxy"]
+    verbs: ["watch"]
+    resources:
+      - group: "" # core
+        resources: ["endpoints", "services", "services/status"]
+  - level: None
+    users: ["kubelet"] # legacy kubelet identity
+    verbs: ["get"]
+    resources:
+      - group: "" # core
+        resources: ["nodes", "nodes/status"]
+  - level: None
+    userGroups: ["system:nodes"]
+    verbs: ["get"]
+    resources:
+      - group: "" # core
+        resources: ["nodes", "nodes/status"]
+  - level: None
+    users:
+      - system:kube-controller-manager
+      - system:kube-scheduler
+      - system:serviceaccount:kube-system:endpoint-controller
+    verbs: ["get", "update"]
+    namespaces: ["kube-system"]
+    resources:
+      - group: "" # core
+        resources: ["endpoints"]
+  - level: None
+    users: ["system:apiserver"]
+    verbs: ["get"]
+    resources:
+      - group: "" # core
+        resources: ["namespaces", "namespaces/status", "namespaces/finalize"]
+  - level: None
+    users:
+      - system:kube-controller-manager
+    verbs: ["get", "list"]
+    resources:
+      - group: "metrics.k8s.io"
+  - level: None
+    nonResourceURLs:
+      - /healthz*
+      - /version
+      - /swagger*
+  - level: None
+    resources:
+      - group: "" # core
+        resources: ["events"]
+  - level: Request
+    users: ["kubelet", "system:node-problem-detector", "system:serviceaccount:kube-system:node-problem-detector"]
+    verbs: ["update","patch"]
+    resources:
+      - group: "" # core
+        resources: ["nodes/status", "pods/status"]
+    omitStages:
+      - "RequestReceived"
+  - level: Request
+    userGroups: ["system:nodes"]
+    verbs: ["update","patch"]
+    resources:
+      - group: "" # core
+        resources: ["nodes/status", "pods/status"]
+    omitStages:
+      - "RequestReceived"
+  - level: Request
+    users: ["system:serviceaccount:kube-system:namespace-controller"]
+    verbs: ["deletecollection"]
+    omitStages:
+      - "RequestReceived"
+  # Secrets, ConfigMaps, and TokenReviews can contain sensitive & binary data,
+  # so only log at the Metadata level.
+  - level: Metadata
+    resources:
+      - group: "" # core
+        resources: ["secrets", "configmaps"]
+      - group: authentication.k8s.io
+        resources: ["tokenreviews"]
+    omitStages:
+      - "RequestReceived"
+  - level: Request
+    resources:
+      - group: ""
+        resources: ["serviceaccounts/token"]
+  - level: Request
+    verbs: ["get", "list", "watch"]
+    resources: 
+      - group: "" # core
+      - group: "admissionregistration.k8s.io"
+      - group: "apiextensions.k8s.io"
+      - group: "apiregistration.k8s.io"
+      - group: "apps"
+      - group: "authentication.k8s.io"
+      - group: "authorization.k8s.io"
+      - group: "autoscaling"
+      - group: "batch"
+      - group: "certificates.k8s.io"
+      - group: "extensions"
+      - group: "metrics.k8s.io"
+      - group: "networking.k8s.io"
+      - group: "policy"
+      - group: "rbac.authorization.k8s.io"
+      - group: "scheduling.k8s.io"
+      - group: "settings.k8s.io"
+      - group: "storage.k8s.io"
+    omitStages:
+      - "RequestReceived"
+  # Default level for known APIs
+  - level: RequestResponse
+    resources: 
+      - group: "" # core
+      - group: "admissionregistration.k8s.io"
+      - group: "apiextensions.k8s.io"
+      - group: "apiregistration.k8s.io"
+      - group: "apps"
+      - group: "authentication.k8s.io"
+      - group: "authorization.k8s.io"
+      - group: "autoscaling"
+      - group: "batch"
+      - group: "certificates.k8s.io"
+      - group: "extensions"
+      - group: "metrics.k8s.io"
+      - group: "networking.k8s.io"
+      - group: "policy"
+      - group: "rbac.authorization.k8s.io"
+      - group: "scheduling.k8s.io"
+      - group: "settings.k8s.io"
+      - group: "storage.k8s.io"
+    omitStages:
+      - "RequestReceived"
+  # Default level for all other requests.
+  - level: Metadata
+    omitStages:
+      - "RequestReceived"
+
+

권장 사항

+

감사 로그 활성화

+

감사 로그는 EKS에서 관리하는 EKS 관리형 쿠버네티스 컨트롤 플레인 로그의 일부입니다. 쿠버네티스 API 서버, 컨트롤러 관리자 및 스케줄러에 대한 로그와 감사 로그를 포함하는 컨트롤 플레인 로그의 활성화/비활성화 지침은 AWS 문서에서 확인할 수 있습니다.

+
+

Info

+

컨트롤 플레인 로깅을 활성화하면 로그를 CloudWatch에 저장하는 데 비용이 발생합니다. 이로 인해 지속적인 보안 비용에 대한 광범위한 문제가 제기됩니다. 궁극적으로 이런 비용을 보안 침해 비용 (예: 재정적 손실, 평판 훼손 등)과 비교해야 합니다. 이 가이드의 권장 사항 중 일부만 구현하면 환경을 적절하게 보호할 수 있을 것입니다.

+
+
+

Warning

+

클라우드워치 로그 항목의 최대 크기는 256KB인 반면 쿠버네티스 API 요청 최대 크기는 1.5MiB입니다. 256KB를 초과하는 로그 항목은 잘리거나 요청 메타데이터만 포함됩니다.

+
+

감사 메타데이터 활용

+

쿠버네티스 감사 로그에는 요청이 승인되었는지 여부를 나타내는 authorization.k8s.io/decision와 결정의 이유를 나타내는 authorization.k8s.io/reason, 두 개의 어노테이션이 포함되어 있습니다. 이런 속성을 사용하여 특정 API 호출이 허용된 이유를 확인할 수 있습니다.

+

의심스러운 이벤트에 대한 알람 생성

+

403 Forbidden 및 401 Unauthorized 응답이 증가하는 위치를 자동으로 알리는 경보를 생성한 다음 host , sourceIPsk8s_user.username 과 같은 속성을 사용 하여 이런 요청의 출처를 찾아냅니다.

+

Log Insights로 로그 분석

+

CloudWatch Log Insights를 사용하여 RBAC 객체 (예: 롤, 롤바인딩, 클러스터롤, 클러스터 롤바인딩) 에 대한 변경 사항을 모니터링할 수 있습니다. 몇 가지 샘플 쿼리는 다음과 같습니다.

+

aws-auth 컨피그맵 에 대한 업데이트를 나열합니다:

+
fields @timestamp, @message
+| filter @logStream like "kube-apiserver-audit"
+| filter verb in ["update", "patch"]
+| filter objectRef.resource = "configmaps" and objectRef.name = "aws-auth" and objectRef.namespace = "kube-system"
+| sort @timestamp desc
+
+

Validation 웹훅에 대한 생성 또는 변경 사항을 나열합니다:

+
fields @timestamp, @message
+| filter @logStream like "kube-apiserver-audit"
+| filter verb in ["create", "update", "patch"] and responseStatus.code = 201
+| filter objectRef.resource = "validatingwebhookconfigurations"
+| sort @timestamp desc
+
+

롤에 대한 생성, 업데이트, 삭제 작업을 나열합니다:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="roles" and verb in ["create", "update", "patch", "delete"]
+
+

롤바인딩에 대한 생성, 업데이트, 삭제 작업을 나열합니다:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="rolebindings" and verb in ["create", "update", "patch", "delete"]
+
+

클러스터롤에 대한 생성, 업데이트, 삭제 작업을 나열합니다:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="clusterroles" and verb in ["create", "update", "patch", "delete"]
+
+

클러스터롤바인딩에 대한 생성, 업데이트, 삭제 작업을 나열합니다:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="clusterrolebindings" and verb in ["create", "update", "patch", "delete"]
+
+

시크릿에 대한 무단 읽기 작업을 표시합니다:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="secrets" and verb in ["get", "watch", "list"] and responseStatus.code="401"
+| stats count() by bin(1m)
+
+

실패한 익명 요청 목록:

+
fields @timestamp, @message, sourceIPs.0
+| sort @timestamp desc
+| limit 100
+| filter user.username="system:anonymous" and responseStatus.code in ["401", "403"]
+
+

CloudTrail 로그 감사

+

IAM Roles for Service Account(IRSA)을 활용하는 파드에서 호출한 AWS API는 서비스 어카운트 이름과 함께 CloudTrail에 자동으로 로깅됩니다. API 호출 권한이 명시적으로 부여되지 않은 서비스 어카운트의 이름이 로그에 표시되면 IAM 역할의 신뢰 정책이 잘못 구성되었다는 표시일 수 있습니다. 일반적으로 Cloudtrail은 AWS API 호출을 특정 IAM 보안 주체에 할당할 수 있는 좋은 방법입니다.

+

CloudTrail Insights를 사용하여 의심스러운 활동 발견

+

CloudTrail Insights는 CloudTrail 트레일에서 쓰기 관리 이벤트를 자동으로 분석하고 비정상적인 활동이 발생하면 알려줍니다. 이를 통해 IRSA 기능을 사용하여 IAM 역할을 맡는 파드 등 AWS 계정의 쓰기 API에 대한 호출량이 증가하는 시기를 파악할 수 있습니다. 자세한 내용은 CloudTrail Insights 발표: 비정상적인 API 활동 식별 및 대응을 참조하십시오.

+

추가 리소스

+

로그의 양이 증가하면 Log Insights 또는 다른 로그 분석 도구를 사용하여 로그를 파싱하고 필터링하는 것이 비효율적일 수 있습니다. 대안으로 Sysdig Falcoekscloudwatch를 실행하는 것도 고려해 볼 수 있습니다. Falco는 감사 로그를 분석하고 오랜 기간 동안 이상 징후나 악용에 대해 플래그를 지정합니다. ekscloudwatch 프로젝트는 분석을 위해 CloudWatch의 감사 로그 이벤트를 팔코로 전달합니다. 팔코는 일련의 기본 감사 규칙과 함께 자체 감사 규칙을 추가할 수 있는 기능을 제공합니다.

+

또 다른 옵션은 감사 로그를 S3에 저장하고 SageMaker Random Cut Forest 알고리즘을 사용하여 추가 조사가 필요한 이상 동작에 사용하는 것일 수 있습니다.

+

도구 및 리소스

+

다음 상용 및 오픈 소스 프로젝트를 사용하여 클러스터가 확립된 모범 사례와 일치하는지 평가할 수 있습니다.

+
    +
  • kubeaudit
  • +
  • kube-scan 쿠버네티스 공통 구성 점수 산정 시스템 프레임워크에 따라 클러스터에서 실행 중인 워크로드에 위험 점수를 할당합니다.
  • +
  • kubesec.io
  • +
  • polaris
  • +
  • Starboard
  • +
  • Snyk
  • +
  • Kubescape Kubescape는 클러스터, YAML 파일 및 헬름 차트를 스캔하는 오픈 소스 쿠버네티스 보안 도구입니다. 여러 프레임워크 (NSA-CISAMITRE ATT&CK®)에 따라 설정 오류를 탐지합니다.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/hosts/index.html b/ko/security/docs/hosts/index.html new file mode 100644 index 000000000..828d60857 --- /dev/null +++ b/ko/security/docs/hosts/index.html @@ -0,0 +1,2517 @@ + + + + + + + + + + + + + + + + + + + + + + + 인프라 보안 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

인프라(호스트) 보호

+

컨테이너 이미지를 보호하는 것도 중요하지만 이미지를 실행하는 인프라를 보호하는 것도 마찬가지로 중요합니다. 이 섹션에서는 호스트를 대상으로 직접 시작된 공격으로 인한 위험을 완화하는 다양한 방법을 살펴봅니다. 이 지침은 런타임 보안 섹션에 설명된 지침과 함께 사용해야 합니다.

+

권장 사항

+

컨테이너 실행에 최적화된 OS 사용

+

Flatcar Linux, Project Atomic, RancherOS 및 리눅스 컨테이너 실행을 위해 설계된 AWS의 컨테이너 실행 최적화 OS인 Bottlerocket을 사용해 보세요. 이것은 공격 표면 감소, 부팅 시 검증된 디스크 이미지, SELinux를 사용한 권한 제한 등이 포함하고 있습니다.

+

또는 쿠버네티스 워커 노드에 EKS 최적화 AMI를 사용할 수 있습니다. EKS 최적화 AMI는 정기적으로 릴리스되며 컨테이너식 워크로드를 실행하는 데 필요한 최소한의 OS 패키지 및 바이너리 세트를 포함합니다.

+

워커 노드 OS를 최신 상태로 유지

+

Bottlerocket과 같은 컨테이너에 최적화된 호스트 OS를 사용하거나 EKS 최적화 AMI와 같은 Amazon 머신 이미지를 사용하고 최신 보안 패치를 사용하여 이런 호스트 OS 이미지를 최신 상태로 유지하는 것이 가장 좋습니다.

+

EKS 최적화 AMI의 경우 변경 로그 또는 릴리스 노트 채널를 정기적으로 확인하고 업데이트된 워커 노드 이미지를 클러스터로 자동 롤아웃합니다.

+

인프라를 변경할 수 없는 대상으로 분류하고 워커 노드 교체를 자동화하십시오

+

전체 업그레이드를 수행하는 대신 새 패치 또는 업데이트가 제공되면 워커 노드를 교체합니다. 몇 가지 방법으로 이 문제를 해결할 수 있습니다. 그룹의 모든 노드가 최신 AMI로 교체될 때까지 순차적으로 노드를 차단하고 드레이닝하는 최신 AMI를 사용하여 기존 자동 확장 그룹에 인스턴스를 추가할 수도 있습니다. 또는 모든 노드가 교체될 때까지 이전 노드 그룹에서 노드를 순차적으로 차단하고 제거하면서 새 노드 그룹에 인스턴스를 추가할 수도 있습니다. EKS 관리형 노드 그룹은 첫 번째 접근 방식을 사용하며 새 AMI를 사용할 수 있게 되면 콘솔에 작업자를 업그레이드하라는 메시지를 표시합니다. 또한 'eksctl'에는 최신 AMI로 노드 그룹을 생성하고 인스턴스가 종료되기 전에 노드 그룹에서 파드를 정상적으로 차단하고 드레이닝하는 메커니즘이 있습니다. 워커 노드를 교체하는 데 다른 방법을 사용하기로 결정한 경우, 새 업데이트/패치가 릴리스되고 컨트롤 플레인이 업그레이드될 때 작업자를 정기적으로 교체해야 할 수 있으므로 프로세스를 자동화하여 사람의 감독을 최소화하는 것이 좋습니다.

+

EKS Fargate를 사용하면 AWS는 업데이트가 제공되는 대로 기본 인프라를 자동으로 업데이트합니다.이 작업을 원활하게 수행할 수 있는 경우가 많지만 업데이트로 인해 파드 일정이 변경되는 경우가 있을 수 있습니다.따라서 애플리케이션을 Fargate 파드로 실행할 때는 여러 복제본으로 배포를 생성하는 것이 좋습니다.

+

kube-bench를 주기적으로 실행하여 쿠버네티스에 대한 CIS 벤치마크 준수 여부를 확인합니다

+

kube-bench는 쿠버네티스의 CIS 벤치마크와 비교하여 클러스터를 평가하는 Aqua의 오픈 소스 프로젝트입니다. 벤치마크는 관리되지 않는 쿠버네티스 클러스터를 보호하는 모범 사례를 설명합니다. CIS 쿠버네티스 벤치마크는 컨트롤 플레인과 데이터 플레인을 포함합니다. Amazon EKS는 완전 관리형 컨트롤 플레인을 제공하므로 CIS 쿠버네티스 벤치마크의 모든 권장 사항이 적용되는 것은 아닙니다. 이 범위에 Amazon EKS 구현 방식이 반영되도록 AWS는 CIS Amazon EKS 벤치마크를 만들었습니다. EKS 벤치마크는 CIS 쿠버네티스 벤치마크를 계승하고 EKS 클러스터의 특정 구성 고려 사항과 함께 커뮤니티의 추가 의견을 반영합니다.

+

EKS 클러스터에 대해 kube-bench를 실행할 때는 아쿠아 시큐리티의 이 지침을 따릅니다. 자세한 내용은 CIS Amazon EKS 벤치마크 소개를 참조합니다.

+

워커 노드에 대한 액세스 최소화

+

호스트에 원격으로 접속해야 할 때는 SSH 액세스를 활성화하는 대신 SSM Session Manager를 사용합니다. 분실, 복사 또는 공유될 수 있는 SSH 키와 달리 세션 관리자에서는 IAM을 사용하여 EC2 인스턴스에 대한 액세스를 제어할 수 있습니다. 또한 인스턴스에서 실행된 명령에 대한 감사 추적 및 로그를 제공합니다.

+

2020년 8월 19일부터 관리형 노드 그룹은 사용자 지정 AMI와 EC2 시작 템플릿(Launch Template)을 지원합니다. 이를 통해 SSM 에이전트를 AMI에 내장하거나 워커 노드가 부트스트랩될 때 설치할 수 있습니다. 최적화된 AMI 또는 ASG의 시작 템플릿을 수정하지 않는 경우, 이 예시에서처럼 데몬셋을 사용하여 SSM 에이전트를 설치할 수 있습니다.

+

SSM 기반 SSH 액세스를 위한 최소 IAM 정책

+

AmazonSSMManagedInstanceCore AWS 관리형 정책에는 SSH 액세스를 피하려는 경우 SSM Session Manager 및 SSM RunCommand에 필요하지 않은 여러 권한이 포함되어 있습니다. +특히 우려되는 것은 SSM:GetParameter (s)에 대한 * 권한입니다. 이렇게 하면 해당 역할이 파라미터 스토어의 모든 파라미터(AWS 관리형 KMS 키가 구성된 SecureString 포함)에 액세스할 수 있게 됩니다.

+

다음 IAM 정책에는 SSM Systems Manager를 통해 노드 액세스를 활성화하기 위한 최소 권한 세트가 포함되어 있습니다.

+
{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "EnableAccessViaSSMSessionManager",
+      "Effect": "Allow",
+      "Action": [
+        "ssmmessages:OpenDataChannel",
+        "ssmmessages:OpenControlChannel",
+        "ssmmessages:CreateDataChannel",
+        "ssmmessages:CreateControlChannel",
+        "ssm:UpdateInstanceInformation"
+      ],
+      "Resource": "*"
+    },
+    {
+      "Sid": "EnableSSMRunCommand",
+      "Effect": "Allow",
+      "Action": [
+        "ssm:UpdateInstanceInformation",
+        "ec2messages:SendReply",
+        "ec2messages:GetMessages",
+        "ec2messages:GetEndpoint",
+        "ec2messages:FailMessage",
+        "ec2messages:DeleteMessage",
+        "ec2messages:AcknowledgeMessage"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
+
+

이 정책을 적용하고 Session Manager 플러그인을 설치하면 다음을 실행하여 노드에 접속합니다.

+
aws ssm start-session --target [INSTANCE_ID_OF_EKS_NODE]
+
+
+

Note

+

Session Manager 로깅 활성화에 권한을 추가하는 것도 고려해 볼 수 있습니다.

+
+

프라이빗 서브넷에 워커 노드 배포

+

워커 노드를 프라이빗 서브넷에 배치하면 공격이 자주 발생하는 인터넷에 대한 노출을 최소화할 수 있습니다. 2020년 4월 22일부터 관리형 노드 그룹의 노드에 대한 퍼블릭 IP 주소 할당은 해당 노드가 배포되는 서브넷에 의해 제어됩니다. 이전에는 관리형 노드 그룹의 노드에 퍼블릭 IP가 자동으로 할당되었습니다. 워커 노드를 퍼블릭 서브넷에 배포하기로 선택한 경우, 제한적인 AWS 보안 그룹 규칙을 구현하여 노출을 제한합니다.

+

Amazon Inspector를 실행하여 호스트의 노출, 취약성 및 모범 사례와의 편차를 평가하십시오

+

Amazon Inspector를 사용하여 노드에 대한 의도하지 않은 네트워크 액세스와 기본 Amazon EC2 인스턴스의 취약성을 확인할 수 있습니다.

+

Amazon Inspector는 Amazon EC2 Systems Manager(SSM) 에이전트가 설치되고 활성화된 경우에만 Amazon EC2 인스턴스에 대한 일반적인 취약성 및 노출 (CVE) 데이터를 제공할 수 있습니다. 이 에이전트는 EKS 최적화 Amazon Linux AMI를 비롯한 여러 Amazon 머신 이미지 (AMI)에 사전 설치되어 있습니다. SSM 에이전트 상태에 관계없이 모든 Amazon EC2 인스턴스는 네트워크 연결 문제 여부를 검사합니다.Amazon EC2용 스캔 구성에 대한 자세한 내용은 Amazon EC2 인스턴스 스캔을 참조합니다.

+
+

Attention

+

Fargate 파드를 실행하는 데 사용되는 인프라에서는 Inspector를 실행할 수 없습니다.

+
+

대안으로 선택 가능한 옵션

+

SELinux 실행

+
+

Info

+

RHEL(Red Hat Enterprise Linux), CentOS, Bottlerocket과 Amazon Linux 2023 에서 사용 가능

+
+

SELinux는 컨테이너를 서로 격리하고 호스트로부터 격리된 상태로 유지하기 위한 추가 보안 계층을 제공합니다. SELinux를 통해 관리자는 모든 사용자, 애플리케이션, 프로세스 및 파일에 대해 필수 액세스 제어(MAC)를 적용할 수 있습니다. 레이블 집합을 기반으로 특정 리소스에 대해 수행할 수 있는 작업을 제한하는 안정장치라고 생각하면 됩니다. EKS에서는 SELinux를 사용하여 컨테이너가 서로의 리소스에 액세스하는 것을 방지할 수 있습니다.

+

컨테이너 SELinux 정책은 container-selinux 패키지에 정의되어 있습니다. Docker CE에는 Docker(또는 다른 컨테이너 런타임)에서 생성한 프로세스와 파일이 제한된 시스템 액세스로 실행되도록 하려면 이 패키지 (종속 항목 포함)가 필요합니다.컨테이너는 svirt_lxc_net_t의 별칭인 container_t 레이블을 활용합니다. 이런 정책은 컨테이너가 호스트의 특정 기능에 액세스하는 것을 효과적으로 방지합니다.

+

Docker용 SELinux를 구성하면 Docker는 워크로드에 container_t 레이블링하여 타입으로 자동으로 인식하고 각 컨테이너에 고유한 MCS 레벨을 부여합니다. 이렇게 하면 컨테이너가 서로 격리됩니다. 보다 엄격한 제한이 필요한 경우 SELinux에서 파일 시스템의 특정 영역에 대한 컨테이너 권한을 부여하는 자체 프로파일을 만들 수 있습니다. 이는 컨테이너/파드마다 다른 프로파일을 생성할 수 있다는 점에서 PSP와 비슷합니다. 예를 들어, 일련의 제한적인 제어가 포함된 일반 워크로드용 프로파일과 권한 있는 액세스가 필요한 항목에 대한 프로파일을 각각 가질 수 있습니다.

+

컨테이너용 SELinux에는 기본 제한을 수정하도록 구성할 수 있는 옵션 세트가 있습니다. 필요에 따라 다음과 같은 SELinux Booleans를 활성화하거나 비활성화할 수 있습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
BooleanDefaultDescription
container_connect_anyoff컨테이너가 호스트의 권한 있는 포트에 액세스할 수 있도록 허용합니다. 호스트의 443 또는 80에 포트를 매핑해야 하는 컨테이너가 있는 경우를 예로 들 수 있습니다.
container_manage_cgroupoff컨테이너가 cgroup 구성을 관리할 수 있도록 허용합니다. 예를 들어 systemd를 실행하는 컨테이너는 이 기능을 활성화해야 합니다.
container_use_cephfsoff컨테이너가 ceph 파일 시스템을 사용할 수 있도록 허용합니다.
+

기본적으로 컨테이너는 /usr에서 읽고 실행할 수 있으며 /etc에서 대부분의 콘텐츠를 읽을 수 있습니다. /var/lib/docker/var/lib/containers 아래의 파일에는 container_var_lib_t라는 레이블이 붙어 있습니다. 기본 레이블의 전체 목록을 보려면 container.fc 파일을 참조합니다.

+
docker container run -it \
+  -v /var/lib/docker/image/overlay2/repositories.json:/host/repositories.json \
+  centos:7 cat /host/repositories.json
+# cat: /host/repositories.json: Permission denied
+
+docker container run -it \
+  -v /etc/passwd:/host/etc/passwd \
+  centos:7 cat /host/etc/passwd
+# cat: /host/etc/passwd: Permission denied
+
+

container_file_t로 레이블이 지정된 파일은 컨테이너에서 쓸 수 있는 유일한 파일입니다. 볼륨 마운트를 쓰기 가능하게 하려면 끝에 :z 또는 :Z를 지정해야 합니다.

+
    +
  • :z 는 컨테이너가 읽고 쓸 수 있도록 파일의 레이블을 다시 지정합니다.
  • +
  • :Z 는 컨테이너 읽고 쓸 수 있도록 파일에 레이블을 다시 지정합니다.
  • +
+
ls -Z /var/lib/misc
+# -rw-r--r--. root root system_u:object_r:var_lib_t:s0   postfix.aliasesdb-stamp
+
+docker container run -it \
+  -v /var/lib/misc:/host/var/lib/misc:z \
+  centos:7 echo "Relabeled!"
+
+ls -Z /var/lib/misc
+#-rw-r--r--. root root system_u:object_r:container_file_t:s0 postfix.aliasesdb-stamp
+
+
docker container run -it \
+  -v /var/log:/host/var/log:Z \
+  fluentbit:latest
+
+

쿠버네티스에서는 레이블을 다시 지정하는 방식이 약간 다릅니다. Docker가 파일의 레이블을 자동으로 다시 지정하도록 하는 대신 사용자 지정 MCS 레이블을 지정하여 파드를 실행할 수 있습니다. 레이블 재지정을 지원하는 볼륨은 액세스할 수 있도록 자동으로 레이블이 다시 지정됩니다. MCS 레이블이 일치하는 파드는 해당 볼륨에 접근할 수 있다. 엄격한 격리가 필요한 경우 각 파드에 다른 MCS 레이블을 설정하세요.

+
securityContext:
+  seLinuxOptions:
+    # Provide a unique MCS label per container
+    # You can specify user, role, and type also
+    # enforcement based on type and level (svert)
+    level: s0:c144:c154
+
+

이 예제에서 s0:c144:c154는 컨테이너의 액세스가 허용된 파일에 할당된 MCS 레이블에 해당합니다.

+

EKS에서는 FluentD와 같은 권한 있는 컨테이너를 실행할 수 있는 정책을 생성하고 호스트 디렉터리에 레이블을 다시 지정할 필요 없이 호스트의 /var/log에서 읽을 수 있도록 허용하는 SELinux 정책을 생성할 수 있습니다. 레이블이 같은 파드는 동일한 호스트 볼륨에 액세스할 수 있습니다.

+

CentOS7 및 RHEL7에 SELinux가 구성된 Amazon EKS 샘플 AMI를 구현했습니다. 이런 AMI는 STIG, CJIS, C2S와 같이 규제가 엄격한 고객의 요구 사항을 충족하는 샘플 구현을 시연하기 위해 개발되었습니다.

+
+

Caution

+
+

SELinux는 타입이 제한되지 않은 컨테이너를 무시합니다.

+

도구 및 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/iam/index.html b/ko/security/docs/iam/index.html new file mode 100644 index 000000000..adfd07d3f --- /dev/null +++ b/ko/security/docs/iam/index.html @@ -0,0 +1,2990 @@ + + + + + + + + + + + + + + + + + + + + + + + 인증 및 접근 관리 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

인증 및 접근 관리

+

AWS IAM(Identity and Access Management)은 인증 및 권한 부여라는 두 가지 필수 기능을 수행하는 AWS 서비스입니다. 인증에는 자격 증명 확인이 포함되는 반면 권한 부여는 AWS 리소스에서 수행할 수 있는 작업을 관리합니다. AWS 내에서 리소스는 다른 AWS 서비스(예: EC2) 또는 IAM 사용자 또는 IAM 역할과 같은 AWS 보안 주체일 수 있습니다. 리소스가 수행할 수 있는 작업을 관리하는 규칙은 IAM 정책으로 표현됩니다.

+

EKS 클러스터에 대한 접근 제어

+

쿠버네티스 프로젝트는 베어러(Bearer) 토큰, X.509 인증서, OIDC 등 kube-apiserver 서비스에 대한 요청을 인증하기 위한 다양한 방식을 지원합니다. EKS는 현재 웹훅(Webhook) 토큰 인증, 서비스 어카운트 토큰 및 2021년 2월 21일부터 OIDC 인증을 기본적으로 지원합니다.

+

웹훅 인증 방식은 베어러 토큰을 확인하는 웹훅을 호출합니다. EKS에서 이런 베어러 토큰은 kubectl 명령 실행 시 AWS CLI 또는 aws-iam-authenticator 클라이언트에 의해 생성됩니다. 명령을 실행하면 토큰은 kube-apiserver로 전달되고 다시 웹훅으로 포워딩됩니다. 요청이 올바른 형식이면 웹훅은 토큰 본문에 포함된 미리 서명된 URL을 호출합니다. 이 URL은 요청 서명의 유효성을 검사하고 사용자 정보(사용자 어카운트, ARN 및 사용자 ID 등)를 kube-apiserver에 반환합니다.

+

인증 토큰을 수동으로 생성하려면 터미널 창에 다음 명령을 입력합니다.

+
aws eks get-token --cluster-name <클러스터_이름>
+
+

프로그래밍 방식으로 토큰을 얻을 수도 있습니다. 다음은 Go 언어로 작성된 예입니다.

+
package main
+
+import (
+ "fmt"
+ "log"
+ "sigs.k8s.io/aws-iam-authenticator/pkg/token"
+)
+
+func main()  {
+ g, _ := token.NewGenerator(false, false)
+ tk, err := g.Get("<cluster_name>")
+ if err != nil {
+  log.Fatal(err)
+ }
+ fmt.Println(tk)
+}
+
+

출력 응답은 다음과 형태를 가집니다.

+
{
+  "kind": "ExecCredential", 
+  "apiVersion": "client.authentication.k8s.io/v1alpha1", 
+  "spec": {}, 
+  "status": {
+    "expirationTimestamp": "2020-02-19T16:08:27Z", 
+    "token": "k8s-aws-v1.aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFKTkdSSUxLTlNSQzJXNVFBJTJGMjAyMDAyMTklMkZ1cy1lYXN0LTElMkZzdHMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDIwMDIxOVQxNTU0MjdaJlgtQW16LUV4cGlyZXM9NjAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JTNCeC1rOHMtYXdzLWlkJlgtQW16LVNpZ25hdHVyZT0yMjBmOGYzNTg1ZTMyMGRkYjVlNjgzYTVjOWE0MDUzMDFhZDc2NTQ2ZjI0ZjI4MTExZmRhZDA5Y2Y2NDhhMzkz"
+  }
+}
+
+

각 토큰은 k8s-aws-v1.으로 시작하고 base64로 인코딩된 문자열이 뒤따릅니다. 문자열은 디코딩하면 다음과 같은 형태를 가집니다.

+
https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=0KIAJPFRILKNSRC2W5QA%2F20200219%2F$REGION%2Fsts%2Faws4_request&X-Amz-Date=20200219T155427Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=BB0f8f3285e320ddb5e683a5c9a405301ad76546f24f28111fdad09cf648a393
+
+

토큰은 Amazon 자격 증명 크리덴셜 및 서명이 포함된 미리 서명된 URL로 구성됩니다. 자세한 내용은 GetCallerIdentity API 문서를 참조합니다.

+

토큰은 15분의 TTL 수명이 있고, 수명 종류 후에는 새 토큰을 생성해야 합니다. 이는 kubectl과 같은 클라이언트를 사용할 때 자동으로 처리 되지만 쿠버네티스 대시보드를 사용하는 경우 토큰이 만료될 때마다 새 토큰을 생성하고 다시 인증해야 합니다.

+

사용자 ID가 AWS IAM 서비스에 의해 인증되면 kube-apiserver 는 'kube-system' 네임스페이스에서 'aws-auth' 컨피그맵을 읽어 사용자와 연결할 RBAC 그룹을 결정합니다. aws-auth 컨피그맵 은 IAM 보안 주체(예: IAM 사용자 및 역할)와 쿠버네티스 RBAC 그룹 간의 정적 매핑을 생성하는 데 사용됩니다. RBAC 그룹은 쿠버네티스 롤바인딩 또는 클러스터롤바인딩에서 참조할 수 있습니다. 쿠버네티스 리소스(객체) 모음에 대해 수행할 수 있는 일련의 작업(동사)을 정의한다는 점에서 IAM 역할과 유사합니다.

+

권장 사항

+

인증에 서비스 어카운트 토큰을 사용하지 마세요

+

서비스 어카운트 토큰은 수명이 긴 정적 사용자 인증 정보입니다. 손상, 분실 또는 도난된 경우 공격자는 서비스 어카운트이 삭제될 때까지 해당 토큰과 관련된 모든 작업을 수행할 수 있습니다. 경우에 따라 클러스터 외부에서 쿠버네티스 API를 사용해야 하는 애플리케이션(예: CI/CD 파이프라인 애플리케이션)에 대한 예외를 부여해야 할 수 있습니다. 이런 애플리케이션이 EC2 인스턴스와 같은 AWS 인프라에서 실행되는 경우 대신 인스턴스 프로파일을 사용하고 이를 'aws-auth' 컨피그맵의 쿠버네티스 RBAC 역할에 매핑하는 것이 좋습니다.

+

AWS 리소스에 대한 최소 권한 액세스 사용

+

쿠버네티스 API에 액세스하기 위해 IAM 사용자에게 AWS 리소스에 대한 권한을 할당할 필요가 없습니다. IAM 사용자에게 EKS 클러스터에 대한 액세스 권한을 부여해야 하는 경우 특정 쿠버네티스 RBAC 그룹에 매핑되는 해당 사용자 의 'aws-auth' 컨피그맵에 항목을 생성합니다.

+

여러 사용자가 클러스터에 대해 동일한 액세스 권한이 필요한 경우 IAM 역할 사용

+

'aws-auth' 컨피그맵 에서 각 개별 IAM 사용자에 대한 항목을 생성하는 대신 해당 사용자가 IAM 역할을 수임하고 해당 역할을 쿠버네티스 RBAC 그룹에 매핑하도록 허용합니다. 특히 액세스가 필요한 사용자 수가 증가함에 따라 유지 관리가 더 쉬워집니다.

+
+

Attention

+

aws-auth 컨피그맵에 의해 매핑된 IAM 보안 주체로 EKS 클러스터에 액세스할 때 aws-auth 컨피그맵에 설명된 사용자 이름이 쿠버네티스 감사 로그의 사용자 필드에 기록됩니다. IAM 역할을 사용하는 경우 해당 역할을 맡는 실제 사용자는 기록되지 않으며 감사할 수 없습니다.

+
+

aws-auth 컨피그맵에서 mapRoles를 사용하여 K8s RBAC 권한을 IAM 역할에 할당할 때 사용자 이름에 {{SessionName}}을 포함해야 합니다. 이렇게 하면 감사 로그에 세션 이름이 기록되므로 CloudTrail 로그와 함께 이 역할을 맡은 실제 사용자를 추적할 수 있습니다.

+
- rolearn: arn:aws:iam::XXXXXXXXXXXX:role/testRole
+  username: testRole:{{SessionName}}
+  groups:
+    - system:masters
+
+

쿠버네티스 1.20 또는 이후 버전에서는 User.Extra.sessionName.0이 쿠버네티스 감사 로그에 추가되었으므로 이런 변경이 더 이상 필요하지 않습니다.

+

롤바인딩(RoleBinding) 및 클러스터롤바인딩(ClusterRoleBinding) 생성 시 최소 권한 접근 허용

+

AWS 리소스에 대한 액세스 권한 부여에 대한 이전 항목과 마찬가지로 롤바인딩 및 클러스터롤바인딩에는 특정 기능을 수행하는 데 필요한 권한 집합만 포함되어야 합니다. 절대적으로 필요한 경우가 아니면 롤(Role) 및 클러스터롤(ClusterRole)에서 ["*"] 를 사용하지 마십시오. 할당할 권한이 확실하지 않은 경우 audit2rbac과 같은 도구를 사용하여 쿠버네티스 감사 로그에서 관찰된 API 호출을 기반으로 역할 및 바인딩을 자동으로 생성하는 것이 좋습니다.

+

EKS 클러스터 엔드포인트를 프라이빗으로 설정

+

기본적으로 EKS 클러스터를 프로비저닝할 때 API 클러스터 엔드포인트는 퍼블릭으로 설정됩니다. 즉, 인터넷에서 액세스할 수 있습니다. 인터넷에서 액세스할 수 있음에도 불구하고 모든 API 요청이 IAM에 의해 인증되고 쿠버네티스 RBAC에 의해 승인되어야 하기 때문에 엔드포인트는 여전히 안전한 것으로 간주됩니다. 즉, 회사 보안 정책에 따라 인터넷에서 API에 대한 액세스를 제한하거나 클러스터 VPC 외부로 트래픽을 라우팅하지 못하도록 하는 경우 다음을 수행할 수 있습니다.

+
    +
  • EKS 클러스터 엔드포인트를 프라이빗으로 구성합니다. 이 주제에 대한 자세한 내용은 클러스터 엔드포인트 액세스 수정을 참조하십시오.
  • +
  • 클러스터 엔드포인트를 퍼블릭으로 두고 클러스터 엔드포인트와 통신할 수 있는 CIDR 블록을 지정합니다. 해당 블록은 클러스터 엔드포인트에 액세스할 수 있도록 허용된 퍼블릭 IP 주소 집합입니다.
  • +
  • 퍼블릭 엔드포인트는 접근이 허용된 화이트리스트 기반의 일부 CIDR 블록에만 허용하고 프라이빗 엔드포인트를 활성화합니다. 이렇게 하면 컨트롤 플레인이 프로비저닝될 때 클러스터 VPC에 프로비저닝되는 크로스 어카운트 ENI를 통해 kubelet과 쿠버네티스 API 사이의 모든 네트워크 트래픽을 강제하는 동시에 특정 퍼블릭 IP 범위의 퍼블릭 액세스가 허용됩니다.
  • +
+

전용 IAM 역할로 클러스터 생성

+

Amazon EKS 클러스터를 생성하면 클러스터를 생성하는 연동 사용자와 같은 IAM 엔터티 사용자 또는 역할에 클러스터의 RBAC 구성에서 system:masters 권한이 자동으로 부여됩니다. 이 액세스는 제거할 수 없으며 aws-auth 컨피그맵을 통해 관리되지 않습니다. 따라서 전용 IAM 역할로 클러스터를 생성하고 이 역할을 맡을 수 있는 사람을 정기적으로 감사하는 것이 좋습니다. 이 역할은 클러스터에서 일상적인 작업을 수행하는 데 사용되어서는 안 되며, 대신 이런 목적을 위해 aws-auth 컨피그맵을 통해 추가 사용자에게 클러스터에 대한 액세스 권한을 부여해야 합니다. aws-auth 컨피그맵이 구성된 이후에는 역할을 삭제할 수 있으며 aws-auth 컨피그맵이 손상되고 그렇지 않으면 클러스터에 액세스할 수 없는 긴급/유리 파손 시나리오에서만 다시 생성 할 수 있습니다. 이는 일반적으로 직접 사용자 액세스가 구성되지 않은 운영 클러스터에서 특히 유용할 수 있습니다.

+

도구를 사용하여 aws-auth 컨피그맵 변경

+

잘못된 형식의 aws-auth 컨피그맵으로 인해 클러스터에 대한 접근 권한을 잃을 수 있습니다. 컨피그맵을 변경해야 하는 경우 도구를 사용하십시오.

+

eksctl

+

eksctl CLI에는 aws-auth 컨피그맵에 ID 매핑을 추가하기 위한 명령이 포함되어 있습니다.

+

CLI 도움말 보기:

+
eksctl create iamidentitymapping --help
+
+

IAM 역할을 클러스터 관리자로 지정:

+
 eksctl create iamidentitymapping --cluster  <clusterName> --region=<region> --arn arn:aws:iam::123456:role/testing --group system:masters --username admin
+
+

자세한 내용은 eksctl 문서를 참조하십시오.

+

keikoproj의 aws-auth

+

keikoproj의 aws-auth 에는 cli 및 go 라이브러리가 모두 포함되어 있습니다.

+

CLI 도움말 다운로드 및 보기:

+
go get github.com/keikoproj/aws-auth
+aws-auth help
+
+

또는 kubectl용 krew 플러그인 관리자aws-auth 를 설치 합니다.

+
kubectl krew install aws-auth
+kubectl aws-auth
+
+

go 라이브러리를 비롯한 자세한 내용은 GitHub 내 aws-auth 문서를 확인하십시오.

+

AWS IAM Authenticator CLI

+

aws-iam-authenticator 프로젝트에는 컨피그맵을 업데이트하기 위한 CLI가 포함되어 있습니다.

+

GitHub에서 릴리스 다운로드하세요.

+

IAM 역할에 클러스터 권한을 추가합니다:

+
./aws-iam-authenticator add role --rolearn arn:aws:iam::185309785115:role/lil-dev-role-cluster --username lil-dev-user --groups system:masters --kubeconfig ~/.kube/config
+
+

클러스터에 대한 접근을 정기적으로 감사합니다

+

클러스터에 접근이 필요한 사람은 시간이 지남에 따라 변경될 수 있습니다. 주기적으로 aws-auth 컨피그맵을 감사하여 접근 권한이 부여된 사람과 할당된 권한을 확인하십시오. 특정 서비스 어카운트, 사용자 또는 그룹에 바인딩된 역할을 검사하기 위해 kubectl-who-can 또는 rbac-lookup과 같은 오픈 소스 도구를 사용할 수도 있습니다. 해당 주제에 대해서는 감사섹션에서 더 자세히 살펴 보겠습니다. 추가 아이디어는 NCC Group의 이 기사에서 찾을 수 있습니다.

+

인증 및 액세스 관리에 대한 대체 접근 방식

+

IAM은 EKS 클러스터에 액세스해야 하는 사용자를 인증하는 데 선호되는 방법이지만, 인증 프록시 또는 쿠버네티스 impersonation등을 사용하는 GitHub와 같은 OIDC ID 공급자를 사용할 수 있습니다. 이런 두 가지 솔루션에 대한 게시물이 AWS 오픈 소스 블로그에 게시되었습니다.

+ +
+

Attention

+

EKS는 기본적으로 프록시를 사용하지 않고 OIDC 인증을 지원합니다. 자세한 내용은 Amazon EKS에 대한 OIDC 자격 증명 공급자 인증 소개블로그를 참조하십시오. 다양한 인증 방법에 대한 커넥터를 제공하는 인기 있는 오픈 소스 OIDC 공급자인 Dex로 EKS를 구성하는 방법을 보여주는 예는 Dex 및 dex-k8s-authenticator를 사용하여 Amazon EKS 인증 블로그를 참조하세요. 블로그에 설명된 대로 OIDC 공급자가 인증한 사용자 이름/사용자 그룹은 쿠버네티스 감사 로그에 나타납니다.

+
+

또한 AWS SSO를 사용하여 Azure AD와 같은 외부 자격 증명 공급자와 AWS를 페더레이션할 수 있습니다. 이를 사용하기로 결정한 경우 AWS CLI v2.0에는 SSO 세션을 현재 CLI 세션과 쉽게 연결하고 IAM 역할을 수임할 수 있는 명명된 프로파일을 생성하는 옵션이 포함되어 있습니다. 사용자의 쿠버네티스 RBAC 그룹을 결정하는 데 IAM 역할이 사용되므로 kubectl 을 실행하기 "전" 역할을 수임(Assume)하여야 합니다.

+

추가 리소스

+

rbac.dev 쿠버네티스 RBAC에 대한 블로그 및 도구를 포함한 추가 리소스 목록

+

파드 아이덴티티

+

쿠버네티스 클러스터 내에서 실행되는 특정 애플리케이션은 제대로 작동하기 위해 쿠버네티스 API를 호출할 수 있는 권한이 필요합니다. 예를 들어 AWS 로드밸런서 컨트롤러는 서비스의 엔드포인트를 나열할 수 있어야 합니다. 또한 컨트롤러는 ALB를 프로비저닝하고 구성하기 위해 AWS API를 호출할 수 있어야 합니다. 이 섹션에서는 파드에 권한을 할당하는 모범 사례를 살펴봅니다.

+

쿠버네티스 서비스 어카운트

+

서비스 어카운트는 파드에 쿠버네티스 RBAC 역할을 할당할 수 있는 특수한 유형의 개체입니다. 클러스터 내의 각 네임스페이스에 대해 기본 서비스 어카운트이 자동으로 생성됩니다. 특정 서비스 어카운트을 참조하지 않고 네임스페이스에 파드를 배포하면, 해당 네임스페이스의 기본 서비스 어카운트이 자동으로 파드에 할당되고 시크릿, 즉 해당 서비스 아카운트의 서비스 어카운트 (JWT) 토큰은 /var/run/secrets/kubernetes.io/serviceaccount에서 볼륨으로 파드에 마운트됩니다. 해당 디렉터리의 서비스 어카운트 토큰을 디코딩하면 다음과 같은 메타데이터가 나타납니다.

+
{
+  "iss": "kubernetes/serviceaccount",
+  "kubernetes.io/serviceaccount/namespace": "default",
+  "kubernetes.io/serviceaccount/secret.name": "default-token-5pv4z",
+  "kubernetes.io/serviceaccount/service-account.name": "default",
+  "kubernetes.io/serviceaccount/service-account.uid": "3b36ddb5-438c-11ea-9438-063a49b60fba",
+  "sub": "system:serviceaccount:default:default"
+}
+
+

기본 서비스 어카운트에는 쿠버네티스 API에 대한 다음 권한이 있습니다.

+
apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  annotations:
+    rbac.authorization.kubernetes.io/autoupdate: "true"
+  creationTimestamp: "2020-01-30T18:13:25Z"
+  labels:
+    kubernetes.io/bootstrapping: rbac-defaults
+  name: system:discovery
+  resourceVersion: "43"
+  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/system%3Adiscovery
+  uid: 350d2ab8-438c-11ea-9438-063a49b60fba
+rules:
+- nonResourceURLs:
+  - /api
+  - /api/*
+  - /apis
+  - /apis/*
+  - /healthz
+  - /openapi
+  - /openapi/*
+  - /version
+  - /version/
+  verbs:
+  - get
+
+

이 역할은 인증되지 않은 사용자와 인증된 사용자가 API 정보를 읽을 수 있는 권한을 부여하며 공개적으로 액세스해도 안전한 것으로 간주됩니다.

+

파드 내에서 실행 중인 애플리케이션이 쿠버네티스 API를 호출할 때 해당 API를 호출할 수 있는 권한을 명시적으로 부여하는 서비스 어카운트를 파드에 할당해야 합니다. 사용자 접근에 대한 지침과 유사하게 서비스 어카운트에 바인딩된 Role 또는 ClusterRole은 애플리케이션이 작동하는 데 필요한 API 리소스 및 메서드로 제한되어야 합니다. 기본이 아닌 서비스 어카운트를 사용하려면 파드의 spec.serviceAccountName 필드를 사용하려는 서비스 어카운트의 이름으로 설정하기만 하면 됩니다. 서비스 어카운트 생성에 대한 추가 정보는 해당 문서를 참조하십시오.

+
+

Note

+

쿠버네티스 1.24 이전에는 쿠버네티스가 각 서비스 어카운트에 대한 암호를 자동으로 생성했습니다. 이 시크릿은 파드 내 /var/run/secrets/kubernetes.io/serviceaccount 경로로 마운트되었으며 파드에서 쿠버네티스 API 서버를 인증하는 데 사용됩니다. 쿠버네티스 1.24에서는 파드가 실행될 때 서비스 어카운트 토큰이 동적으로 생성되며 기본적으로 1시간 동안만 유효합니다. 서비스 어카운트의 시크릿은 생성되지 않습니다. Jenkins와 같이 쿠버네티스 API에 인증해야 하는 클러스터 외부에서 실행되는 애플리케이션이 있는 경우, metadata.annotations.kubernetes.io/service-account.name: <SERVICE_ACCOUNT_NAME>와 같은 서비스 어카운트를 참조하는 어노테이션과 함께 kubernetes.io/service-account-token 유형의 시크릿을 생성해야 한다. 이 방법으로 생성된 시크릿은 만료되지 않습니다.

+
+

서비스 어카운트용 IAM 역할(IRSA)

+

IRSA는 쿠버네티스 서비스 어카운트에 IAM 역할을 할당할 수 있는 기능입니다. Service Account Token Volume Projection이라는 쿠버네티스 기능을 활용하여 작동합니다. 파드가 IAM 역할을 참조하는 서비스 어카운트으로 구성된 경우 쿠버네티스 API 서버는 시작 시 클러스터에 대한 공개 OIDC 검색 엔드포인트를 호출합니다. 엔드포인트는 Kubernetes에서 발행한 OIDC 토큰에 암호로 서명하고, 생성된 토큰은 볼륨으로 마운트됩니다. 이 서명된 토큰을 통해 파드는 IAM 역할과 연결된 AWS API를 호출할 수 있습니다. AWS API가 호출되면 AWS SDK는 sts:AssumeRoleWithWebIdentity를 호출합니다. 토큰의 서명을 확인한 후 IAM은 쿠버네티스에서 발행한 토큰을 임시 AWS 역할 자격 증명으로 교환합니다.

+

IRSA에 대한 (JWT)토큰을 디코딩하면 아래에 표시된 예와 유사한 출력이 생성됩니다.

+
{
+  "aud": [
+    "sts.amazonaws.com"
+  ],
+  "exp": 1582306514,
+  "iat": 1582220114,
+  "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128",
+  "kubernetes.io": {
+    "namespace": "default",
+    "pod": {
+      "name": "alpine-57b5664646-rf966",
+      "uid": "5a20f883-5407-11ea-a85c-0e62b7a4a436"
+    },
+    "serviceaccount": {
+      "name": "s3-read-only",
+      "uid": "a720ba5c-5406-11ea-9438-063a49b60fba"
+    }
+  },
+  "nbf": 1582220114,
+  "sub": "system:serviceaccount:default:s3-read-only"
+}
+
+

이 특정 토큰은 파드에 S3 보기 전용 권한을 부여합니다. 애플리케이션이 S3에서 읽기를 시도하면 토큰이 다음과 유사한 임시 IAM 자격 증명 세트로 교환됩니다.

+
{
+    "AssumedRoleUser": {
+        "AssumedRoleId": "AROA36C6WWEJULFUYMPB6:abc", 
+        "Arn": "arn:aws:sts::123456789012:assumed-role/eksctl-winterfell-addon-iamserviceaccount-de-Role1-1D61LT75JH3MB/abc"
+    }, 
+    "Audience": "sts.amazonaws.com", 
+    "Provider": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128", 
+    "SubjectFromWebIdentityToken": "system:serviceaccount:default:s3-read-only", 
+    "Credentials": {
+        "SecretAccessKey": "ORJ+8Adk+wW+nU8FETq7+mOqeA8Z6jlPihnV8hX1", 
+        "SessionToken": "FwoGZXIvYXdzEGMaDMLxAZkuLpmSwYXShiL9A1S0X87VBC1mHCrRe/pB2oes+l1eXxUYnPJyC9ayOoXMvqXQsomq0xs6OqZ3vaa5Iw1HIyA4Cv1suLaOCoU3hNvOIJ6C94H1vU0siQYk7DIq9Av5RZe+uE2FnOctNBvYLd3i0IZo1ajjc00yRK3v24VRq9nQpoPLuqyH2jzlhCEjXuPScPbi5KEVs9fNcOTtgzbVf7IG2gNiwNs5aCpN4Bv/Zv2A6zp5xGz9cWj2f0aD9v66vX4bexOs5t/YYhwuwAvkkJPSIGvxja0xRThnceHyFHKtj0H+bi/PWAtlI8YJcDX69cM30JAHDdQH+ltm/4scFptW1hlvMaP+WReCAaCrsHrAT+yka7ttw5YlUyvZ8EPog+j6fwHlxmrXM9h1BqdikomyJU00gm1++FJelfP+1zAwcyrxCnbRl3ARFrAt8hIlrT6Vyu8WvWtLxcI8KcLcJQb/LgkW+sCTGlYcY8z3zkigJMbYn07ewTL5Ss7LazTJJa758I7PZan/v3xQHd5DEc5WBneiV3iOznDFgup0VAMkIviVjVCkszaPSVEdK2NU7jtrh6Jfm7bU/3P6ZG+CkyDLIa8MBn9KPXeJd/y+jTk5Ii+fIwO/+mDpGNUribg6TPxhzZ8b/XdZO1kS1gVgqjXyVC+M+BRBh6C4H21w/eMzjCtDIpoxt5rGKL6Nu/IFMipoC4fgx6LIIHwtGYMG7SWQi7OsMAkiwZRg0n68/RqWgLzBt/4pfjSRYuk=", 
+        "Expiration": "2020-02-20T18:49:50Z", 
+        "AccessKeyId": "0SIA12CFWWEJUMHACL7Z"
+    }
+}
+
+

EKS 컨트롤 플레인의 일부로 실행되는 Mutating 웹훅은 AWS 역할 ARN과 웹 자격 증명 토큰 파일의 경로를 환경 변수로 파드에 주입합니다. 이런 값은 수동으로 제공할 수도 있습니다.

+
AWS_ROLE_ARN=arn:aws:iam::AWS_ACCOUNT_ID:role/IAM_ROLE_NAME
+AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
+
+

kubelet은 총 TTL의 80%보다 오래되거나 24시간이 지나면 프로젝션된 토큰을 자동으로 교체합니다. AWS SDK는 토큰이 회전할 때 토큰을 다시 로드하는 역할을 합니다. IRSA에 대한 자세한 내용은 AWS 문서를 참조합니다.

+

포드 ID 권장 사항

+

IRSA를 사용하도록 aws-node 데몬셋 업데이트

+

현재 aws-node 데몬셋은 EC2 인스턴스에 할당된 역할을 사용하여 파드에 IP를 할당하도록 구성되어 있습니다. 이 역할에는 AmazonEKS_CNI_Policy 및 EC2ContainerRegistryReadOnly와 같이 노드에서 실행 중인 모든 파드가 ENI를 연결/분리하거나, IP 주소를 할당/할당 해제하거나, ECR에서 이미지를 가져오도록 효과적으로 허용하는 몇 가지 AWS 관리형 정책이 포함됩니다. 이는 클러스터에 위험을 초래하므로 IRSA를 사용하도록 aws-node 데몬셋을 업데이트하는 것이 좋습니다. 이 작업을 수행하기 위한 스크립트는 이 가이드의 리파지토리에서 찾을 수 있습니다.

+

워커 노드에 할당된 인스턴스 프로파일에 대한 접근 제한

+

IRSA를 사용하면 IRSA 토큰을 사용하도록 파드의 자격 증명 체인을 업데이트하지만 파드는 워커 노드에 할당된 인스턴스 프로파일의 권한을 계속 상속할 수 있습니다. IRSA 사용 시 허용되지 않은 권한의 범위를 최소화하기 위해 인스턴스 메타데이터 액세스를 차단하는 것이 강력하게 권장됩니다.

+
+

Caution

+

인스턴스 메타데이터에 대한 액세스를 차단하면 IRSA를 사용하지 않는 파드가 워커 노드에 할당된 역할을 상속받지 못합니다.

+
+

아래 예와 같이 인스턴스가 IMDSv2만 사용하도록 하고 홉 제한을 1로 업데이트하여 인스턴스 메타데이터에 대한 액세스를 차단할 수 있습니다. 노드 그룹의 시작 템플릿에 이런 설정을 포함할 수도 있습니다. 인스턴스 메타데이터를 비활성화 하지마세요. 이렇게 하면 노드 종료 핸들러와 같은 구성 요소와 인스턴스 메타데이터에 의존하는 기타 요소가 제대로 작동하지 않습니다.

+
aws ec2 modify-instance-metadata-options --instance-id <value> --http-tokens required --http-put-response-hop-limit 1
+
+

Terraform을 사용하여 관리형 노드 그룹과 함께 사용할 시작 템플릿을 만드는 경우 메타데이터 블록을 추가하여 다음 코드 스니펫에 표시된 대로 홉 수를 구성하십시오.

+
resource "aws_launch_template" "foo" {
+  name = "foo"
+  ...
+    metadata_options {
+    http_endpoint               = "enabled"
+    http_tokens                 = "required"
+    http_put_response_hop_limit = 1
+    instance_metadata_tags      = "enabled"
+  }
+  ...
+
+

노드에서 iptables를 조작하여 EC2 메타데이터에 대한 파드의 액세스를 차단할 수도 있습니다. 이 방법에 대한 자세한 내용은 인스턴스 메타데이터 서비스에 대한 액세스 제한 문서를 참조하세요.

+

IRSA를 지원하지 않는 이전 버전의 AWS SDK를 사용하는 애플리케이션이 있는 경우 SDK 버전을 업데이트해야 합니다.

+

IRSA에 대한 IAM 역할 신뢰 정책의 범위를 서비스 어카운트 이름으로 지정합니다

+

신뢰 정책은 네임스페이스 또는 네임스페이스 내의 특정 서비스 어카운트로 범위를 지정할 수 있습니다. IRSA를 사용하는 경우 서비스 어카운트 이름을 포함하여 역할 신뢰 정책을 가능한 한 명시적으로 만드는 것이 가장 좋습니다. 이렇게 하면 동일한 네임스페이스 내의 다른 파드가 역할을 맡는 것을 효과적으로 방지할 수 있습니다. CLI eksctl 은 서비스 어카운트/IAM 역할을 생성하는 데 사용할 때 이 작업을 자동으로 수행합니다. 자세한 내용은 eksctl 문서를 참조하세요.

+

애플리케이션이 IMDS에 액세스해야 하는 경우 IMDSv2를 사용하고 EC2 인스턴스의 홉 제한을 2로 늘리세요

+

IMDSv2에서는 PUT 요청을 사용하여 세션 토큰을 가져와야 합니다. 초기 PUT 요청에는 세션 토큰에 대한 TTL이 포함되어야 합니다. 최신 버전의 AWS SDK는 이를 처리하고 해당 토큰의 갱신을 자동으로 처리합니다. 또한 IP 전달을 방지하기 위해 EC2 인스턴스의 기본 홉 제한이 의도적으로 1로 설정되어 있다는 점에 유의해야 합니다. 결과적으로 EC2 인스턴스에서 실행되는 세션 토큰을 요청하는 파드는 결국 시간 초과되어 IMDSv1 데이터 흐름을 사용하도록 대체될 수 있습니다. EKS는 v1과 v2를 모두 _활성화_하고 eksctl 또는 공식 CloudFormation 템플릿으로 프로비저닝된 노드에서 홉 제한을 2로 변경하여 지원 IMDSv2를 추가합니다.

+

서비스 어카운트 토큰 자동 마운트 비활성화

+

애플리케이션이 Kubernetes API를 호출할 필요가 없는 경우 애플리케이션 의 PodSpec에서 automountServiceAccountToken 속성을 false로 설정하거나 각 네임스페이스의 기본 서비스 어카운트을 패치하여 더 이상 파드에 자동으로 마운트되지 않도록 합니다. 예:

+
kubectl patch serviceaccount default -p $'automountServiceAccountToken: false'
+
+

각 애플리케이션에 전용 서비스 어카운트 사용

+

각 애플리케이션에는 고유한 전용 서비스 어카운트이 있어야 합니다. 이는 쿠버네티스 API 및 IRSA의 서비스 어카운트에 적용됩니다.

+
+

Attention

+

전체 클러스터 업그레이드를 수행하는 대신 클러스터 업그레이드에 블루/그린 접근 방식을 사용하는 경우 각 IRSA IAM 역할의 신뢰 정책을 새 클러스터의 OIDC 엔드포인트로 업데이트해야 합니다. 블루/그린 클러스터 업그레이드는 이전 클러스터와 함께 최신 버전의 쿠버네티스를 실행하는 클러스터를 생성하고 로드밸런서 또는 서비스 메시를 사용하여 이전 클러스터에서 실행되는 서비스에서 새 클러스터로 트래픽을 원활하게 이동하는 것입니다.

+
+

루트가 아닌 사용자로 애플리케이션 실행

+

컨테이너는 기본적으로 루트로 실행됩니다. 이렇게 하면 웹 자격 증명 토큰 파일을 읽을 수 있지만 컨테이너를 루트로 실행하는 것은 모범 사례로 간주되지 않습니다. 또는 PodSpec에 spec.securityContext.runAsUser 속성을 추가하는 것이 좋습니다. runAsUser 의 값 은 임의의 값입니다.

+

다음 예제에서 파드 내의 모든 프로세스는 RunAsUser 필드에 지정된 사용자 ID로 실행됩니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: security-context-demo
+spec:
+  securityContext:
+    runAsUser: 1000
+    runAsGroup: 3000
+  containers:
+  - name: sec-ctx-demo
+    image: busybox
+    command: [ "sh", "-c", "sleep 1h" ]
+
+

루트가 아닌 사용자로 컨테이너를 실행하면 기본적으로 토큰에 0600 [Root] 권한이 할당되기 때문에 컨테이너가 IRSA 서비스 어카운트 토큰을 읽을 수 없습니다. fsgroup=65534 [Nobody]를 포함하도록 컨테이너의 securityContext를 업데이트하면 컨테이너가 토큰을 읽을 수 있습니다.

+
spec:
+  securityContext:
+    fsGroup: 65534
+
+

Kubernetes 1.19 및 이후 버전에서는 이 변경이 더 이상 필요하지 않습니다.

+

애플리케이션에 대한 최소 접근 권한 부여

+

Action Hero는 애플리케이션이 제대로 작동하는 데 필요한 AWS API 호출 및 해당 IAM 권한을 식별하기 위해 애플리케이션과 함께 실행할 수 있는 유틸리티입니다. 애플리케이션에 할당된 IAM 역할의 범위를 점진적으로 제한하는 데 도움이 된다는 점에서 IAM Access Advisor와 유사합니다. 자세한 내용은 AWS 리소스에 최소 접근 권한 부여에 대한 설명서를 참조하십시오.

+

불필요한 익명 접근 검토 및 철회

+

이상적으로는 모든 API 작업에 대해 익명의 접근을 비활성화하여야 합니다. 쿠버네티스 기본 제공 사용자 system:anonymous에 대한 RoleBinding 또는 ClusterRoleBinding을 생성하여 익명 액세스 권한을 부여합니다. rbac-lookup 도구를 사용하여 system:anonymous 사용자가 클러스터에 대해 갖는 권한을 식별할 수 있습니다.

+
./rbac-lookup | grep -P 'system:(anonymous)|(unauthenticated)'
+system:anonymous               cluster-wide        ClusterRole/system:discovery
+system:unauthenticated         cluster-wide        ClusterRole/system:discovery
+system:unauthenticated         cluster-wide        ClusterRole/system:public-info-viewer
+
+

system:public-info-viewer외의 ClusterRole 또는 모든 역할은 system:anonymous 사용자 또는 system:unauthenticated 그룹에 바인딩되지 않아야 합니다.

+

특정 API에서 익명 액세스를 활성화해야 하는 정당한 이유가 있을 수 있습니다. 클러스터의 경우 익명 사용자가 특정 API만 액세스할 수 있도록 하고 인증 없이 해당 API를 노출해도 클러스터가 취약해지지 않도록 해야 합니다.

+

Kubernetes/EKS 버전 1.14 이전에는 system:unauthenticated 그룹이 기본적으로 system:discovery 및 system:basic-user 클러스터 역할에 연결되었습니다. 클러스터를 버전 1.14 이상으로 업데이트했더라도 클러스터를 업데이트해도 이런 권한이 취소되지 않으므로 클러스터에서 이런 권한이 계속 활성화될 수 있습니다. +system:public-info-viewer를 제외하고 어떤 ClusterRole에 "system:unauthenticated"가 있는지 확인하려면 다음 명령을 실행할 수 있습니다(jq 유틸리티가 필요합니다):

+
kubectl get ClusterRoleBinding -o json | jq -r '.items[] | select(.subjects[]?.name =="system:unauthenticated") | select(.metadata.name != "system:public-info-viewer") | .metadata.name'
+
+

그리고 "system:unauthenticated"는 아래 명령을 사용하여 "system:public-info-viewer"를 제외한 모든 역할에서 제거할 수 있습니다.

+
kubectl get ClusterRoleBinding -o json | jq -r '.items[] | select(.subjects[]?.name =="system:unauthenticated") | select(.metadata.name != "system:public-info-viewer") | del(.subjects[] | select(.name =="system:unauthenticated"))' | kubectl apply -f -
+
+

또는 kubectl describe 및 kubectl edit을 사용하여 수동으로 확인하고 제거할 수 있다. system:unauthenticated 그룹에 클러스터에 대한 system:discovery 권한이 있는지 확인하려면 다음 명령을 실행하십시오.

+
kubectl describe clusterrolebindings system:discovery
+
+Name:         system:discovery
+Labels:       kubernetes.io/bootstrapping=rbac-defaults
+Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
+Role:
+  Kind:  ClusterRole
+  Name:  system:discovery
+Subjects:
+  Kind   Name                    Namespace
+  ----   ----                    ---------
+  Group  system:authenticated
+  Group  system:unauthenticated
+
+

system:unauthenticated 그룹에 클러스터에 대한 system:basic-user 권한이 있는지 확인하려면 다음 명령을 실행합니다.

+
kubectl describe clusterrolebindings system:basic-user
+
+Name:         system:basic-user
+Labels:       kubernetes.io/bootstrapping=rbac-defaults
+Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
+Role:
+  Kind:  ClusterRole
+  Name:  system:basic-user
+Subjects:
+  Kind   Name                    Namespace
+  ----   ----                    ---------
+  Group  system:authenticated
+  Group  system:unauthenticated
+
+

system:unauthenticated 그룹이 클러스터의 system:discovery 및/또는 system:basic-user ClusterRoles에 바인딩된 경우 이런 역할을 system:unauthenticated 그룹에서 분리해야 합니다. 다음 명령을 사용하여 system:discovery ClusterRoleBinding을 편집합니다:

+
kubectl edit clusterrolebindings system:discovery
+
+

위 명령은 아래와 같이 편집기에서 system:discovery ClusterRoleBinding의 현재 정의를 엽니다:

+
# Please edit the object below. Lines beginning with a '#' will be ignored,
+# and an empty file will abort the edit. If an error occurs while saving this file will be
+# reopened with the relevant failures.
+#
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  annotations:
+    rbac.authorization.kubernetes.io/autoupdate: "true"
+  creationTimestamp: "2021-06-17T20:50:49Z"
+  labels:
+    kubernetes.io/bootstrapping: rbac-defaults
+  name: system:discovery
+  resourceVersion: "24502985"
+  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterrolebindings/system%3Adiscovery
+  uid: b7936268-5043-431a-a0e1-171a423abeb6
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: system:discovery
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: Group
+  name: system:authenticated
+- apiGroup: rbac.authorization.k8s.io
+  kind: Group
+  name: system:unauthenticated
+
+

위 편집기 화면의 "Subjects" 섹션에서 system:unauthenticated 그룹 항목을 삭제합니다.

+

system:basic-user ClusterRoleBinding에 대해 동일한 단계를 반복합니다.

+

대체 접근 방식

+

IRSA는 파드에 AWS "ID"를 할당하는 _선호 되는 방법_이지만 애플리케이션에 최신 버전의 AWS SDK를 포함해야 합니다. 현재 IRSA를 지원하는 SDK의 전체 목록은 AWS 문서를 참조합니다. IRSA 호환 SDK로 즉시 업데이트할 수 없는 애플리케이션이 있는 경우, kube2iamkiam 을 포함하여 쿠버네티스 파드에 IAM 역할을 할당하는 데 사용할 수 있는 몇 가지 커뮤니티 구축 솔루션이 있습니다. AWS는 이런 솔루션의 사용을 보증하거나 용인하지 않지만 IRSA와 유사한 결과를 얻기 위해 커뮤니티에서 자주 사용합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/image/index.html b/ko/security/docs/image/index.html new file mode 100644 index 000000000..15626345f --- /dev/null +++ b/ko/security/docs/image/index.html @@ -0,0 +1,2660 @@ + + + + + + + + + + + + + + + + + + + + + + + 이미지 보안 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

이미지 보안

+

컨테이너 이미지는 공격에 대한 첫 번째 방어선으로 고려하여야 합니다. 안전하지 않고 잘못 구성된 이미지는 공격자는 컨테이너의 경계를 벗어나 호스트에 액세스할 수 있도록 허용합니다. 호스트에 들어가면 공격자는 민감한 정보에 액세스하거나 클러스터 내 또는 AWS 계정 내에 접근할 수 있습니다. 다음 모범 사례는 이런 상황이 발생할 위험을 완화하는 데 도움이 됩니다.

+

권장 사항

+

최소 이미지 생성

+

먼저 컨테이너 이미지에서 필요없는 바이너리를 모두 제거합니다. Dockerhub로부터 검증되지 않은 이미지를 사용하는 경우 각 컨테이너 레이어의 내용을 볼 수 있는 Dive와 같은 애플리케이션을 사용하여 이미지를 검사합니다. 권한을 상승할 수 있는 SETUID 및 SETGID 비트가 있는 모든 바이너리를 제거하고 nc나 curl과 같이 악의적인 용도로 사용될 수 있는 셸과 유틸리티를 모두 제거하는 것을 고려합니다. 다음 명령을 사용하여 SETUID 및 SETGID 비트가 있는 파일을 찾을 수 있습니다.

+
find / -perm /6000 -type f -exec ls -ld {} \;
+
+

이런 파일에서 특수 권한을 제거하려면 컨테이너 이미지에 다음 지시문을 추가합니다.

+
RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} \; || true
+
+

멀티 스테이지 빌드 사용

+

멀티 스테이지 빌드를 사용하면 최소한의 이미지를 만들 수 있습니다. 지속적 통합 주기의 일부를 자동화하는 데 멀티 스테이지 빌드를 사용하는 경우가 많습니다. 예를 들어 멀티 스테이지 빌드를 사용하여 소스 코드를 린트하거나 정적 코드 분석을 수행할 수 있습니다. 이를 통해 개발자는 파이프라인 실행을 기다릴 필요 없이 거의 즉각적인 피드백을 받을 수 있습니다. 멀티 스테이지 빌드는 컨테이너 레지스트리로 푸시되는 최종 이미지의 크기를 최소화할 수 있기 때문에 보안 관점에서 매력적입니다. 빌드 도구 및 기타 관련 없는 바이너리가 없는 컨테이너 이미지는 이미지의 공격 표면을 줄여 보안 상태를 개선합니다. 멀티 스테이지 빌드에 대한 추가 정보는 본 문서를 참조합니다.

+

컨테이너 이미지를 위한 소프트웨어 재료 명세서 (SBOM, Software Bill of Materials) 생성

+

SBOM은 컨테이너 이미지를 구성하는 소프트웨어 아티팩트의 중첩된 인벤토리입니다. +SBOM은 소프트웨어 보안 및 소프트웨어 공급망 위험 관리의 핵심 구성 요소입니다. SBOM을 생성하여 중앙 리포지토리에 저장하고 SBOM의 취약성 검사는 다음과 같은 문제를 해결하는 데 도움이 됩니다.

+
    +
  • 가시성: 컨테이너 이미지를 구성하는 구성 요소를 이해합니다. 중앙 리포지토리에 저장하면 배포 이후에도 언제든지 SBOM을 감사 및 스캔하여 제로 데이 취약성과 같은 새로운 취약성을 탐지하고 이에 대응할 수 있습니다.
  • +
  • 출처 검증: 아티팩트의 출처 및 출처에 대한 기존 가정이 사실이고 빌드 또는 제공 프로세스 중에 아티팩트 또는 관련 메타데이터가 변조되지 않았음을 보증합니다.
  • +
  • 신뢰성: 특정 유물과 그 내용물이 의도한 작업, 즉 목적에 적합하다는 것을 신뢰할 수 있다는 보장. 여기에는 코드를 실행하기에 안전한지 판단하고 코드 실행과 관련된 위험에 대해 정보에 입각한 결정을 내리는 것이 포함됩니다. 인증된 SBOM 및 인증된 CVE 스캔 보고서와 함께 검증된 파이프라인 실행 보고서를 작성하여 이미지 소비자에게 이 이미지가 실제로 보안 구성 요소를 갖춘 안전한 수단 (파이프라인) 을 통해 생성되었음을 확인하면 신뢰성이 보장됩니다.
  • +
  • 종속성 신뢰 확인: 아티팩트의 종속성 트리가 사용하는 아티팩트의 신뢰성과 출처를 반복적으로 검사합니다. SBOM의 드리프트는 신뢰할 수 없는 무단 종속성, 침입 시도 등 악의적인 활동을 탐지하는 데 도움이 될 수 있습니다.
  • +
+

다음 도구를 사용하여 SBOM을 생성할 수 있습니다:

+
    +
  • Amazon Inspector를 사용하여 SBOM 생성 및 내보내기를 수행할 수 있습니다.
  • +
  • Syft from Anchore 는 SBOM 생성에도 사용할 수 있습니다. 취약성 스캔을 더 빠르게 하기 위해 컨테이너 이미지에 대해 생성된 SBOM을 스캔을 위한 입력으로 사용할 수 있습니다. 그런 다음 검토 및 감사 목적으로 이미지를 Amazon ECR과 같은 중앙 OCI 리포지토리로 푸시하기 전에 SBOM 및 스캔 보고서를 이미지에 증명 및 첨부합니다.
  • +
+

CNCF 소프트웨어 공급망 모범 사례 가이드를 검토하고 소프트웨어 공급망 보안에 대해 자세히 알아보세요.

+

취약점이 있는지 이미지를 정기적으로 스캔

+

가상 머신과 마찬가지로 컨테이너 이미지에는 취약성이 있는 바이너리와 애플리케이션 라이브러리가 포함되거나 시간이 지남에 따라 취약성이 발생할 수 있습니다. 악용으로부터 보호하는 가장 좋은 방법은 이미지 스캐너로 이미지를 정기적으로 스캔하는 것입니다. Amazon ECR에 저장된 이미지는 푸시 또는 온디맨드로 스캔할 수 있습니다. (24시간 동안 한 번) ECR은 현재 두 가지 유형의 스캔 - 기본 및 고급을 지원합니다. 기본 스캔은 Clair의 오픈 소스 이미지 스캔 솔루션을 무료로 활용합니다. 고급 스캔추가 비용이 과금되며 Amazon Inspector를 사용하여 자동 연속 스캔을 제공합니다. 이미지를 스캔한 후 결과는 EventBridge의 ECR용 이벤트 스트림에 기록됩니다. ECR 콘솔 내에서 스캔 결과를 볼 수도 있습니다. 심각하거나 심각한 취약성이 있는 이미지는 삭제하거나 다시 빌드해야 합니다. 배포된 이미지에서 취약점이 발견되면 가능한 한 빨리 교체해야 합니다.

+

취약성이 있는 이미지가 배포된 위치를 아는 것은 환경을 안전하게 유지하는 데 필수적입니다. 이미지 추적 솔루션을 직접 구축할 수도 있지만, 다음과 같이 이 기능을 비롯한 기타 고급 기능을 즉시 사용할 수 있는 상용 제품이 이미 여러 개 있습니다.

+ +

Kubernetes 검증 웹훅을 사용하여 이미지에 심각한 취약점이 없는지 검증할 수도 있습니다.검증 웹훅은 쿠버네티스 API보다 먼저 호출됩니다. 일반적으로 웹훅에 정의된 검증 기준을 준수하지 않는 요청을 거부하는 데 사용됩니다.이 블로그는 ECR DescribeImagesCanVinds API를 호출하여 파드가 심각한 취약성이 있는 이미지를 가져오는지 여부를 확인하는 서버리스 웹훅을 소개합니다. 취약성이 발견되면 파드가 거부되고 CVE 목록이 포함된 메시지가 이벤트로 반환됩니다.

+

증명(Attestation)을 사용하여 아티팩트 무결성 검증

+

증명이란 특정 사물 (예: 파이프라인 실행, SBOM) 또는 취약성 스캔 보고서와 같은 다른 사물에 대한 "전제 조건" 또는 "주제", 즉 컨테이너 이미지를 주장하는 암호화 방식으로 서명된 "진술"입니다.

+

증명을 통해 사용자는 아티팩트가 소프트웨어 공급망의 신뢰할 수 있는 출처에서 나온 것인지 검증할 수 있습니다.예를 들어 이미지에 포함된 모든 소프트웨어 구성 요소나 종속성을 알지 못한 상태에서 컨테이너 이미지를 사용할 수 있습니다. 하지만 컨테이너 이미지 제작자가 어떤 소프트웨어가 존재하는지에 대해 말하는 내용을 신뢰할 수 있다면 제작자의 증명을 이용해 해당 아티팩트를 신뢰할 수 있습니다. 즉, 직접 분석을 수행하는 대신 워크플로우에서 아티팩트를 안전하게 사용할 수 있습니다.

+
    +
  • 증명은 AWS Signer 또는 Sigstore cosign을 사용하여 생성할 수 있습니다.
  • +
  • Kyverno와 같은 쿠버네티스 어드미션 컨트롤러를 사용하여 증명 확인을 할 수 있습니다.
  • +
  • 컨테이너 이미지에 증명 생성 및 첨부를 포함한 주제와 함께 오픈 소스 도구를 사용하는 AWS의 소프트웨어 공급망 관리 모범 사례에 대해 자세히 알아보려면 이 워크샵을을 참조합니다.
  • +
+

ECR 리포지토리에 대한 IAM 정책 생성

+

조직에서 공유 AWS 계정 내에서 독립적으로 운영되는 여러 개발 팀이 있는 경우가 드물지 않습니다. 이런 팀이 자산을 공유할 필요가 없는 경우 각 팀이 상호 작용할 수 있는 리포지토리에 대한 액세스를 제한하는 IAM 정책 세트를 만드는 것이 좋습니다. 이를 구현하는 좋은 방법은 ECR 네임스페이스를 사용하는 것입니다. 네임스페이스는 유사한 리포지토리를 그룹화하는 방법입니다. 예를 들어 팀 A의 모든 레지스트리 앞에 team-a/를 붙이고 팀 B의 레지스트리 앞에는 team-b/ 접두사를 사용할 수 있습니다. 액세스를 제한하는 정책은 다음과 같을 수 있습니다.

+
{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "AllowPushPull",
+      "Effect": "Allow",
+      "Action": [
+        "ecr:GetDownloadUrlForLayer",
+        "ecr:BatchGetImage",
+        "ecr:BatchCheckLayerAvailability",
+        "ecr:PutImage",
+        "ecr:InitiateLayerUpload",
+        "ecr:UploadLayerPart",
+        "ecr:CompleteLayerUpload"
+      ],
+      "Resource": [
+        "arn:aws:ecr:<region>:<account_id>:repository/team-a/*"
+      ]
+    }
+  ]
+}
+
+

ECR 프라이빗 엔드포인트 사용 고려

+

ECR API에는 퍼블릭 엔드포인트가 있습니다. 따라서 IAM에서 요청을 인증하고 승인하기만 하면 인터넷에서 ECR 레지스트리에 액세스할 수 있습니다. 클러스터 VPC에 IGW(인터넷 게이트웨이)가 없는 샌드박스 환경에서 운영해야 하는 경우 ECR용 프라이빗 엔드포인트를 구성할 수 있습니다. 프라이빗 엔드포인트를 생성하면 인터넷을 통해 트래픽을 라우팅하는 대신 프라이빗 IP 주소를 통해 ECR API에 비공개로 액세스할 수 있습니다. 이 주제에 대한 추가 정보는 Amazon ECR 인터페이스 VPC 엔드포인트를 참조합니다.

+

ECR 엔드포인트 정책 구현

+

기본 엔드포인트 정책은 리전 내의 모든 ECR 리포지토리에 대한 액세스를 허용합니다. 이로 인해 공격자/내부자가 데이터를 컨테이너 이미지로 패키징하고 다른 AWS 계정의 레지스트리로 푸시하여 데이터를 유출할 수 있습니다. 이 위험을 완화하려면 ECR 리포지토리에 대한 API 액세스를 제한하는 엔드포인트 정책을 생성해야 합니다. 예를 들어 다음 정책은 계정의 모든 AWS 원칙이 ECR 리포지토리에 대해서만 모든 작업을 수행하도록 허용합니다.

+
{
+  "Statement": [
+    {
+      "Sid": "LimitECRAccess",
+      "Principal": "*",
+      "Action": "*",
+      "Effect": "Allow",
+      "Resource": "arn:aws:ecr:<region>:<account_id>:repository/*"
+    }
+  ]
+}
+
+

AWS 조직에 속하지 않은 IAM 원칙에 의한 이미지 푸시/풀링을 방지하는 새로운 PrincipalOrGid 속성을 사용하는 조건을 설정하여 이를 더욱 개선할 수 있습니다. 자세한 내용은 AWS:PrincipalorgID를 참조하십시오. +com.amazonaws.<region>.ecr.dkrcom.amazonaws.<region>.ecr.api 엔드포인트 모두에 동일한 정책을 적용하는 것을 권장합니다. +EKS는 ECR에서 kube-proxy, coredns 및 aws-node용 이미지를 가져오므로, 레지스트리의 계정 ID (예: 602401143452.dkr. ecr.us-west-2.amazonaws.com /*)를 엔드포인트 정책의 리소스 목록에 추가하거나 "*"에서 가져오기를 허용하고 계정 ID에 대한 푸시를 제한하도록 정책을 변경해야 합니다. 아래 표는 EKS 이미지를 제공하는 AWS 계정과 클러스터 지역 간의 매핑을 보여줍니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account NumberRegion
602401143452All commercial regions except for those listed below
------
800184023465ap-east-1 - Asia Pacific (Hong Kong)
558608220178me-south-1 - Middle East (Bahrain)
918309763551cn-north-1 - China (Beijing)
961992271922cn-northwest-1 - China (Ningxia)
+

엔드포인트 정책 사용에 대한 자세한 내용은 VPC 엔드포인트 정책을 사용하여 Amazon ECR 액세스 제어를 참조합니다.

+

ECR에 대한 수명 주기 정책 구현

+

NIST 애플리케이션 컨테이너 보안 가이드 는 "레지스트리의 오래된 이미지"의 위험에 대해 경고하며, 시간이 지나면 취약하고 오래된 소프트웨어 패키지가 포함된 오래된 이미지를 제거하여 우발적인 배포 및 노출을 방지해야 한다고 지적합니다. +각 ECR 저장소에는 이미지 만료 시기에 대한 규칙을 설정하는 수명 주기 정책이 있을 수 있습니다. AWS 공식 문서에는 테스트 규칙을 설정하고 평가한 다음 적용하는 방법이 설명되어 있습니다. 공식 문서에는 리포지토리의 이미지를 필터링하는 다양한 방법을 보여주는 여러 수명 주기 정책 예제가 있습니다.

+
    +
  • 이미지 생성시기 또는 개수로 필터링
  • +
  • 태그 또는 태그가 지정되지 않은 이미지로 필터링
  • +
  • 여러 규칙 또는 단일 규칙에서 이미지 태그로 필터링
  • +
+
+경고 +

장기 실행 애플리케이션용 이미지를 ECR에서 제거하면 애플리케이션을 재배포하거나 수평으로 확장할 때 이미지 가져오기 오류가 발생할 수 있습니다.이미지 수명 주기 정책을 사용할 때는 배포와 해당 배포에서 참조하는 이미지를 최신 상태로 유지하고 릴리스/배포의 빈도를 설명하는 [이미지] 만료 규칙을 항상 만들 수 있도록 CI/CD 모범 사례를 마련해야 합니다.

+
+

선별된 이미지 세트 만들기

+

개발자가 직접 이미지를 만들도록 허용하는 대신 조직의 다양한 애플리케이션 스택에 대해 검증된 이미지 세트를 만드는 것을 고려해 보세요. 이렇게 하면 개발자는 Dockerfile 작성 방법을 배우지 않고 코드 작성에 집중할 수 있습니다. 변경 사항이 Master에 병합되면 CI/CD 파이프라인은 자동으로 에셋을 컴파일하고, 아티팩트 리포지토리에 저장하고, 아티팩트를 적절한 이미지에 복사한 다음 ECR과 같은 Docker 레지스트리로 푸시할 수 있습니다. 최소한 개발자가 자체 Dockerfile을 만들 수 있는 기본 이미지 세트를 만들어야 합니다. 이상적으로는 Dockerhub에서 이미지를 가져오지 않는 것이 좋습니다. a) 이미지에 무엇이 들어 있는지 항상 알 수는 없고 b) 상위 1000개 이미지 중 약 1/5에는 취약점이 있기 때문입니다. 이런 이미지 및 취약성 목록은 이 사이트에서 확인할 수 있습니다.

+

루트가 아닌 사용자로 실행하려면 Dockerfile에 USER 지시문을 추가

+

파드 보안 섹션에서 언급했듯이 컨테이너를 루트로 실행하는 것은 피해야 합니다. 이를 PodSpec의 일부로 구성할 수 있지만 Dockerfile에는 USER 디렉티브를 사용하는 것이 좋습니다. USER 지시어는 USER 지시문 뒤에 나타나는 RUN, ENTRYPOINT 또는 CMD 명령을 실행할 때 사용할 UID를 설정합니다.

+

Dockerfile 린트

+

Linting을 사용하여 Dockerfile이 사전 정의된 지침(예: 'USER' 지침 포함 또는 모든 이미지에 태그를 지정해야 함)을 준수하는지 확인할 수 있습니다. dockerfile_lint는 일반적인 모범 사례를 검증하고 도커파일 린트를 위한 자체 규칙을 구축하는 데 사용할 수 있는 규칙 엔진을 포함하는 RedHat의 오픈소스 프로젝트입니다. 규칙을 위반하는 Dockerfile이 포함된 빌드는 자동으로 실패한다는 점에서 CI 파이프라인에 통합할 수 있습니다.

+

스크래치에서 이미지 빌드

+

이미지를 구축할 때 컨테이너 이미지의 공격 표면을 줄이는 것이 주요 목표가 되어야 합니다. 이를 위한 이상적인 방법은 취약성을 악용하는 데 사용할 수 있는 바이너리가 없는 최소한의 이미지를 만드는 것입니다. 다행히 도커에는 scratch에서 이미지를 생성하는 메커니즘이 있습니다. Go와 같은 언어를 사용하면 다음 예제와 같이 정적 연결 바이너리를 만들어 Dockerfile에서 참조할 수 있습니다.

+
############################
+# STEP 1 build executable binary
+############################
+FROM golang:alpine AS builder# Install git.
+# Git is required for fetching the dependencies.
+RUN apk update && apk add --no-cache gitWORKDIR $GOPATH/src/mypackage/myapp/COPY . . # Fetch dependencies.
+# Using go get.
+RUN go get -d -v# Build the binary.
+RUN go build -o /go/bin/hello
+
+############################
+# STEP 2 build a small image
+############################
+FROM scratch# Copy our static executable.
+COPY --from=builder /go/bin/hello /go/bin/hello# Run the hello binary.
+ENTRYPOINT ["/go/bin/hello"]
+
+

이렇게 하면 애플리케이션으로만 구성된 컨테이너 이미지가 생성되어 매우 안전합니다.

+

ECR과 함께 불변 태그 사용

+

변경 불가능한 태그를 사용하면 이미지 저장소로 푸시할 때마다 이미지 태그를 업데이트해야 합니다. 이렇게 하면 공격자가 이미지의 태그를 변경하지 않고도 악성 버전으로 이미지를 덮어쓰는 것을 막을 수 있습니다. 또한 이미지를 쉽고 고유하게 식별할 수 있는 방법을 제공합니다.

+

이미지, SBOM, 파이프라인 실행 및 취약성 보고서에 서명

+

도커가 처음 도입되었을 때는 컨테이너 이미지를 검증하기 위한 암호화 모델이 없었습니다. v2에서 도커는 이미지 매니페스트에 다이제스트를 추가했습니다. 이를 통해 이미지 구성을 해시하고 해시를 사용하여 이미지의 ID를 생성할 수 있었습니다. 이미지 서명이 활성화되면 도커 엔진은 매니페스트의 서명을 확인하여 콘텐츠가 신뢰할 수 있는 출처에서 생성되었으며 변조가 발생하지 않았는지 확인합니다. 각 계층이 다운로드된 후 엔진은 계층의 다이제스트를 확인하여 콘텐츠가 매니페스트에 지정된 콘텐츠와 일치하는지 확인합니다. 이미지 서명을 사용하면 이미지와 관련된 디지털 서명을 검증하여 안전한 공급망을 효과적으로 구축할 수 있습니다.

+

AWS Signer 또는 Sigstore Cosign을 사용하여 컨테이너 이미지에 서명하고, SBOM에 대한 증명, 취약성 스캔 보고서 및 파이프라인 실행 보고서를 생성할 수 있습니다. 이런 증명은 이미지의 신뢰성과 무결성을 보장하고, 이미지가 실제로 어떠한 간섭이나 변조 없이 신뢰할 수 있는 파이프라인에 의해 생성되었으며, 이미지 게시자가 검증하고 신뢰하는 SBOM에 문서화된 소프트웨어 구성 요소만 포함한다는 것을 보증합니다. 이런 증명을 컨테이너 이미지에 첨부하여 리포지토리로 푸시할 수 있습니다.

+

다음 섹션에서는 감사 및 어드미션 컨트롤러 검증을 위해 입증된 아티팩트를 사용하는 방법을 살펴보겠습니다.

+

쿠버네티스 어드미션 컨트롤러를 사용한 이미지 무결성 검증

+

동적 어드미션 컨트롤러를 사용하여 대상 쿠버네티스 클러스터에 이미지를 배포하기 전에 자동화된 방식으로 이미지 서명과 입증된 아티팩트를 확인하고 아티팩트의 보안 메타데이터가 어드미션 컨트롤러 정책을 준수하는 경우에만 배포를 승인할 수 있습니다.

+

예를 들어 이미지의 서명을 암호로 확인하는 정책, 입증된 SBOM, 입증된 파이프라인 실행 보고서 또는 입증된 CVE 스캔 보고서를 작성할 수 있습니다.보고서에 데이터를 확인하기 위한 조건을 정책에 작성할 수 있습니다. 예를 들어, CVE 스캔에는 중요한 CVE가 없어야 합니다. 이런 조건을 충족하는 이미지에만 배포가 허용되며 다른 모든 배포는 어드미션 컨트롤러에 의해 거부됩니다.

+

어드미션 컨트롤러의 예는 다음과 같습니다:

+ +

컨테이너 이미지의 패키지 업데이트

+

이미지의 패키지를 업그레이드하려면 도커파일에 apt-get update && apt-get upgrade 실행을 포함해야 합니다. 업그레이드하려면 루트로 실행해야 하지만 이는 이미지 빌드 단계에서 발생합니다. 애플리케이션을 루트로 실행할 필요는 없습니다. 업데이트를 설치한 다음 USER 지시문을 사용하여 다른 사용자로 전환할 수 있습니다. 기본 이미지를 루트 사용자가 아닌 사용자로 실행하는 경우 루트 사용자로 전환했다가 다시 실행하세요. 기본 이미지 관리자에게만 의존하여 최신 보안 업데이트를 설치하지 마십시오.

+

apt-get clean을 실행하여 /var/cache/apt/archives/에서 설치 프로그램 파일을 삭제합니다. 패키지를 설치한 후 rm -rf /var/lib/apt/lists/*를 실행할 수도 있습니다. 이렇게 하면 설치할 수 있는 인덱스 파일이나 패키지 목록이 제거됩니다. 이런 명령은 각 패키지 관리자마다 다를 수 있다는 점에 유의하십시오. 예를 들면 다음과 같습니다.

+
RUN apt-get update && apt-get install -y \
+    curl \
+    git \
+    libsqlite3-dev \
+    && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+

도구 및 리소스

+
    +
  • docker-slim는 안전한 최소 이미지를 구축합니다.
  • +
  • dockle는 Dockerfile이 보안 이미지 생성 모범 사례와 일치하는지 확인합니다.
  • +
  • dockerfile-lint Rule based linter for Dockerfiles
  • +
  • hadolint는 도커파일용 규칙 기반 린터입니다.
  • +
  • Gatekeeper and OPA는 정책 기반 어드미션 컨트롤러입니다.
  • +
  • Kyverno는 쿠버네티스 네이티브 정책 엔진입니다.
  • +
  • in-toto를 통해 공급망의 특정 단계가 수행될 예정이었는지, 해당 단계가 올바른 행위자에 의해 수행되었는지 사용자가 확인할 수 있습니다.
  • +
  • Notary는 컨테이너 이미지 서명 프로젝트입니다.
  • +
  • Notary v2
  • +
  • Grafeas는 소프트웨어 공급망을 감사 및 관리하기 위한 개방형 아티팩트 메타데이터 API입니다.
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/incidents/index.html b/ko/security/docs/incidents/index.html new file mode 100644 index 000000000..295f4b4e4 --- /dev/null +++ b/ko/security/docs/incidents/index.html @@ -0,0 +1,2538 @@ + + + + + + + + + + + + + + + + + + + + + + + 사고 대응 및 포렌식 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

사고 대응 및 포렌식

+

사고에 신속하게 대응할 수 있으면 침해로 인한 피해를 최소화하는 데 도움이 될 수 있습니다. 의심스러운 행동을 경고할 수 있는 신뢰할 수 있는 경고 시스템을 갖추는 것이 올바른 사고 대응 계획의 첫 번째 단계입니다. 사고가 발생하면 영향을 받는 컨테이너를 폐기하여 교체할지 아니면 컨테이너를 격리하고 검사할지 신속하게 결정해야 합니다. 포렌식 조사 및 근본 원인 분석의 일환으로 컨테이너를 격리하기로 선택한 경우 다음과 같은 일련의 활동을 따라야 합니다.

+

사고 대응 계획 예

+

문제가 되는 파드와 워커 노드 식별

+

첫 번째 조치는 침해를 격리하는 것입니다. 먼저 침해가 발생한 위치를 파악하고 해당 파드와 해당 노드를 인프라의 나머지 부분으로부터 격리합니다.

+

워크로드 이름을 사용하여 문제가 되는 파드와 워커 노드를 식별

+

문제가 되는 파드의 이름과 네임스페이스를 알면 다음과 같이 파드를 실행하는 워커 노드를 식별할 수 있습니다.

+
kubectl get pods <name> --namespace <namespace> -o=jsonpath='{.spec.nodeName}{"\n"}'   
+
+

디플로이먼트와 같은 워크로드 리소스가 손상된 경우, 워크로드 리소스의 일부인 모든 파드가 손상될 가능성이 있다. 다음 명령어를 사용하여 워크로드 리소스의 모든 파드와 해당 파드가 실행 중인 노드를 나열하세요.

+
selector=$(kubectl get deployments <name> \
+ --namespace <namespace> -o json | jq -j \
+'.spec.selector.matchLabels | to_entries | .[] | "\(.key)=\(.value)"')
+
+kubectl get pods --namespace <namespace> --selector=$selector \
+-o json | jq -r '.items[] | "\(.metadata.name) \(.spec.nodeName)"'
+
+

위는 디플로이먼트를 위한 명령입니다. 레플리카셋, 스테이트풀셋 등과 같은 다른 워크로드 리소스에 대해서도 동일한 명령을 실행할 수 있습니다.

+

서비스 어카운트 이름을 사용하여 문제가 되는 파드와 워커 노드를 식별

+

경우에 따라 서비스 어카운트가 손상된 것으로 확인될 수 있습니다. 식별된 서비스 어카운트를 사용하는 파드가 손상될 가능성이 있습니다. 다음 명령어를 사용하여 서비스 어카운트 및 실행 중인 노드를 사용하여 모든 파드를 식별할 수 있습니다.

+
kubectl get pods -o json --namespace <namespace> | \
+    jq -r '.items[] |
+    select(.spec.serviceAccount == "<service account name>") |
+    "\(.metadata.name) \(.spec.nodeName)"'
+
+

취약하거나 손상된 이미지와 워커 노드가 있는 파드를 식별

+

경우에 따라 클러스터의 파드에서 사용 중인 컨테이너 이미지가 악의적이거나 손상된 것을 발견할 수 있습니다. 컨테이너 이미지는 악의적이거나 손상된 것입니다. 멀웨어가 포함되어 있는 것으로 밝혀진 경우, 알려진 잘못된 이미지 또는 악용된 CVE가 있는 경우입니다. 컨테이너 이미지를 사용하는 모든 파드가 손상된 것으로 간주해야 합니다. 다음 명령어로 파드가 실행 중인 이미지와 노드를 사용하여 파드를 식별할 수 있다.

+
IMAGE=<Name of the malicious/compromised image>
+
+kubectl get pods -o json --all-namespaces | \
+    jq -r --arg image "$IMAGE" '.items[] | 
+    select(.spec.containers[] | .image == $image) | 
+    "\(.metadata.name) \(.metadata.namespace) \(.spec.nodeName)"'
+
+

파드에 대한 모든 수신 및 송신 트래픽을 거부하는 네트워크 정책을 생성하여 파드를 격리

+

모든 트래픽 거부 규칙은 파드에 대한 모든 연결을 끊어 이미 진행 중인 공격을 중지하는 데 도움이 될 수 있습니다.다음 네트워크 정책은 레이블이 app=web인 파드에 적용됩니다.

+
apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: default-deny
+spec:
+  podSelector:
+    matchLabels: 
+      app: web
+  policyTypes:
+  - Ingress
+  - Egress
+
+
+

Attention

+

공격자가 기본 호스트에 대한 액세스 권한을 획득한 경우 네트워크 정책이 효과가 없는 것으로 판명될 수 있습니다. 그런 일이 발생했다고 의심되는 경우 AWS 보안 그룹을 사용하여 손상된 호스트를 다른 호스트로부터 격리할 수 있습니다.호스트의 보안 그룹을 변경할 때는 해당 호스트에서 실행 중인 모든 컨테이너에 영향을 미치므로 주의하십시오.

+
+

필요한 경우 파드 또는 워커 노드에 할당된 임시 보안 자격 증명을 취소

+

워커 노드에 파드가 다른 AWS 리소스에 액세스할 수 있도록 허용하는 IAM 역할을 할당받은 경우, 공격으로 인한 추가 피해를 방지하기 위해 인스턴스에서 해당 역할을 제거해야 합니다. 마찬가지로, 파드에 IAM 역할이 할당된 경우, 다른 워크로드에 영향을 주지 않으면서 역할에서 IAM 정책을 안전하게 제거할 수 있는지 평가해 보세요.

+

워커 노드 차단(cordon)

+

영향을 받는 워커 노드를 차단함으로써 영향을 받는 노드에 파드를 스케줄링하지 않도록 스케줄러에 알리는 것입니다.이렇게 하면 다른 워크로드에 영향을 주지 않으면서 포렌식 연구를 위해 노드를 제거할 수 있습니다.

+
+

Info

+

이 지침은 각 Fargate 파드가 자체 샌드박스 환경에서 실행되는 Fargate에는 적용되지 않습니다. 차단하는 대신 모든 수신 및 송신 트래픽을 거부하는 네트워크 정책을 적용하여 영향을 받는 Fargate 파드를 격리하십시오.

+
+

영향을 받는 워커 노드에서 종료 보호 활성화

+

공격자는 영향을 받은 노드를 종료하여 자신의 흔적을 지우려고 시도할 수 있습니다. 종료 방지 기능을 활성화하면 이런 일이 발생하지 않도록 할 수 있습니다. 인스턴스 스케일-인 보호는 스케일-인 이벤트로부터 노드를 보호합니다.

+
+

Warning

+

스팟 인스턴스에서는 종료 방지 기능을 활성화할 수 없습니다.

+
+

문제가 되는 파드/노드에 현재 조사 중임을 나타내는 레이블을 지정

+

이는 조사가 완료될 때까지 클러스터 관리자에게 영향을 받는 파드/노드를 변경하지 말라는 경고 역할을 합니다.

+

워커 노드에서 휘발성 아티팩트 캡처

+
    +
  • 운영 체제 메모리 캡처. 이는 도커 데몬(또는 다른 컨테이너 런타임)과 컨테이너별 하위 프로세스의 메모리를 캡처합니다. 이는 LiMEVolatility와 같은 도구를 사용하거나, 이를 기반으로 구축되는 Amazon EC2용 자동 포렌식 오케스트레이터와 같은 상위 수준 도구를 사용하여 수행할 수 있습니다.
  • +
  • 실행 중인 프로세스와 열린 포트의 netstat 트리 덤프를 수행. 이는 컨테이너별로 도커 데몬과 해당 하위 프로세스가 캡처됩니다.
  • +
  • 상태가 변경되기 전에 컨테이너 레벨의 상태를 저장하는 명령을 실행 컨테이너 런타임의 기능을 사용하여 현재 실행 중인 컨테이너에 대한 정보를 캡처할 수 있습니다. 예를 들어 Docker를 사용하면 다음과 같은 작업을 수행할 수 있습니다.
  • +
  • docker top CONTAINER : 실행 중인 프로세스를 확인
  • +
  • docker logs CONTAINER : 데몬 레벨 로그 확인
  • +
  • +

    docker inspect CONTAINER : 컨테이너의 다양한 정보 확인

    +

    컨테이너에서 '도커' 대신 nerdctl CLI를 사용하면 동일한 결과를 얻을 수 있다 (예: nerdctl inspect). 컨테이너 런타임에 따라 몇 가지 추가 명령을 사용할 수 있습니다. 예를 들어 도커에는 컨테이너 파일 시스템의 변경 사항을 확인하기 위한 docker diff와 휘발성 메모리(RAM)를 포함한 모든 컨테이너 상태를 저장하는 docker checkpoint가 있습니다. 컨테이너드 또는 CRI-O 런타임의 유사한 기능에 대한 설명은 이 쿠버네티스 블로그 게시물을 참조합니다.

    +
  • +
  • +

    포렌식 캡처를 위해 컨테이너를 일시 중지.

    +
  • +
  • 인스턴스의 EBS 볼륨 스냅샷을 생성.
  • +
+

손상된 파드 또는 워크로드 리소스 재배포

+

포렌식 분석을 위해 데이터를 수집한 후에는 손상된 파드 또는 워크로드 리소스를 재배포할 수 있습니다.

+

먼저 손상된 취약점에 대한 수정 사항을 배포하고 새 대체 파드를 시작합니다. 그리고 취약한 파드를 삭제합니다.

+

취약한 파드를 상위 수준의 쿠버네티스 워크로드 리소스(예: 디플로이먼트 또는 데몬셋) 에서 관리하는 경우, 이를 삭제하면 새 파드가 스케줄링된다. 따라서 취약한 파드가 다시 실행될 것입니다. 이 경우 취약성을 수정한 후 새로운 대체 워크로드 리소스를 배포해야 합니다. 그런 다음 취약한 워크로드를 삭제해야 합니다.

+

권장 사항

+

AWS 보안 사고 대응 백서 검토

+

이 섹션에서는 의심되는 보안 침해를 처리하기 위한 몇 가지 권장 사항과 함께 간략한 개요를 제공하지만, 이 주제는 AWS 보안 사고 대응 백서에서 자세히 다룹니다.

+

보안 게임 데이를 통한 대응 준비

+

보안 실무자를 레드팀과 블루팀, 두 팀으로 나눕니다. 레드 팀은 여러 시스템의 취약점을 조사하는 데 집중하고 파란색 팀은 취약점을 방어하는 데 집중합니다. 별도의 팀을 만들 수 있는 보안 실무자가 충분하지 않은 경우 쿠버네티스 악용에 대한 지식이 있는 외부 법인을 고용하는 것을 고려해 보십시오.

+

Kubesploit는 CyberArk의 침투 테스트 프레임워크로, 게임 데이를 진행하는 데 사용할 수 있습니다. 클러스터에서 취약점을 검사하는 다른 도구와 달리 kubesploit는 실제 공격을 시뮬레이션합니다. 이를 통해 블루 팀은 공격에 대한 대응을 연습하고 그 효과를 측정할 수 있습니다.

+

클러스터에 대한 침투 테스트 실행

+

자신의 클러스터를 주기적으로 공격하면 취약성과 구성 오류를 발견하는 데 도움이 될 수 있습니다. 시작하기 전에 클러스터를 대상으로 테스트를 수행하기 전에 침투 테스트 지침을 따르십시오.

+

도구 및 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/index.html b/ko/security/docs/index.html new file mode 100644 index 000000000..9bde7ca4e --- /dev/null +++ b/ko/security/docs/index.html @@ -0,0 +1,2226 @@ + + + + + + + + + + + + + + + + + + + + + + + 홈 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Amazon EKS 보안 모범 사례 가이드

+

이 가이드는 위험 평가 및 완화 전략을 통해 비즈니스 가치를 제공하는 동시에 EKS에 의존하는 정보, 시스템 및 자산을 보호하는 방법에 대한 조언을 제공합니다. 여기의 지침은 고객이 모범 사례에 따라 EKS를 구현하는 데 도움이 되도록 AWS에서 게시하는 일련의 모범 사례 가이드 중 일부입니다. 성능, 운영 우수성, 비용 최적화 및 안정성에 대한 가이드는 앞으로 몇 개월 내에 제공될 예정입니다.

+

이 가이드를 사용하는 방법

+

이 가이드는 EKS 클러스터와 클러스터가 지원하는 워크로드에 대한 보안 제어의 효과를 구현하고 모니터링하는 책임이 있는 보안 실무자를 대상으로 합니다. 가이드는 더 쉽게 사용할 수 있도록 다양한 주제 영역으로 구성되어 있습니다. 각 주제는 간략한 개요로 시작하여 EKS 클러스터 보안을 위한 권장 사항 및 모범 사례 목록이 이어집니다. 주제는 특정 순서로 읽을 필요가 없습니다.

+

공동 책임 모델에 대한 이해

+

보안 및 규정 준수는 EKS와 같은 관리형 서비스를 사용할 때 공동 책임으로 간주됩니다. 일반적으로 말해서 AWS는 클라우드"의" 보안을 책임지고 고객은 클라우드"에서의" 보안을 책임집니다. EKS에서 AWS는 EKS 관리형 쿠버네티스 컨트롤 플레인 관리를 담당합니다. 여기에는 쿠버네티스 마스터, ETCD 데이터베이스 및 AWS가 안전하고 안정적인 서비스를 제공하는 데 필요한 기타 인프라가 포함됩니다. EKS의 소비자는 IAM, 파드 보안, 런타임 보안, 네트워크 보안 등과 같은 이 가이드의 주제에 대해 주로 책임이 있습니다.

+

인프라 보안과 관련하여 AWS는 자체 관리 작업자에서 관리형 노드 그룹, Fargate로 이동할 때 추가 책임을 맡습니다. 예를 들어 Fargate를 사용하면 AWS가 파드를 실행하는 데 사용되는 기본 인스턴스/런타임을 보호할 책임이 있습니다.

+

 공동 책임 모델 - Fargate

+

AWS는 또한 쿠버네티스 패치 버전 및 보안 패치로 EKS 최적화 AMI를 최신 상태로 유지할 책임이 있습니다. 관리형 노드 그룹를 사용하는 고객은 EKS API, CLI, Cloudformation 또는 AWS 콘솔을 통해 노드 그룹을 최신 AMI로 업그레이드할 책임이 있습니다. 또한 Fargate와 달리 관리형 노드 그룹은 인프라/클러스터를 자동으로 확장하지 않습니다. 이는 cluster-autoscaler 또는 Karpenter, 네이티브 AWS 오토스케일링, SpotInst의 Ocean 또는 Atlassian의 에스컬레이터와 같은 다른 기술로 처리할 수 있습니다.

+

공유 책임 모델 - 관리형 노드 그룹

+

시스템을 설계하기 전에 귀하의 책임과 서비스 공급자(AWS) 사이의 경계선이 어디인지 아는 것이 중요합니다.

+

공동 책임 모델에 대한 추가 정보는 AWS 공식 문서를 참조하세요.

+

소개

+

EKS와 같은 관리형 쿠버네티스 서비스를 사용할 때 관련된 몇 가지 보안 모범 사례 영역이 있습니다.

+
    +
  • ID 및 액세스 관리
  • +
  • 파드 보안
  • +
  • 런타임 보안
  • +
  • 네트워크 보안
  • +
  • 멀티테넌시
  • +
  • 탐지 관리
  • +
  • 인프라 보안
  • +
  • 데이터 암호화 및 시크릿 관리
  • +
  • 규정 컴플라이언스
  • +
  • 사고 대응 및 법의학
  • +
  • 이미지 보안
  • +
+

시스템 설계의 일부로 보안 상태에 영향을 미칠 수 있는 보안 관련 사항과 관행에 대해 생각해야 합니다. 예를 들어 리소스 집합에 대해 작업을 수행할 수 있는 사람을 제어해야 합니다. 또한 보안 사고를 신속하게 식별하고 무단 액세스로부터 시스템과 서비스를 보호하며 데이터 보호를 통해 데이터의 기밀성과 무결성을 유지할 수 있는 능력이 필요합니다. 보안 사고에 대응하기 위해 잘 정의되고 연습된 일련의 프로세스를 갖추면 보안 태세도 향상됩니다. 이런 도구와 기술은 재정적 손실 방지 또는 규제 의무 준수와 같은 목표를 지원하기 때문에 중요합니다.

+

AWS는 보안에 민감한 다양한 고객의 피드백을 기반으로 진화한 다양한 보안 서비스 세트를 제공하여 조직이 보안 및 규정 준수 목표를 달성할 수 있도록 지원합니다. 매우 안전한 기반을 제공함으로써 고객은 "차별화되지 않은 무거운 작업"에 소요되는 시간을 줄이고 비즈니스 목표를 달성하는 데 더 많은 시간을 할애할 수 있습니다.

+

피드백

+

이 가이드는 광범위한 EKS/쿠버네티스 커뮤니티에서 직접적인 피드백과 제안을 수집하기 위해 GitHub에 릴리스되고 있습니다. 가이드에 포함해야 한다고 생각하는 모범 사례가 있는 경우 GitHub 리포지토리에 이슈를 제출하거나 PR을 제출하세요. 우리의 의도는 서비스에 새로운 기능이 추가되거나 새로운 모범 사례가 발전할 때 가이드를 주기적으로 업데이트하는 것입니다.

+

더 읽을 거리

+

쿠버네티스 보안 백서, 보안 감사 워킹 그룹에서 작성한 이 백서는 보안 실무자가 건전한 설계 및 구현 결정을 내리는 데 도움을 주기 위해 쿠버네티스 공격 표면 및 보안 아키텍처의 주요 측면을 설명합니다.

+

CNCF 또한 클라우드 보안에 대한 백서를 발행하였습니다. 이 백서에서는 기술 환경이 어떻게 발전했는지 살펴보고, 이런 기술 발전에 맞추어 DevOps 프로세스 및 애자일 방법론 등에 따른 보안 실천사항을 적용하는 것을 권장합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/multiaccount/index.html b/ko/security/docs/multiaccount/index.html new file mode 100644 index 000000000..f40a15d95 --- /dev/null +++ b/ko/security/docs/multiaccount/index.html @@ -0,0 +1,2570 @@ + + + + + + + + + + + + + + + + + + + + + + + 멀티 어카운트 전략 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

멀티 어카운트 전략

+

AWS는 비즈니스 애플리케이션 및 데이터를 분리하고 관리하는 데 도움이 되는 다중 계정 전략 및 AWS 조직을 사용할 것을 권장합니다. 다중 계정 전략을 사용하면 많은 이점이 있습니다.

+
    +
  • AWS API 서비스 할당량이 증가했습니다. 할당량은 AWS 계정에 적용되며, 워크로드에 여러 계정을 사용하면 워크로드에 사용할 수 있는 전체 할당량이 늘어납니다.
  • +
  • 더 간단한 인증 및 접근 권한 관리 (IAM) 정책. 워크로드와 이를 지원하는 운영자에게 자체 AWS 계정에만 액세스 권한을 부여하면 최소 권한 원칙을 달성하기 위해 세분화된 IAM 정책을 수립하는 데 걸리는 시간을 줄일 수 있습니다.
  • +
  • AWS 리소스의 격리 개선. 설계상 계정 내에서 프로비저닝된 모든 리소스는 다른 계정에 프로비저닝된 리소스와 논리적으로 격리됩니다. 이 격리 경계를 통해 애플리케이션 관련 문제, 잘못된 구성 또는 악의적인 동작의 위험을 제한할 수 있습니다. 한 계정 내에서 문제가 발생하면 다른 계정에 포함된 워크로드에 미치는 영향을 줄이거나 제거할 수 있습니다.
  • +
  • AWS 다중 계정 전략 백서에서 추가적인 혜택에 대해 설명되어 있습니다.
  • +
+

다음 섹션에서는 중앙 집중식 또는 분산형 EKS 클러스터 접근 방식을 사용하여 EKS 워크로드에 대한 다중 계정 전략을 구현하는 방법을 설명합니다.

+

멀티 테넌트 클러스터를 위한 멀티 워크로드 계정 전략 계획

+

다중 계정 AWS 전략에서는 특정 워크로드에 속하는 리소스(예: S3 버킷, ElastiCache 클러스터 및 DynamoDB 테이블)는 모두 해당 워크로드에 대한 모든 리소스가 포함된 AWS 계정에서 생성됩니다. 이를 워크로드 계정이라고 하며, EKS 클러스터는 클러스터 계정이라고 하는 계정에 배포됩니다. 클러스터 계정은 다음 섹션에서 살펴보겠습니다. 전용 워크로드 계정에 리소스를 배포하는 것은 쿠버네티스 리소스를 전용 네임스페이스에 배포하는 것과 비슷합니다.

+

그런 다음 필요에 따라 소프트웨어 개발 라이프사이클 또는 기타 요구 사항에 따라 워크로드 계정을 더 세분화할 수 있습니다. 예를 들어 특정 워크로드에는 프로덕션 계정, 개발 계정 또는 특정 지역에서 해당 워크로드의 인스턴스를 호스팅하는 계정이 있을 수 있습니다. 추가 정보는 AWS 백서에서 확인할 수 있습니다.

+

EKS 다중 계정 전략을 구현할 때 다음과 같은 접근 방식을 채택할 수 있습니다:

+

중앙 집중식 EKS 클러스터

+

이 접근 방식에서는 EKS 클러스터를 클러스터 계정이라는 단일 AWS 계정에 배포합니다. IAM roles for Service Accounts (IRSA) 또는 EKS Pod Identitiesentities를 사용하여 임시 AWS 자격 증명을 제공하고 AWS Resource Access Manager (RAM) 를 사용하여 네트워크 액세스를 단순화하면 멀티 테넌트 EKS 클러스터에 다중 계정 전략을 채택할 수 있습니다. 클러스터 계정에는 VPC, 서브넷, EKS 클러스터, EC2/Fargate 컴퓨팅 리소스 (작업자 노드) 및 EKS 클러스터를 실행하는 데 필요한 추가 네트워킹 구성이 포함됩니다.

+

멀티 테넌트 클러스터를 위한 멀티 워크로드 계정 전략에서 AWS 계정은 일반적으로 리소스 그룹을 격리하기 위한 메커니즘으로 쿠버네티스 네임스페이스와 일치합니다. 멀티 테넌트 EKS 클러스터에 대한 멀티 계정 전략을 구현할 때는 EKS 클러스터 내의 테넌트 격리의 모범 사례를 여전히 따라야 합니다.

+

AWS 조직에 클러스터 계정을 여러 개 보유할 수 있으며, 소프트웨어 개발 수명 주기 요구 사항에 맞는 클러스터 계정을 여러 개 보유하는 것이 가장 좋습니다. 대규모 워크로드를 운영하는 경우 모든 워크로드에 사용할 수 있는 충분한 쿠버네티스 및 AWS 서비스 할당량을 확보하려면 여러 개의 클러스터 계정이 필요할 수 있습니다.

+ + + + + + + + + + + +
위 다이어그램에서 AWS RAM은 클러스터 계정의 서브넷을 워크로드 계정으로 공유하는 데 사용됩니다. 그런 다음 EKS 파드에서 실행되는 워크로드는 IRSA 또는 EKS Pod Identitiesentities와 역할 체인을 사용하여 워크로드 계정에서 역할을 맡아 AWS 리소스에 액세스합니다.
+

멀티 테넌트 클러스터를 위한 멀티 워크로드 계정 전략 구현

+

AWS 리소스 액세스 관리자와 서브넷 공유

+

AWS Resource Access Manager (RAM)을 사용하면 AWS 계정 전체에서 리소스를 공유할 수 있습니다.

+

AWS 조직에 RAM이 활성화되어 있는 경우, 클러스터 계정의 VPC 서브넷을 워크로드 계정과 공유할 수 있습니다. 이렇게 하면 Amazon ElastiCache 클러스터 또는 Amazon Relational Database Service (RDS) 데이터베이스 등 워크로드 계정이 소유한 AWS 리소스를 EKS 클러스터와 동일한 VPC에 배포하고 EKS 클러스터에서 실행되는 워크로드에서 사용할 수 있습니다.

+

RAM을 통해 리소스를 공유하려면 클러스터 계정의 AWS 콘솔에서 RAM을 열고 "리소스 공유" 및 "리소스 공유 생성"을 선택합니다. 리소스 공유의 이름을 지정하고 공유하려는 서브넷을 선택합니다. 다시 다음을 선택하고 서브넷을 공유하려는 워크로드 계정의 12자리 계정 ID를 입력하고 다음을 다시 선택한 후 Create resource share (리소스 공유 생성) 를 클릭하여 완료합니다. 이 단계가 끝나면 워크로드 계정은 리소스를 해당 서브넷에 배포할 수 있습니다.

+

프로그래밍 방식 또는 IaC로 RAM 공유를 생성할 수도 있습니다.

+

EKS Pod Identitiesentities와 IRSA 중 선택

+

re:Invent 2023에서 AWS는 EKS의 파드에 임시 AWS 자격 증명을 전달하는 더 간단한 방법으로 EKS Pod Identitiesentities를 출시했습니다. IRSA 및 EKS 파드 자격 증명은 모두 EKS 파드에 임시 AWS 자격 증명을 전달하는 유효한 방법이며 앞으로도 계속 지원될 것입니다. 어떤 전달 방법이 요구 사항에 가장 잘 맞는지 고려해야 합니다.

+

EKS 클러스터 및 여러 AWS 계정을 사용하는 경우 IRSA는 EKS 클러스터가 직접 호스팅되는 계정 이외의 AWS 계정에서 역할을 직접 맡을 수 있지만 EKS Pod Identities는 역할 체인을 구성해야 합니다. 자세한 비교는 EKS 문서를 참조하십시오.

+
IRSA(IAM Roles for Service Accounts)를 사용하여 AWS API 리소스에 액세스
+

IAM Roles for Service Accounts (IRSA)를 사용하면 EKS에서 실행되는 워크로드에 임시 AWS 자격 증명을 제공할 수 있습니다. IRSA를 사용하여 클러스터 계정에서 워크로드 계정의 IAM 역할에 대한 임시 자격 증명을 얻을 수 있습니다. 이를 통해 클러스터 계정의 EKS 클러스터에서 실행되는 워크로드가 워크로드 계정에 호스팅된 S3 버킷과 같은 AWS API 리소스를 원활하게 사용하고 Amazon RDS 데이터베이스 또는 Amazon EFS File Systems와 같은 리소스에 IAM 인증을 사용할 수 있습니다.

+

워크로드 계정에서 IAM 인증을 사용하는 AWS API 리소스 및 기타 리소스는 동일한 워크로드 계정의 IAM 역할 자격 증명으로만 액세스할 수 있습니다. 단, 교차 계정 액세스가 가능하고 명시적으로 활성화된 경우는 예외입니다.

+
교차 계정 액세스를 위한 IRSA 활성화
+

클러스터 계정의 워크로드에 대해 IRSA가 워크로드 계정의 리소스에 액세스할 수 있도록 하려면 먼저 워크로드 계정에 IAM OIDC ID 공급자를 생성해야 합니다. IRSA 설정 절차와 동일한 방법으로 이 작업을 수행할 수 있습니다. 단, 워크로드 계정에 ID 공급자가 생성된다는 점만 다릅니다: https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html

+

그런 다음 EKS의 워크로드에 대해 IRSA를 구성할 때 설명서와 동일한 단계를 수행 할 수 있지만 “예제 다른 계정의 클러스터에서 ID 공급자 생성” 섹션에서 설명한 대로 워크로드 계정의 12자리 계정 ID를 사용할 수 있습니다.

+

이를 구성한 후에는 EKS에서 실행되는 애플리케이션이 해당 서비스 계정을 직접 사용하여 워크로드 계정에서 역할을 담당하고 해당 계정 내의 리소스를 사용할 수 있습니다.

+
EKS Pod Identities로 AWS API 리소스에 액세스
+

EKS Pod Identities는 EKS에서 실행되는 워크로드에 AWS 자격 증명을 제공하는 새로운 방법입니다. EKS Pod Identities는 EKS의 파드에 AWS 자격 증명을 제공하기 위해 더 이상 OIDC 구성을 관리할 필요가 없기 때문에 AWS 리소스 구성을 간소화합니다.

+
계정 간 액세스를 위한 EKS Pod Identities 활성화
+

IRSA와 달리 EKS Pod Identities는 EKS 클러스터와 동일한 계정의 역할에 직접 액세스 권한을 부여하는 데만 사용할 수 있습니다. 다른 AWS 계정의 역할에 액세스하려면 EKS Pod Identities를 사용하는 파드가 역할 체인을 수행해야 합니다.

+

다양한 AWS SDK에서 사용할 수 있는 프로세스 자격 증명 공급자를 사용하여 aws 구성 파일이 포함된 애플리케이션 프로필에서 역할 체인을 구성할 수 있습니다. 다음과 같이 프로필을 구성할 때 credentials _process를 자격 증명 소스로 사용할 수 있습니다.

+
# Content of the AWS Config file
+[profile account_b_role] 
+source_profile = account_a_role 
+role_arn = arn:aws:iam::444455556666:role/account-b-role
+
+[profile account_a_role] 
+credential_process = /eks-credential-processrole.sh
+
+

credential_process에 의해 호출된 스크립트의 소스:

+
#!/bin/bash
+# Content of the eks-credential-processrole.sh
+# This will retreive the credential from the pod identities agent,
+# and return it to the AWS SDK when referenced in a profile
+curl -H "Authorization: $(cat $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)" $AWS_CONTAINER_CREDENTIALS_FULL_URI | jq -c '{AccessKeyId: .AccessKeyId, SecretAccessKey: .SecretAccessKey, SessionToken: .Token, Expiration: .Expiration, Version: 1}' 
+
+

계정 A와 B 역할을 모두 포함하는 aws 구성 파일을 생성하고 파드 사양에 AWS_CONFIG_FILE 및 AWS_PROFILE 환경 변수를 지정할 수 있습니다.환경 변수가 파드 사양에 이미 존재하는 경우 EKS 파드 아이덴티티 웹훅은 오버라이드되지 않는다.

+
# Snippet of the PodSpec
+containers: 
+  - name: container-name
+    image: container-image:version
+    env:
+    - name: AWS_CONFIG_FILE
+      value: path-to-customer-provided-aws-config-file
+    - name: AWS_PROFILE
+      value: account_b_role
+
+

EKS Pod Identities와의 역할 체인을 위한 역할 신뢰 정책을 구성할 때 EKS 특정 속성을 세션 태그로 참조하고 속성 기반 액세스 제어(ABAC)를 사용하여 IAM 역할에 대한 액세스를 파드가 속한 쿠버네티스 서비스 계정과 같은 특정 EKS Pod ID 세션으로만 제한할 수 있습니다.

+

이러한 특성 중 일부는 보편적으로 고유하지 않을 수 있다는 점에 유의하십시오. 예를 들어 두 EKS 클러스터는 동일한 네임스페이스를 가질 수 있고 한 클러스터는 네임스페이스 전체에서 동일한 이름의 서비스 계정을 가질 수 있습니다. 따라서 EKS Pod Identities 및 ABAC를 통해 액세스 권한을 부여할 때는 서비스 계정에 대한 액세스 권한을 부여할 때 항상 클러스터 ARN과 네임스페이스를 고려하는 것이 좋습니다.

+
교차 계정 액세스를 위한 ABAC 및 EKS Pod Identities
+

다중 계정 전략의 일환으로 EKS Pod ID를 사용하여 다른 계정에서 역할 (역할 체인) 을 맡는 경우, 다른 계정에 액세스해야 하는 각 서비스 계정에 고유한 IAM 역할을 할당하거나, 여러 서비스 계정에서 공통 IAM 역할을 사용하고 ABAC를 사용하여 액세스할 수 있는 계정을 제어할 수 있습니다.

+

ABAC를 사용하여 역할 체인을 통해 다른 계정에 역할을 수임할 수 있는 서비스 계정을 제어하려면 예상 값이 있을 때만 역할 세션에서 역할을 수임하도록 허용하는 역할 신뢰 정책 설명을 생성해야 합니다. 다음 역할 신뢰 정책은 kubernetes-service-account, eks-cluster-arnkubernetes-namespace 태그가 모두 기대되는 값을 갖는 경우에만 EKS 클러스터 계정 (계정 ID 111122223333) 의 역할이 역할을 수임할 수 있습니다.

+
{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Principal": {
+                "AWS": "arn:aws:iam::111122223333:root"
+            },
+            "Action": "sts:AssumeRole",
+            "Condition": {
+                "StringEquals": {
+                    "aws:PrincipalTag/kubernetes-service-account": "PayrollApplication",
+                    "aws:PrincipalTag/eks-cluster-arn": "arn:aws:eks:us-east-1:111122223333:cluster/ProductionCluster",
+                    "aws:PrincipalTag/kubernetes-namespace": "PayrollNamespace"
+                }
+            }
+        }
+    ]
+}
+
+

이 전략을 사용할 때는 공통 IAM 역할에 STS:assumeRole 권한만 있고 다른 AWS 액세스는 허용하지 않는 것이 가장 좋습니다.

+

ABAC를 사용할 때는 IAM 역할과 사용자를 꼭 필요한 사람에게만 태그할 수 있는 권한을 가진 사람을 제어하는 것이 중요합니다. IAM 역할 또는 사용자에 태그를 지정할 수 있는 사람은 EKS Pod Identities에서 설정하는 것과 동일한 태그를 역할/사용자에 설정할 수 있으며 권한을 에스컬레이션할 수 있습니다. IAM 정책 또는 서비스 제어 정책(SCP)을 사용하여 IAM 역할 및 사용자에게 kubernetes-eks- 태그를 설정할 수 있는 액세스 권한을 가진 사용자를 제한할 수 있습니다.

+

분산형 EKS 클러스터

+

이 접근 방식에서는 EKS 클러스터가 각 워크로드 AWS 계정에 배포되고 Amazon S3 버킷, VPC, Amazon DynamoDB 테이블 등과 같은 다른 AWS 리소스와 함께 사용됩니다. 각 워크로드 계정은 독립적이고 자급자족하며 각 사업부/애플리케이션 팀에서 운영합니다. 이 모델을 사용하면 다양한 클러스터 기능 (AI/ML 클러스터, 배치 처리, 범용 등) 에 대한 재사용 가능한 블루프린트를 생성하고 애플리케이션 팀 요구 사항에 따라 클러스터를 판매할 수 있습니다. 애플리케이션 팀과 플랫폼 팀 모두 각각의 GitOps 리포지토리에서 작업하여 워크로드 클러스터로의 배포를 관리합니다.

+ + + + + + + + + + + +
De-centralized EKS Cluster Architecture
위 다이어그램에서 Amazon EKS 클러스터 및 기타 AWS 리소스는 각 워크로드 계정에 배포됩니다. 그런 다음 EKS 파드에서 실행되는 워크로드는 IRSA 또는 EKS Pod Identities를 사용하여 AWS 리소스에 액세스합니다.
+

GitOps는 전체 시스템이 Git 리포지토리에 선언적으로 설명되도록 애플리케이션 및 인프라 배포를 관리하는 방법입니다. 버전 제어, 변경 불가능한 아티팩트 및 자동화의 모범 사례를 사용하여 여러 Kubernetes 클러스터의 상태를 관리할 수 있는 운영 모델입니다. 이 다중 클러스터 모델에서는 각 워크로드 클러스터가 여러 Git 저장소로 부트스트랩되어 각 팀(애플리케이션, 플랫폼, 보안 등)이 클러스터에 각각의 변경 사항을 배포할 수 있습니다.

+

각 계정에서 IAM roles for Service Accounts (IRSA) 또는 EKS Pod Identities를 활용하여 EKS 워크로드가 다른 AWS 리소스에 안전하게 액세스할 수 있는 임시 AWS 자격 증명을 얻을 수 있도록 할 수 있습니다. IAM 역할은 각 워크로드 AWS 계정에서 생성되며 이를 k8s 서비스 계정에 매핑하여 임시 IAM 액세스를 제공합니다. 따라서 이 접근 방식에서는 계정 간 액세스가 필요하지 않습니다. IRSA의 각 워크로드에서 설정하는 방법에 대한 IAM roles for Service Accounts 설명서와 각 계정에서 EKS Pod Identities를 설정하는 방법에 대한 EKS Pod Identities 문서를 참조하십시오.

+

중앙 집중된 네트워킹

+

또한 AWS RAM을 활용하여 VPC 서브넷을 워크로드 계정과 공유하고 이 계정에서 Amazon EKS 클러스터 및 기타 AWS 리소스를 시작할 수 있습니다. 이를 통해 중앙 집중식 네트워크 관리/관리, 간소화된 네트워크 연결, 탈중앙화된 EKS 클러스터가 가능합니다. 이 접근 방식에 대한 자세한 설명과 고려 사항은 AWS 블로그를 참조하십시오.

+ + + + + + + + + + + +
De-centralized EKS Cluster Architecture using VPC Shared Subnets
위 다이어그램에서 AWS RAM은 중앙 네트워킹 계정의 서브넷을 워크로드 계정으로 공유하는 데 사용됩니다. 그러면 EKS 클러스터 및 기타 AWS 리소스가 각 워크로드 계정의 해당 서브넷에서 시작됩니다. EKS 파드는 IRSA 또는 EKS Pod Identities를 사용하여 AWS 리소스에 액세스합니다.
+

중앙화된 EKS 클러스터와 분산화된 EKS 클러스터

+

중앙 집중식 또는 분산형 중 어느 것을 사용할지는 요구 사항에 따라 달라집니다. 이 표는 각 전략의 주요 차이점을 보여줍니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#중앙화된 EKS 클러스터분산화된 EKS 클러스터
클러스터 관리:단일 EKS 클러스터를 관리하는 것이 여러 클러스터를 관리하는 것보다 쉽습니다.여러 EKS 클러스터를 관리하는 데 따른 운영 오버헤드를 줄이려면 효율적인 클러스터 관리 자동화가 필요합니다
비용 효율성:EKS 클러스터 및 네트워크 리소스를 재사용할 수 있어 비용 효율성이 향상됩니다.워크로드당 네트워킹 및 클러스터 설정이 필요하므로 추가 리소스가 필요합니다
복원력:클러스터가 손상되면 중앙 집중식 클러스터의 여러 워크로드가 영향을 받을 수 있습니다.클러스터가 손상되면 손상은 해당 클러스터에서 실행되는 워크로드로만 제한됩니다. 다른 모든 워크로드는 영향을 받지 않습니다.
격리 및 보안:격리/소프트 멀티테넌시는 '네임스페이스'와 같은 k8의 기본 구조를 사용하여 구현됩니다. 워크로드는 CPU, 메모리 등과 같은 기본 리소스를 공유할 수 있습니다. AWS 리소스는 기본적으로 다른 AWS 계정에서 액세스할 수 없는 자체 워크로드 계정으로 격리됩니다.리소스를 공유하지 않는 개별 클러스터 및 노드에서 워크로드가 실행되므로 컴퓨팅 리소스의 격리가 강화됩니다. AWS 리소스는 기본적으로 다른 AWS 계정에서 액세스할 수 없는 자체 워크로드 계정으로 격리됩니다.
성능 및 확장성:워크로드가 매우 큰 규모로 성장함에 따라 클러스터 계정에서 kubernetes 및 AWS 서비스 할당량이 발생할 수 있습니다. 클러스터 계정을 추가로 배포하여 더 확장할 수 있습니다클러스터와 VPC가 많아질수록 각 워크로드의 사용 가능한 k8과 AWS 서비스 할당량이 많아집니다
네트워킹:클러스터당 단일 VPC가 사용되므로 해당 클러스터의 애플리케이션을 더 간단하게 연결할 수 있습니다.분산되지 않은 EKS 클러스터 VPC 간에 라우팅을 설정해야 합니다.
쿠버네티스 액세스 관리:모든 워크로드 팀에 액세스를 제공하고 쿠버네티스 리소스가 적절하게 분리되도록 클러스터에서 다양한 역할과 사용자를 유지해야 합니다.각 클러스터가 워크로드/팀 전용으로 사용되므로 액세스 관리가 간소화됩니다.
AWS 액세스 관리:AWS 리소스는 기본적으로 워크로드 계정의 IAM 역할을 통해서만 액세스할 수 있는 자체 계정에 배포됩니다. 워크로드 계정의 IAM 역할은 IRSA 또는 EKS Pod Identities와의 교차 계정으로 간주됩니다.AWS 리소스는 기본적으로 워크로드 계정의 IAM 역할을 통해서만 액세스할 수 있는 자체 계정에 배포됩니다. 워크로드 계정의 IAM 역할은 IRSA 또는 EKS Pod Identities를 사용하여 파드에 직접 전달됩니다.
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/multitenancy/index.html b/ko/security/docs/multitenancy/index.html new file mode 100644 index 000000000..85497ce41 --- /dev/null +++ b/ko/security/docs/multitenancy/index.html @@ -0,0 +1,2800 @@ + + + + + + + + + + + + + + + + + + + + + + + 멀티 테넌시 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

테넌트 격리

+

멀티테넌시를 생각할 때 공유 인프라에서 실행되는 다른 사용자나 애플리케이션으로부터 사용자나 애플리케이션을 분리하려는 경우가 많습니다.

+

쿠버네티스는 단일 테넌트 오케스트레이터 입니다. 즉, 컨트롤 플레인의 단일 인스턴스가 클러스터 내 모든 테넌트 간에 공유됩니다. 하지만 멀티테넌시와 유사한 형태를 만드는 데 사용할 수 있는 다양한 쿠버네티스 객체가 있습니다. 예를 들어 네임스페이스와 역할 기반 접근 제어(RBAC) 를 구현하여 테넌트를 논리적으로 서로 격리할 수 있습니다. 마찬가지로 할당량 및 제한 범위를 사용하여 각 테넌트가 사용할 수 있는 클러스터 리소스의 양을 제어할 수 있습니다. 하지만 클러스터는 강력한 보안 경계를 제공하는 유일한 구조입니다. 클러스터 내 호스트에 대한 액세스 권한을 획득한 공격자는 해당 호스트에 마운트된 모든 시크릿, 컨피그맵, 볼륨을 가져올 수 있기 때문입니다. 또한 Kubelet을 가장하여 노드의 속성을 조작하거나 클러스터 내에서 옆으로 이동할 수도 있습니다.

+

다음 섹션에서는 쿠버네티스와 같은 단일 테넌트 오케스트레이터를 사용할 때 발생하는 위험을 줄이면서 테넌트 격리를 구현하는 방법을 설명합니다.

+

소프트 멀티테넌시

+

소프트 멀티테넌시를 사용하면 네임스페이스, 역할 및 롤바인딩, 네트워크 정책과 같은 네이티브 쿠버네티스 구조를 사용하여 테넌트를 논리적으로 분리할 수 있습니다.예를 들어 RBAC는 테넌트가 서로의 리소스에 액세스하거나 조작하는 것을 방지할 수 있습니다. 쿼터 및 리밋 범위는 각 테넌트가 소비할 수 있는 클러스터 리소스의 양을 제어하는 반면, 네트워크 정책은 서로 다른 네임스페이스에 배포된 애플리케이션이 서로 통신하지 못하도록 하는 데 도움이 될 수 있습니다.

+

그러나 이런 컨트롤 중 어느 것도 다른 테넌트의 파드가 노드를 공유하는 것을 막지는 못합니다. 더 강력한 격리가 필요한 경우 노드 셀렉터, 안티-어피니티 규칙 및/또는 테인트 및 톨러레이션을 사용하여 서로 다른 테넌트의 파드를 별도의 노드로 강제로 스케줄링할 수 있습니다. 이를 종종 단독 테넌트 노드 라고 합니다. 테넌트가 많은 환경에서는 이 작업이 다소 복잡하고 비용이 많이 들 수 있습니다.

+
+

Attention

+

네임스페이스는 전역적으로 범위가 지정된 유형이므로 네임스페이스로 구현된 소프트 멀티테넌시는 필터링된 네임스페이스 목록을 테넌트에 제공할 수 없습니다. 테넌트가 특정 네임스페이스를 볼 수 있는 경우 클러스터 내의 모든 네임스페이스를 볼 수 있습니다.

+
+
+

Warning

+

소프트 멀티테넌시를 사용하면 테넌트는 기본적으로 클러스터 내에서 실행되는 모든 서비스에 대해 CoreDNS를 쿼리할 수 있습니다.공격자는 클러스터의 모든 파드에서 dig SRV *.*.svc.cluster.local을 실행하여 이를 악용할 수 있습니다.클러스터 내에서 실행되는 서비스의 DNS 레코드에 대한 액세스를 제한해야 하는 경우 CoreDNS용 방화벽 또는 정책 플러그인을 사용하는 것이 좋습니다. 자세한 내용은 https://github.com/coredns/policy#kubernetes-metadata-multi-tenancy-policy을 참조하십시오.

+
+

Kiosk는 소프트 멀티 테넌시의 구현을 지원하는 오픈 소스 프로젝트입니다. 다음 기능을 제공하는 일련의 CRD 및 컨트롤러로 구현됩니다.

+
    +
  • 공유 쿠버네티스 클러스터에서 테넌트를 분리하기 위한 계정 및 계정 사용자
  • +
  • 계정 사용자를 위한 셀프 서비스 네임스페이스 프로비저닝
  • +
  • 클러스터 공유 시 서비스 품질 및 공정성을 보장하기 위한 계정 제한
  • +
  • 안전한 테넌트 격리 및 셀프 서비스 네임스페이스 초기화를 위한 네임스페이스 템플릿
  • +
+

Loft는 다음 기능을 추가하는 Kiosk 및 DevSpace의 관리자가 제공하는 상용 제품입니다.

+
    +
  • 다른 클러스터의 공간에 대한 액세스 권한을 부여하기 위한 멀티 클러스터 액세스
  • +
  • 절전 모드 는 비활성 기간 동안 공간에서 배포를 축소합니다.
  • +
  • GitHub와 같은 OIDC 인증 공급자를 사용한 싱글 사인온
  • +
+

소프트 멀티 테넌시로 해결할 수 있는 세 가지 주요 사용 사례가 있습니다.

+

엔터프라이즈 설정

+

첫 번째는 "테넌트"가 직원, 계약자 또는 조직의 승인을 받았다는 점에서 어느정도 신뢰을 받는 기업 환경입니다. 각 테넌트는 일반적으로 부서 또는 팀과 같은 행정 부서에 소속됩니다.

+

이런 유형의 설정에서는 일반적으로 클러스터 관리자가 네임스페이스 생성 및 정책 관리를 담당합니다. 또한 특정 개인에게 네임스페이스를 감독하는 위임 관리 모델을 구현하여 배포, 서비스, 파드, 작업 등과 같이 정책과 관련이 없는 개체에 대해 CRUD 작업을 수행할 수 있도록 할 수도 있습니다.

+

컨테이너 런타임에서 제공하는 격리는 이 설정 내에서 허용될 수도 있고 파드 보안을 위한 추가 제어 기능으로 보강해야 할 수도 있습니다.더 엄격한 격리가 필요한 경우 서로 다른 네임스페이스에 있는 서비스 간의 통신을 제한해야 할 수도 있습니다.

+

서비스로서 쿠버네티스

+

대조적으로 소프트 멀티테넌시는 Kuberenetes as a Service(KaaS) 상황에서 사용할 수 있는 설정입니다. KaaS를 사용하면 애플리케이션이 일련의 PaaS 서비스를 제공하는 컨트롤러 및 CRD 컬렉션과 함께 공유 클러스터에서 호스팅됩니다. 테넌트는 쿠버네티스 API 서버와 직접 상호 작용하며 비정책 객체에 대해 CRUD 작업을 수행할 수 있습니다. 테넌트가 자체 네임스페이스를 생성하고 관리할 수 있다는 점에서 셀프 서비스의 요소도 있습니다. 이런 유형의 환경에서는 테넌트는 신뢰할 수 없는 코드도 실행할 수 있는 것으로 간주됩니다.

+

이런 유형의 환경에서 테넌트를 격리하려면 엄격한 네트워크 정책과 파드 샌드박싱 을 구현해야 할 수 있습니다. 샌드박싱은 Firecracker와 같은 마이크로 VM 내에서 또는 사용자 공간 커널에서 파드 컨테이너를 실행하는 것입니다. 이제 EKS Fargate를 사용하여 샌드박스가 적용된 파드를 만들 수 있습니다.

+

서비스형 소프트웨어(SaaS)

+

소프트 멀티테넌시의 최종 사용 사례는 서비스형 소프트웨어 (SaaS) 설정입니다.이 환경에서 각 테넌트는 클러스터 내에서 실행되는 애플리케이션의 특정 인스턴스 와 연결됩니다.각 인스턴스는 종종 자체 데이터를 갖고 있으며 일반적으로 쿠버네티스 RBAC와 독립적인 별도의 액세스 제어를 사용합니다.

+

다른 사용 사례와 달리 SaaS 설정의 테넌트는 쿠버네티스 API와 직접 인터페이스하지 않습니다. 대신 SaaS 애플리케이션은 쿠버네티스 API와 인터페이스하여 각 테넌트를 지원하는 데 필요한 객체를 생성합니다.

+

쿠버네티스 구성

+

각 인스턴스에서 다음 구조를 사용하여 테넌트를 서로 격리합니다:

+

네임스페이스

+

네임스페이스는 소프트 멀티테넌시를 구현하는 데 필수적입니다.클러스터를 논리적 파티션으로 나눌 수 있습니다.멀티테넌시를 구현하는 데 필요한 할당량, 네트워크 정책, 서비스 어카운트 및 기타 개체의 범위는 네임스페이스로 지정됩니다.

+

네트워크 정책

+

기본적으로 쿠버네티스 클러스터의 모든 파드는 서로 통신할 수 있습니다. 이 동작은 네트워크 정책을 사용하여 변경할 수 있습니다.

+

네트워크 정책은 레이블 또는 IP 주소 범위를 사용하여 파드 간 통신을 제한합니다. 테넌트 간 엄격한 네트워크 격리가 필요한 멀티 테넌트 환경에서는 파드 간 통신을 거부하는 기본 규칙과 모든 파드가 DNS 서버에 이름 확인을 쿼리할 수 있도록 허용하는 다른 규칙으로 시작하는 것이 좋습니다. 이를 통해 네임스페이스 내에서 통신을 허용하는 더 많은 허용 규칙을 추가할 수 있습니다. 필요에 따라 이를 더 세분화할 수 있습니다.

+
+

Attention

+

네트워크 정책은 필요하지만 충분하지는 않습니다. 네트워크 정책을 적용하려면 Calico 또는 Cilium과 같은 정책 엔진이 필요합니다.

+
+

역할 기반 접근 제어(RBAC)

+

역할 및 롤바인딩은 쿠버네티스에서 역할 기반 접근 제어 (RBAC) 를 적용하는 데 사용되는 쿠버네티스 오브젝트입니다. 역할에는 클러스터의 오브젝트에 대해 수행할 수 있는 작업 목록이 포함되어 있습니다. 롤바인딩은 역할이 적용되는 개인 또는 그룹을 지정합니다. 엔터프라이즈 및 KaaS 설정에서 RBAC를 사용하여 선택한 그룹 또는 개인이 개체를 관리할 수 있도록 허용할 수 있습니다.

+

쿼터

+

쿼터은 클러스터에서 호스팅되는 워크로드에 대한 제한을 정의하는 데 사용됩니다. 쿼터을 사용하면 파드가 소비할 수 있는 최대 CPU 및 메모리 양을 지정하거나 클러스터 또는 네임스페이스에 할당할 수 있는 리소스 수를 제한할 수 있습니다. Limit Range를 사용하면 각 제한의 최소값, 최대값 및 기본값을 선언할 수 있습니다.

+

공유 클러스터에서 리소스를 오버커밋하면 리소스를 최대화할 수 있으므로 종종 유용합니다. 그러나 클러스터에 대한 무제한 액세스는 리소스 부족을 초래하여 성능 저하 및 애플리케이션 가용성 손실로 이어질 수 있습니다. 파드의 요청이 너무 낮게 설정되고 실제 리소스 사용량이 노드의 용량을 초과하면 노드에 CPU 또는 메모리 압력이 발생하기 시작합니다. 이 경우 파드가 재시작되거나 노드에서 제거될 수 있습니다.

+

이를 방지하려면 멀티 테넌트 환경에서 네임스페이스에 할당량을 부과하여 테넌트가 클러스터에서 파드를 예약할 때 요청 및 제한을 지정하도록 계획해야 합니다. 또한 파드가 소비할 수 있는 리소스의 양을 제한하여 잠재적인 서비스 거부를 완화할 수 있습니다.

+

또는 쿼터를 사용하여 테넌트의 지출에 맞게 클러스터의 리소스를 할당할 수 있습니다.이는 KaaS 시나리오에서 특히 유용합니다.

+

파드 우선순위 및 선점

+

파드 우선순위 및 선점은 다른 파드에 비해 파드에 더 많은 중요성을 부여하고자 할 때 유용할 수 있다.예를 들어 파드 우선 순위를 사용하면 고객 A의 파드가 고객 B보다 높은 우선 순위로 실행되도록 구성할 수 있습니다. 사용 가능한 용량이 충분하지 않은 경우 스케줄러는 고객 B의 우선 순위가 낮은 파드를 제외하고 고객 A의 우선 순위가 높은 파드를 수용합니다. 이는 프리미엄을 지불하려는 고객이 더 높은 우선 순위를 받는 SaaS 환경에서 특히 유용할 수 있습니다.

+
+

Attention

+

파드의 우선순위는 우선순위가 낮은 다른 파드에 원치 않는 영향을 미칠 수 있다. 예를 들어, 대상 파드는 정상적으로 종료되지만 PodDisruptionBudget은 보장되지 않아 파드 쿼럼에 의존하는 우선순위가 낮은 애플리케이션이 중단될 수 있습니다. 선점 제한을 참조하십시오.

+
+

완화 제어

+

멀티 테넌트 환경 관리자의 주된 관심사는 공격자가 기본 호스트에 대한 접근 권한을 얻지 못하도록 하는 것입니다. 이런 위험을 완화하려면 다음 제어 방법을 고려해야 합니다.

+

컨테이너를 위한 샌드박스 실행 환경

+

샌드박싱은 각 컨테이너를 격리된 자체 가상 시스템에서 실행하는 기술입니다. 파드 샌드박싱을 수행하는 기술로는 Firecracker, Weave의 Firekube등이 있습니다.

+

Firecracker를 EKS 지원 런타임으로 만들기 위한 노력에 대한 추가 정보는 이 글을 참조하십시오.

+

개방형 정책 에이전트(OPA) 및 게이트키퍼

+

GatekeeperOPA로 생성된 정책을 시행하는 Kubernetes 어드미션 컨트롤러입니다. OPA를 사용하면 별도의 인스턴스 또는 다른 테넌트보다 더 높은 우선순위에서 테넌트의 파드를 실행하는 정책을 생성할 수 있습니다. 이 프로젝트에 대한 일반적인 OPA 정책 모음은 GitHub 리포지토리에서 찾을 수 있습니다.

+

OPA를 사용하여 CoreDNS에서 반환되는 레코드를 필터링/제어할 수 있는 실험적인 CoreDNS를 위한 OPA 플러그인도 있습니다.

+

Kyverno

+

Kyverno는 정책을 쿠버네티스 리소스로 사용하여 구성을 검증, 변경 및 생성할 수 있는 쿠버네티스 기본 정책 엔진입니다. Kyverno는 유효성 검사를 위해 Kustomize 스타일 오버레이를 사용하고 변형을 위한 JSON 패치 및 전략적 병합 패치를 지원하며 유연한 트리거를 기반으로 네임스페이스 간에 리소스를 복제할 수 있습니다.

+

Kyverno를 사용하여 네임스페이스를 격리하고, 파드 보안 및 기타 모범 사례를 적용하고, 네트워크 정책과 같은 기본 구성을 생성할 수 있습니다. 이 프로젝트의 GitHub 리포지토리에 몇 가지 예제가 포함되어 있습니다. 다른 많은 것들이 Kyverno 웹사이트 내 정책 라이브러리에 포함되어 있습니다.

+

테넌트 워크로드를 특정 노드로 격리

+

테넌트 워크로드가 특정 노드에서 실행되도록 제한하면 소프트 멀티 테넌시 모델의 격리를 높이는 데 사용할 수 있습니다. 이 접근 방식을 사용하면 테넌트별 워크로드가 각 테넌트용으로 프로비저닝된 노드에서만 실행됩니다. 이런 격리를 달성하기 위해 네이티브 쿠버네티스 속성(노드 어피니티, 테인트 및 톨러레이션)을 사용하여 특정 노드를 파드 스케줄링 대상으로 지정하고 다른 테넌트의 파드가 테넌트별 노드에 스케줄링되지 않도록 합니다.

+

파트 1 - 노드 어피니티

+

쿠버네티스 노드 어피니티는 노드 레이블을 기반으로 노드를 스케줄링할 대상으로 지정하는 데 사용됩니다. 노드 어피니티 규칙을 사용하면 셀렉터 용어와 일치하는 특정 노드에 파드가 몰리도록 구성할 수 있습니다. 아래 파드 사양에서는 requiredDuringSchedulingIgnoredDuringExecution 노드 어피니티가 각 파드에 적용된다. 결과적으로 파드는 node-restriction.kubernetes.io/tenant: tenants-x와 같은 키/값으로 레이블이 지정된 노드에 배포됩니다.

+
...
+spec:
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: node-restriction.kubernetes.io/tenant
+            operator: In
+            values:
+            - tenants-x
+...
+
+

이 노드 어피니티를 사용하면 스케줄링 중에 레이블이 필요하지만 실행 중에는 필요하지 않습니다. 기본 노드의 레이블이 변경되더라도 해당 레이블 변경으로 인해 파드가 제거되지는 않습니다. 하지만 향후 스케줄링이 영향을 받을 수 있습니다.

+
+

Warning

+

node-restriction.kubernetes.io/ 레이블 접두사는 쿠버네티스에서 특별한 의미를 가집니다. EKS 클러스터에 사용할 수 있는 NodeRestriction은 'kubelet'이 이 접두사를 가진 레이블을 추가/제거/업데이트하는 것을 방지합니다. 공격자는 kubelet의 레이블을 수정할 수 없으므로, kubelet의 자격 증명을 사용하여 노드 개체를 업데이트하거나 이런 레이블을 kubelet으로 전달하도록 시스템 설정을 수정할 수 없습니다. 이 접두사를 모든 파드의 노드 스케줄링에 사용하면 공격자가 노드 레이블을 수정하여 다른 워크로드 세트를 노드로 끌어들이려는 시나리오를 방지할 수 있습니다.

+
+
+

Info

+

노드 어피니티 대신 노드 셀렉터를 사용할 수도 있습니다. 하지만 노드 어피니티는 표현방식이 더 다양하고 파드 스케줄링 중에 더 복잡한 조건을 정의할 수 있습니다. 차이점과 고급 스케줄링 선택에 대한 추가 정보는 CNCF 블로그 게시물인 고급 쿠버네티스 파드의 노드 스케줄링을 참조하십시오.

+
+

파트 2 - 테인트(Taint) 및 톨러레이션(Toleration)

+

파드를 노드로 끌어들이는 것은 이 세 부분으로 구성된 접근 방식의 첫 번째 부분에 불과합니다. 이 접근 방식이 제대로 작동하려면 권한이 부여되지 않은 노드에 파드를 스케줄링하지 않도록 해야 합니다. 쿠버네티스는 원치 않거나 승인되지 않은 파드를 차단하기 위해 노트 테인트를 사용합니다. 테인트는 파드가 스케줄링되지 않도록 노드에 조건을 설정하는 데 사용됩니다. 아래 테인트는 tenant: tenants-x의 키-값 쌍을 사용합니다.

+
...
+    taints:
+      - key: tenant
+        value: tenants-x
+        effect: NoSchedule
+...
+
+

위와 같이 노드 테인트가 주어지면, 테인트를 허용하는 파드만 노드에 스케줄링할 수 있다. 승인된 파드를 노드에 스케줄링할 수 있으려면 아래와 같이 각 파드 사양에 테인트에 대한 톨러레이션이 포함되어야 합니다.

+
...
+  tolerations:
+  - effect: NoSchedule
+    key: tenant
+    operator: Equal
+    value: tenants-x
+...
+
+

위의 톨러레이션을 가진 파드는 적어도 그 특정 테인트로 인하여 해당 노드로 스케줄링이 중단되지는 않을 것이다. 쿠버네티스는 노드 리소스 압박과 같은 특정 상황에서 파드 스케줄링을 일시적으로 중단하기 위해 테인트를 사용하기도 합니다. 노드 어피니티, 테인트 및 톨러레이션을 사용하면 원하는 파드를 특정 노드로 효과적으로 배포하고 원치 않는 파드를 제거할 수 있습니다.

+
+

Attention

+
+

특정 쿠버네티스 파드는 모든 노드에서 실행되어야 합니다. 이런 파드는 예로 컨테이너 네트워크 인터페이스 (CNI)kube-proxy 데몬셋등이 있습니다. 이를 위해 이런 파드의 사양에는 다양한 테인트을 견딜 수 있는 매우 관대한 톨러레이션이 포함되어야 합니다. 이런 톨러레이션을 변경하지 않도록 주의해야 합니다. 이런 허용치를 변경하면 클러스터 작동이 잘못될 수 있습니다. 또한 OPA/GatekeeperKyverno와 같은 정책 관리 도구를 사용하여 승인되지 않은 파드가 이런 관대한 톨러레이션을 사용하지 못하도록 하는 검증 정책도 작성할 수 있습니다.

+

파트 3 - 노드 선택을 위한 정책 기반 관리

+

CICD 파이프라인의 규칙 적용을 포함하여 파드 사양의 노드 어피니티 및 톨러레이션을 관리하는 데 사용할 수 있는 여러 도구가 있습니다. 하지만 쿠버네티스 클러스터 수준에서도 격리를 적용해야 합니다. 이를 위해 정책 관리 도구를 사용하여 요청 페이로드를 기반으로 인바운드 쿠버네티스 API 서버 요청을 변경 하여 위에서 언급한 각 노드 어피니티 규칙 및 톨러레이션을 적용할 수 있습니다.

+

예를 들어, tenants-x 네임스페이스로 향하는 파드에 올바른 노드 어피니티 및 톨러레이션을 스탬핑_하여 _tenants-x 노드에 대한 스케줄링을 허용할 수 있다. 쿠버네티스 뮤테이팅(Mutating) 어드미션 웹훅을 사용하여 구성된 정책 관리 도구를 활용하면 정책을 사용하여 인바운드 파드 사양을 변경할 수 있습니다. 뮤테이션은 원하는 스케줄링이 가능하도록 필요한 요소를 추가합니다. 노드 어피니티를 추가하는 OPA/게이트키퍼 정책의 예는 다음과 같습니다.

+
apiVersion: mutations.gatekeeper.sh/v1alpha1
+kind: Assign
+metadata:
+  name: mutator-add-nodeaffinity-pod
+  annotations:
+    aws-eks-best-practices/description: >-
+      Adds Node affinity - https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
+spec:
+  applyTo:
+  - groups: [""]
+    kinds: ["Pod"]
+    versions: ["v1"]
+  match:
+    namespaces: ["tenants-x"]
+  location: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms"
+  parameters:
+    assign:
+      value: 
+        - matchExpressions:
+          - key: "tenant"
+            operator: In
+            values:
+            - "tenants-x"
+
+

위의 정책은 tenants-x 네임스페이스에 파드를 적용하기 위한 쿠버네티스 API 서버 요청에 적용됩니다. 이 정책은 requiredDuringSchedulingIgnoredDuringExecution 노드 어피니티 규칙을 추가하여, 파드가 tenant: tenants-x 레이블이 붙은 노드에 집중되도록 합니다.

+

아래에 나와 있는 두 번째 정책은 대상 네임스페이스와 그룹, 종류, 버전의 동일한 일치 기준을 사용하여 동일한 파드 사양에 톨러레이션을 추가합니다.

+
apiVersion: mutations.gatekeeper.sh/v1alpha1
+kind: Assign
+metadata:
+  name: mutator-add-toleration-pod
+  annotations:
+    aws-eks-best-practices/description: >-
+      Adds toleration - https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
+spec:
+  applyTo:
+  - groups: [""]
+    kinds: ["Pod"]
+    versions: ["v1"]
+  match:
+    namespaces: ["tenants-x"]
+  location: "spec.tolerations"
+  parameters:
+    assign:
+      value: 
+      - key: "tenant"
+        operator: "Equal"
+        value: "tenants-x"
+        effect: "NoSchedule"
+
+

위의 정책은 파드에만 해당된다. 이는 정책의 location 요소에 있는 변경되는 요소에 대한 경로 때문입니다. 디플로이먼트 및 잡 리소스와 같이 파드를 생성하는 리소스를 처리하기 위한 추가 정책을 작성할 수 있다. 나열된 정책 및 기타 예는 이 가이드의 동반 GitHub 프로젝트에서 확인할 수 있습니다.

+

이 두 뮤테이션의 결과는 파드가 원하는 노드에 끌리는 동시에 특정 노드 테인트에 의해 반발되지 않는다는 것입니다. 이를 확인하기 위해 tenant=tenants-x 로 레이블이 지정된 노드를 가져 오고 tenants-x 네임스페이스 에서 파드를 가져 오기 위해 두 개의 kubectl 호출에서 출력 스니펫을 볼 수 있습니다.

+
kubectl get nodes -l tenant=tenants-x
+NAME                                        
+ip-10-0-11-255...
+ip-10-0-28-81...
+ip-10-0-43-107...
+
+kubectl -n tenants-x get pods -owide
+NAME                                  READY   STATUS    RESTARTS   AGE   IP            NODE
+tenant-test-deploy-58b895ff87-2q7xw   1/1     Running   0          13s   10.0.42.143   ip-10-0-43-107...
+tenant-test-deploy-58b895ff87-9b6hg   1/1     Running   0          13s   10.0.18.145   ip-10-0-28-81...
+tenant-test-deploy-58b895ff87-nxvw5   1/1     Running   0          13s   10.0.30.117   ip-10-0-28-81...
+tenant-test-deploy-58b895ff87-vw796   1/1     Running   0          13s   10.0.3.113    ip-10-0-11-255...
+tenant-test-pod                       1/1     Running   0          13s   10.0.35.83    ip-10-0-43-107...
+
+

위의 출력에서 볼 수 있듯이 모든 파드는 tenant=tenants-x로 표시된 노드에 스케줄링됩니다. 간단히 말해, 파드는 원하는 노드에서만 실행되고 다른 파드(필수 어피니티 및 톨러레이션 제외)는 실행되지 않습니다. 테넌트 워크로드는 효과적으로 격리됩니다.

+

뮤테이션된 파드 스펙의 예는 아래에 나와 있습니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: tenant-test-pod
+  namespace: tenants-x
+spec:
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: tenant
+            operator: In
+            values:
+            - tenants-x
+...
+  tolerations:
+  - effect: NoSchedule
+    key: tenant
+    operator: Equal
+    value: tenants-x
+...
+
+
+

Attention

+

뮤테이팅(mutating) 및 검증(validating) 어드미션 웹훅을 사용하여 쿠버네티스 API 서버 요청 흐름에 통합된 정책 관리 도구는 지정된 기간 내에 API 서버의 요청에 응답하도록 설계되었습니다. 이 시간은 보통 3초 이내입니다. 웹훅 호출이 구성된 시간 내에 응답을 반환하지 못하면 인바운드 API 서버 요청의 변경 및/또는 검증이 발생할 수도 있고 그렇지 않을 수도 있습니다. 이 동작은 승인 웹훅 구성이 Fail-Open 또는 Fail-Closed로 설정되어 있는지 여부에 따라 달라집니다.

+
+

위 예시에서는 OPA/게이트키퍼용으로 작성된 정책을 사용했습니다. 하지만 노드 선택 사용 사례를 처리하는 다른 정책 관리 도구도 있습니다. 예를 들어, 이 Kyverno 정책을 사용하여 노드 어피니티 뮤테이션을 처리할 수 있습니다.

+
+

Tip

+

제대로 작동하는 경우 정책을 변경하면 인바운드 API 서버 요청 페이로드에 대한 원하는 변경 사항이 적용됩니다. 하지만 변경 사항이 계속 적용되기 전에 원하는 변경 사항이 적용되는지 확인하는 검증 정책도 포함해야 합니다. 이는 테넌트-노드 격리에 이런 정책을 사용할 때 특히 중요합니다. 또한 클러스터에 원치 않는 구성이 있는지 정기적으로 점검할 수 있도록 감사 정책을 포함하는 것도 좋습니다.

+
+

참조

+ +

하드 멀티테넌시

+

각 테넌트에 대해 별도의 클러스터를 프로비저닝하여 하드 멀티테넌시를 구현할 수 있습니다. 이렇게 하면 테넌트 간에 매우 강력한 격리가 가능하지만 몇 가지 단점이 있습니다.

+

첫째, 테넌트가 많은 경우 이 접근 방식은 비용이 많이 들 수 있습니다. 각 클러스터의 컨트롤 플레인 비용을 지불해야 할 뿐만 아니라 클러스터 간에 컴퓨팅 리소스를 공유할 수 없게 됩니다. 이로 인해 결국 클러스터의 일부는 활용도가 낮고 다른 클러스터는 과도하게 사용되는 단편화가 발생합니다.

+

둘째, 이런 클러스터를 모두 관리하려면 특수 도구를 구입하거나 구축해야 할 수 있습니다.시간이 지나면 수백 또는 수천 개의 클러스터를 관리하는 것이 너무 복잡해질 수 있습니다.

+

마지막으로 테넌트별로 클러스터를 생성하는 것은 네임스페이스를 생성하는 것보다 느립니다. 하지만 규제가 엄격한 산업이나 강력한 격리가 필요한 SaaS 환경에서는 하드 테넌시 접근 방식이 필요할 수 있습니다.

+

향후 방향

+

Kubernetes 커뮤니티는 소프트 멀티테넌시의 현재 단점과 하드 멀티테넌시의 문제점을 인식하고 있습니다.멀티테넌시 SIG 그룹은 계층적 네임스페이스 컨트롤러(HNC) 및 가상 클러스터를 비롯한 여러 인큐베이션 프로젝트를 통해 이런 단점을 해결하려고 노력하고 있습니다.

+

HNC 제안(KEP)은 테넌트 관리자가 하위 네임스페이스를 생성할 수 있는 기능과 함께 [policy] 개체 상속을 통해 네임스페이스 간의 상위-하위 관계를 생성하는 방법을 설명합니다.

+

가상 클러스터 제안서는 클러스터 내의 각 테넌트("Kubernetes on Kubernetes"라고도 표현)에 대해 API 서버, 컨트롤러 관리자 및 스케줄러를 포함한 컨트롤 플레인 서비스의 개별 인스턴스를 생성하는 메커니즘을 설명합니다.

+

멀티테넌시 벤치마크 제안서는 격리 및 분할을 위한 네임스페이스를 사용하여 클러스터를 공유하는 지침과 지침 준수 여부를 검증하기 위한 명령줄 도구 kubectl-mtb를 제공합니다.

+

멀티 클러스터 관리 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/network/index.html b/ko/security/docs/network/index.html new file mode 100644 index 000000000..dcf524988 --- /dev/null +++ b/ko/security/docs/network/index.html @@ -0,0 +1,3383 @@ + + + + + + + + + + + + + + + + + + + + + + + 네트워크 보안 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

네트워크 보안

+

네트워크 보안에는 여러 측면이 있습니다. 첫 번째는 서비스 간의 네트워크 트래픽 흐름을 제한하는 규칙 적용과 관련됩니다. 두 번째는 전송 중인 트래픽의 암호화와 관련이 있습니다. EKS에서 이런 보안 조치를 구현하는 메커니즘은 다양하지만 종종 다음 항목을 포함합니다.

+

트래픽 관리

+
    +
  • 네트워크 정책
  • +
  • 보안 그룹
  • +
+

네트워크 암호화

+
    +
  • 서비스 메시
  • +
  • 컨테이너 네트워크 인터페이스(CNI)
  • +
  • 인그레스 컨트롤러와 로드밸런서
  • +
  • Nitro 인스턴스
  • +
  • cert-manager와 ACM Private CA
  • +
+

네트워크 정책

+

쿠버네티스 클러스터 내에서는 기본적으로 모든 파드와 파드 간의 통신이 허용된다. 이러한 유연성은 실험을 촉진하는 데 도움이 될 수 있지만 안전한 것으로 간주되지는 않습니다. 쿠버네티스 네트워크 정책은 파드 간 통신(East/West 트래픽이라고도 함)과 파드와 외부 서비스 간의 네트워크 트래픽을 제한하는 메커니즘을 제공합니다. 쿠버네티스 네트워크 정책은 OSI 모델의 계층 3과 4에서 작동합니다. 네트워크 정책은 파드, 네임스페이스 셀렉터 및 레이블을 사용하여 소스 및 대상 파드를 식별하지만 IP 주소, 포트 번호, 프로토콜 또는 이들의 조합을 포함할 수도 있습니다. 네트워크 정책은 파드에 대한 인바운드 또는 아웃바운드 연결 모두에 적용할 수 있으며, 이를 인그레스(ingress) 및 이그레스(egress) 규칙이라고도 합니다.

+

Amazon VPC CNI 플러그인의 기본 네트워크 정책 지원을 통해 네트워크 정책을 구현하여 쿠버네티스 클러스터의 네트워크 트래픽을 보호할 수 있습니다. 이는 업스트림 쿠버네티스 네트워크 정책 API와 통합되어 호환성과 쿠버네티스 표준 준수를 보장합니다. 업스트림 API에서 지원하는 다양한 식별자를 사용하여 정책을 정의할 수 있습니다. 기본적으로 모든 수신 및 송신 트래픽은 파드에 허용됩니다. PolicyType Ingress가 포함된 네트워크 정책을 지정하는 경우 파드 노드의 연결과 인그레스 규칙에서 허용하는 연결만 파드에 대한 연결만 허용됩니다. 이그레스 규칙에도 동일하게 적용됩니다. 여러 규칙이 정의된 경우 결정을 내릴 때 모든 규칙의 통합을 고려합니다. 따라서 평가 순서는 정책 결과에 영향을 미치지 않습니다.

+
+

Attention

+

EKS 클러스터를 처음 프로비저닝할 때 VPC CNI 네트워크 정책 기능은 기본적으로 활성화되지 않습니다. 지원되는 VPC CNI 애드온 버전을 배포했는지 확인하고 vpc-cni 애드온에서 ENABLE_NETWORK_POLICY 플래그를 true로 설정하여 이를 활성화하세요. 자세한 지침은 Amazon EKS 사용자 가이드를 참조하십시오.

+
+

권장사항

+

네트워크 정책 시작하기 - 최소 권한 원칙 적용

+

디폴트 거부(deny) 정책 만들기

+

RBAC 정책과 마찬가지로 네트워크 정책에서도 최소 권한 액세스 원칙을 따르는 것이 좋습니다. 먼저 네임스페이스 내에서 모든 인바운드 및 아웃바운드 트래픽을 제한하는 '모두 거부' 정책을 만드세요.

+
apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: default-deny
+  namespace: default
+spec:
+  podSelector: {}
+  policyTypes:
+  - Ingress
+  - Egress
+
+

default-deny

+
+

Tip

+

위 이미지는 Tufin의 네트워크 정책 뷰어로 생성되었습니다.

+
+

DNS 쿼리를 허용하는 규칙 만들기

+

기본 거부 모든 규칙을 적용한 후에는 파드가 이름 확인을 위해 CoreDNS를 쿼리하도록 허용하는 전역 규칙과 같은 추가 규칙에 계층화를 시작할 수 있습니다. 네임스페이스에 레이블을 지정하여 시작합니다.

+
apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: allow-dns-access
+  namespace: default
+spec:
+  podSelector:
+    matchLabels: {}
+  policyTypes:
+  - Egress
+  egress:
+  - to:
+    - namespaceSelector:
+        matchLabels:
+          kubernetes.io/metadata.name: kube-system
+      podSelector:
+        matchLabels:
+          k8s-app: kube-dns
+    ports:
+    - protocol: UDP
+      port: 53
+
+

allow-dns-access

+

네임스페이스/파드 간 트래픽 흐름을 선택적으로 허용하는 규칙을 점진적으로 추가

+

애플리케이션 요구 사항을 이해하고 필요에 따라 세분화된 수신 및 송신 규칙을 생성하십시오. 아래 예는 포트 80의 인그레스 트래픽을 client-one에서 app-one으로 제한하는 방법을 보여줍니다. 이렇게 하면 공격 표면을 최소화하고 인증되지 않은 접근에 대한 위험을 줄일 수 있습니다.

+
apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: allow-ingress-app-one
+  namespace: default
+spec:
+  podSelector:
+    matchLabels:
+      k8s-app: app-one
+  policyTypes:
+  - Ingress
+  ingress:
+  - from:
+    - podSelector:
+        matchLabels:
+          k8s-app: client-one
+    ports:
+    - protocol: TCP
+      port: 80
+
+

allow-ingress-app-one

+

네트워크 정책 적용 모니터링

+
    +
  • 네트워크 정책 편집기 사용
  • +
  • 네트워크 정책 편집기는 네트워크 흐름 로그의 시각화, 보안 점수, 자동 생성을 지원합니다.
  • +
  • 상호활동적으로 네트워크 정책 구성하세요.
  • +
  • 로그 감사
  • +
  • EKS 클러스터의 감사 로그를 정기적으로 검토하세요.
  • +
  • 감사 로그는 네트워크 정책 변경을 포함하여 클러스터에서 수행된 작업에 대한 풍부한 정보를 제공합니다.
  • +
  • 이 정보를 사용하여 시간 경과에 따른 네트워크 정책 변경 사항을 추적하고 승인되지 않았거나 예상치 못한 변경을 감지할 수 있습니다.
  • +
  • 테스트 자동화
  • +
  • 운영 환경을 미러링하는 테스트 환경을 만들고 네트워크 정책을 위반하려는 워크로드를 정기적으로 배포하여 자동화된 테스트를 구현하십시오.
  • +
  • 메트릭 지표 모니터링
  • +
  • VPC CNI 노드 에이전트에서 프로메테우스 메트릭을 수집하도록 옵저버빌리티 에이전트를 구성하여 에이전트 상태 및 SDK 오류를 모니터링할 수 있습니다.
  • +
  • 정기적으로 네트워크 정책을 감사
  • +
  • 네트워크 정책을 정기적으로 감사하여 현재 애플리케이션 요구 사항을 충족하는지 확인하십시오.애플리케이션이 발전함에 따라 감사를 통해 중복된 인그레스, 이그레스 규칙을 제거하고 애플리케이션에 과도한 권한이 부여되지 않도록 할 수 있습니다.
  • +
  • Open Policy Agent(OPA)를 사용하여 네트워크 정책이 존재하는지 확인
  • +
  • 아래와 같은 OPA 정책을 사용하여 애플리케이션 파드를 온보딩하기 전에 네트워크 정책이 항상 존재하는지 확인하십시오. 이 정책은 해당 네트워크 정책이 없는 경우 k8s-app: sample-app이라는 레이블이 붙은 k8s 파드의 온보딩을 거부합니다.
  • +
+
package kubernetes.admission
+import data.kubernetes.networkpolicies
+
+deny[msg] {
+    input.request.kind.kind == "Pod"
+    pod_label_value := {v["k8s-app"] | v := input.request.object.metadata.labels}
+    contains_label(pod_label_value, "sample-app")
+    np_label_value := {v["k8s-app"] | v := networkpolicies[_].spec.podSelector.matchLabels}
+    not contains_label(np_label_value, "sample-app")
+    msg:= sprintf("The Pod %v could not be created because it is missing an associated Network Policy.", [input.request.object.metadata.name])
+}
+contains_label(arr, val) {
+    arr[_] == val
+}
+
+

문제 해결 (트러블슈팅)

+

vpc-network-policy-controller 및 node-agent 로그 모니터링

+

EKS 컨트롤 플레인의 컨트롤러 매니저 로그를 활성화하여 네트워크 정책 기능을 진단합니다. 컨트롤 플레인 로그를 CloudWatch 로그 그룹으로 스트리밍하고 CloudWatch Log Insights를 사용하여 고급 쿼리를 수행할 수 있습니다. 로그에서 네트워크 정책으로 확인된 파드 엔드포인트 객체, 정책의 조정 상태를 확인하고 정책이 예상대로 작동하는지 디버깅할 수 있습니다.

+

또한 Amazon VPC CNI를 사용하면 EKS 워커 노드에서 정책 적용 로그를 수집하고 Amazon Cloudwatch 로 내보낼 수 있습니다. 활성화되면 CloudWatch Container Insights를 활용하여 네트워크 정책과 관련된 사용에 대한 통찰력을 제공할 수 있습니다.

+

또한 Amazon VPC CNI는 노드 내 eBPF 프로그램과 상호 작용할 수 있는 인터페이스를 제공하는 SDK를 제공합니다. SDK는 aws-node가 노드에 배포될 때 설치됩니다. 노드의 /opt/cni/bin 디렉터리에서 설치된 SDK 바이너리를 찾을 수 있습니다. 출시 시 SDK는 eBPF 프로그램 및 맵 검사와 같은 기본 기능을 지원합니다.

+
sudo /opt/cni/bin/aws-eks-na-cli ebpf progs
+
+

네트워크 트래픽 메타데이터 로깅

+

AWS VPC Flow Logs는 VPC를 통과하는 트래픽에 대한 메타데이터(예: 소스 및 대상 IP 주소, 포트)를 허용/드랍된 패킷과 함께 캡처합니다. 이 정보를 분석하여 VPC 내 리소스(파드 포함) 간에 의심스럽거나 특이한 활동이 있는지 확인할 수 있습니다. 하지만 파드의 IP 주소는 교체될 때 자주 변경되므로 플로우 로그만으로는 충분하지 않을 수 있다. Calico Enterprise는 파드 레이블 및 기타 메타데이터를 사용하여 플로우 로그를 확장하여 파드 간 트래픽 흐름을 더 쉽게 해독할 수 있도록 합니다.

+

보안 그룹

+

EKS는 AWS VPC 보안 그룹(SG)을 사용하여 쿠버네티스 컨트롤 플레인과 클러스터의 워커 노드 사이의 트래픽을 제어합니다. 보안 그룹은 워커 노드, 기타 VPC 리소스 및 외부 IP 주소 간의 트래픽을 제어하는 데에도 사용됩니다. EKS 클러스터 (쿠버네티스 버전 1.14-eks.3 이상)를 프로비저닝하면 클러스터 보안 그룹이 자동으로 생성됩니다. 이 보안 그룹은 EKS 컨트롤 플레인과 관리형 노드 그룹의 노드 간의 자유로운 통신을 허용합니다. 단순화를 위해 비관리형 노드 그룹을 포함한 모든 노드 그룹에 클러스터 SG를 추가하는 것이 좋습니다.

+

쿠버네티스 버전 1.14 및 EKS 버전 eks.3 이전에는 EKS 컨트롤 플레인 및 노드 그룹에 대해 별도의 보안 그룹이 구성되었습니다. 컨트롤 플레인 및 노드 그룹 보안 그룹에 대한 최소 및 권장 규칙은 AWS 문서에서 확인할 수 있습니다. 컨트롤 플레인 보안 그룹 의 최소 규칙에 따라 워커 노드 보안그룹에서 포트 443을 인바운드할 수 있습니다. 이 규칙은 kubelets가 쿠버네티스 API 서버와 통신할 수 있도록 하는 규칙입니다. 또한 워커 노드 보안그룹로의 아웃바운드 트래픽을 위한 포트 10250도 포함되어 있습니다. 10250은 kubelet이 수신하는 포트입니다. 마찬가지로 최소 노드 그룹 규칙은 컨트롤 플레인 보안그룹에서 포트 10250을 인바운드하고 컨트롤 플레인 보안그룹로 아웃바운드하는 443을 허용합니다. 마지막으로 노드 그룹 내 노드 간의 자유로운 통신을 허용하는 규칙이 있습니다.

+

클러스터 내에서 실행되는 서비스와 RDS 데이터베이스와 같이 클러스터 외부에서 실행되는 서비스 간의 통신을 제어해야 하는 경우 파드용 보안 그룹을 고려해 보세요. 파드용 보안 그룹을 사용하면 파드 컬렉션에 기존 보안 그룹을 할당할 수 있다.

+
+

Warning

+

파드를 생성하기 전에 존재하지 않는 보안 그룹을 참조하는 경우, 파드는 스케줄링되지 않는다.

+
+

SecurityGroupPolicy 객체를 생성하고 PodSelector 또는 ServiceAccountSelector를 지정하여 어떤 파드를 보안 그룹에 할당할지 제어할 수 있습니다. 셀렉터를 {}로 설정하면 SecurityGroupPolicy에서 참조하는 보안그룹이 네임스페이스의 모든 파드 또는 네임스페이스의 모든 서비스 어카운트에 할당됩니다. 파드용 보안 그룹을 구현하기 전에 고려 사항을 모두 숙지해야 합니다.

+
+

Important

+

파드에 보안그룹을 사용하는 경우 클러스터 보안 그룹에 포트 53이 아웃바운드되도록 허용하는 보안그룹을 생성해야 합니다. 마찬가지로, 파드 보안 그룹의 포트 53 인바운드 트래픽을 허용하도록 클러스터 보안 그룹을 업데이트해야 합니다.

+
+
+

Important

+

파드용 보안 그룹을 사용할 때에도 보안 그룹 제한사항이 여전히 적용되므로 신중하게 사용해야 합니다.

+
+
+

Important

+

파드에 구성된 모든 프로브에 대해 클러스터 보안 그룹 (kubelet)의 인바운드 트래픽에 대한 규칙을 필수 생성해야 합니다.

+
+
+

Important

+

파드의 보안 그룹은 EC2 인스턴스의 여러 개의 ENI를 할당 하기 위해 ENI 트렁킹 기능을 사용합니다. 파드가 보안그룹에 할당되면 VPC 컨트롤러는 노드 그룹의 브랜치 ENI를 파드와 연결합니다. 파드가 예약될 때 노드그룹에서 사용할 수 있는 브랜치 ENI가 충분하지 않은 경우 파드는 보류 상태로 유지됩니다. 인스턴스가 지원할 수 있는 브랜치 ENI의 수는 인스턴스 유형/패밀리에 따라 다릅니다. 자세한 내용은 AWS 문서를 참조하십시오.

+
+

파드용 보안 그룹은 정책 데몬의 오버헤드 없이 클러스터 내부 및 외부의 네트워크 트래픽을 제어할 수 있는 AWS 네이티브 방법을 제공하지만 다른 옵션도 사용할 수 있습니다. 예를 들어 Cilium 정책 엔진을 사용하면 네트워크 정책에서 DNS 이름을 참조할 수 있습니다. Calico Enterprise에는 네트워크 정책을 AWS 보안 그룹에 매핑하는 옵션이 포함되어 있습니다. Istio와 같은 서비스 메시를 구현한 경우, 이그레스 게이트웨이를 사용하여 네트워크 송신을 검증된 특정 도메인 또는 IP 주소로 제한할 수 있습니다. 이 옵션에 대한 자세한 내용은 Istio의 이그레스 트래픽 제어에 대한 3부작 시리즈를 참조하십시오.

+

언제 네트워크 정책과 파드용 보안 그룹을 사용해야 할까요?

+

쿠버네티스 네트워크 정책을 사용하는 경우

+
    +
  • 파드-파드 간 트래픽 제어
  • +
  • 클러스터 내 파드 간 네트워크 트래픽(이스트-웨스트 트래픽) 제어에 적합
  • +
  • IP 주소 또는 포트 수준에서 트래픽 제어 (OSI 계층 3 또는 4)
  • +
+

파드용 AWS 보안 그룹(SGP) 을 사용해야 하는 경우

+
    +
  • 기존 AWS 구성 활용
  • +
  • AWS 서비스에 대한 액세스를 관리하는 복잡한 EC2 보안 그룹이 이미 있고 애플리케이션을 EC2 인스턴스에서 EKS로 마이그레이션하는 경우 SGP는 보안 그룹 리소스를 재사용하고 이를 파드에 적용할 수 있는 매우 좋은 선택이 될 수 있습니다.
  • +
  • AWS 서비스에 대한 접근 제어
  • +
  • EKS 클러스터 내에서 실행되는 애플리케이션이 다른 AWS 서비스(RDS 데이터베이스)와 통신하려는 경우 SGP를 효율적인 메커니즘으로 사용하여 파드에서 AWS 서비스로의 트래픽을 제어합니다.
  • +
  • 파드 및 노드 트래픽 격리
  • +
  • 파드 트래픽을 나머지 노드 트래픽과 완전히 분리하려면 POD_SECURITY_GROUP_ENFORCING_MODE=strict 모드에서 SGP를 사용하십시오.
  • +
+

파드용 보안 그룹네트워크 정책 모범 사례

+
    +
  • 레이어별 보안
  • +
  • 계층화된 보안 접근 방식을 위해 SGP와 쿠버네티스 네트워크 정책을 함께 사용하십시오.
  • +
  • SGP를 사용하여 클러스터에 속하지 않은 AWS 서비스에 대한 네트워크 수준 액세스를 제한하는 반면, 쿠버네티스 네트워크 정책은 클러스터 내 파드 간 네트워크 트래픽을 제한할 수 있습니다.
  • +
  • 최소 권한 원칙
  • +
  • 파드 또는 네임스페이스 간에 필요한 트래픽만 허용
  • +
  • 애플리케이션 격리
  • +
  • 가능한 경우 네트워크 정책에 따라 애플리케이션을 구분하여 애플리케이션이 손상된 경우 침해 범위를 줄이십시오.
  • +
  • 정책을 단순하고 명확하게 유지
  • +
  • 쿠버네티스 네트워크 정책은 매우 세밀하고 복잡할 수 있으므로 잘못된 구성의 위험을 줄이고 관리 오버헤드를 줄이려면 가능한 한 단순하게 유지하는 것이 가장 좋습니다.
  • +
  • 공격 범위 축소
  • +
  • 애플리케이션 노출을 제한하여 공격 표면을 최소화합니다.
  • +
+
+

Caution

+

파드용 보안 그룹은 strictstandard이라는 두 가지 적용 모드를 제공합니다. EKS 클러스터의 파드 기능에 네트워크 정책과 보안 그룹을 모두 사용할 때는 standard 모드를 사용해야 합니다.

+
+

네트워크 보안과 관련해서는 계층화된 접근 방식이 가장 효과적인 솔루션인 경우가 많습니다. 쿠버네티스 네트워크 정책과 SGP를 함께 사용하면 EKS에서 실행되는 애플리케이션을 위한 강력한 심층 방어 전략을 제공할 수 있습니다.

+

서비스 메시 정책 적용 또는 쿠버네티스 네트워크 정책

+

'서비스 메시'는 애플리케이션에 추가할 수 있는 전용 인프라 계층입니다. 이를 통해 가시성, 트래픽 관리, 보안 등의 기능을 자체 코드에 추가하지 않고도 투명하게 추가할 수 있습니다.

+

서비스 메시는 OSI 모델의 계층 7 (애플리케이션) 에서 정책을 적용하는 반면, 쿠버네티스 네트워크 정책은 계층 3 (네트워크) 및 계층 4 (전송) 에서 작동합니다.이 분야에는 AWS AppMesh, Istio, Linkerd 등과 같은 다양한 제품이 있습니다.

+

정책 시행에 서비스 메시를 사용하는 경우

+
    +
  • 서비스 메쉬가 구성되어 있는 경우
  • +
  • 트래픽 관리, 옵저버빌리티 및 보안과 같은 고급 기능이 필요한 경우
  • +
  • 트래픽 제어, 로드 밸런싱, 서킷 브레이킹, 속도 제한, 타임아웃 등
  • +
  • 서비스가 잘 동작하는지에 대한 자세한 인사이트 (레이턴시, 오류율, 초당 요청 수, 요청량 등)
  • +
  • mTLS과 같은 보안 기능을 위해 서비스 메시를 구현하고 활용하고자 합니다.
  • +
+

더 간단한 사용 사례를 위해 쿠버네티스 네트워크 정책을 선택하세요

+
    +
  • 서로 통신할 수 있는 파드를 제한하세요.
  • +
  • 네트워크 정책은 서비스 메시보다 필요한 리소스가 적기 때문에 단순한 사용 사례나 서비스 메시의 실행 및 관리 오버헤드가 정당하지 않을 수 있는 소규모 클러스터에 적합합니다.
  • +
+
+

Tip

+

네트워크 정책과 서비스 메시를 함께 사용할 수도 있습니다. 네트워크 정책을 사용하여 파드 간에 기본 수준의 보안 및 격리를 제공한 다음 서비스 메시를 사용하여 트래픽 관리, 관찰 가능성 및 보안과 같은 추가 기능을 추가합니다.

+
+

서드파티 네트워크 정책 엔진

+

글로벌 네트워크 정책, DNS 호스트 이름 기반 규칙 지원, 계층 7 규칙, 서비스 어카운트 기반 규칙, 명시적 거부/로그 작업 등과 같은 고급 정책 요구 사항이 있는 경우 타사 네트워크 정책 엔진을 고려해 보십시오. Calico는 EKS와 잘 작동하는 Tigera의 오픈 소스 정책 엔진입니다. Calico는 Kubernetes 네트워크 정책 기능 전체를 구현하는 것 외에도 Istio와 통합될 경우 계층 7 규칙 (예: HTTP)에 대한 지원을 포함하여 더 다양한 기능을 갖춘 확장 네트워크 정책을 지원합니다. Calico 정책의 범위는 네임스페이스, 파드, 서비스 어카운트 또는 전 세계로 지정할 수 있습니다. 정책 범위를 서비스 어카운트으로 지정하면 수신/송신 규칙 집합이 해당 서비스 어카운트과 연결됩니다. 적절한 RBAC 규칙을 적용하면 팀에서 이런 규칙을 재정의하는 것을 방지하여 IT 보안 전문가가 네임스페이스 관리를 안전하게 위임할 수 있습니다. 마찬가지로 Cilium의 관리자들도 HTTP와 같은 계층 7 규칙에 대한 부분적 지원을 포함하도록 네트워크 정책을 확장했습니다. 또한 Cilium은 DNS 호스트 이름을 지원하는데, 이는 쿠버네티스 서비스/파드와 VPC 내부 또는 외부에서 실행되는 리소스 간의 트래픽을 제한하는 데 유용할 수 있다. 반대로 Calico Enterprise에는 쿠버네티스 네트워크 정책을 AWS 보안 그룹과 DNS 호스트 이름에 매핑할 수 있는 기능이 포함되어 있습니다.

+

이 Github 프로젝트에서 일반적인 쿠버네티스 네트워크 정책 목록을 찾을 수 있습니다. Calico에 대한 유사한 규칙 세트는 Calico 문서에서 확인할 수 있습니다.

+

Amazon VPC CNI 네트워크 정책 엔진으로 마이그레이션

+

일관성을 유지하고 예상치 못한 파드 통신 동작을 방지하려면 클러스터에 네트워크 정책 엔진을 하나만 배포하는 것이 좋습니다. 3P에서 VPC CNI 네트워크 정책 엔진으로 마이그레이션하려는 경우 VPC CNI 네트워크 정책 지원을 활성화하기 전에 기존 3P 네트워크 정책 CRD를 쿠버네티스 네트워크 정책 리소스로 변환하는 것이 좋습니다. 또한 마이그레이션된 정책을 프로덕션 환경에 적용하기 전에 별도의 테스트 클러스터에서 테스트하세요. 이를 통해 파드 통신 동작의 잠재적 문제나 불일치를 식별하고 해결할 수 있습니다.

+

마이그레이션 도구

+

마이그레이션 프로세스를 지원하기 위해 기존 Calico/Cilium 네트워크 정책 CRD를 쿠버네티스 네이티브 네트워크 정책으로 변환하는 K8s Network Policy Migrator 도구를 개발했습니다. 변환 후에는 VPC CNI 네트워크 정책 컨트롤러를 실행하는 새 클러스터에서 변환된 네트워크 정책을 직접 테스트할 수 있습니다. 이 도구는 마이그레이션 프로세스를 간소화하고 원활한 전환을 보장하도록 설계되었습니다.

+
+

Important

+

마이그레이션 도구는 네이티브 쿠버네티스 네트워크 정책 API와 호환되는 서드파티 정책만 변환합니다. 서드파티 플러그인이 제공하는 고급 네트워크 정책 기능을 사용하는 경우 마이그레이션 도구는 해당 기능을 건너뛰고 보고합니다.

+
+

참고로, 마이그레이션 도구는 현재 AWS VPC CNI 네트워크 정책 엔지니어링 팀에서 지원하지 않으며, 고객이 최선의 노력을 기울여 사용할 수 있도록 만들어졌습니다. 마이그레이션 프로세스를 원활하게 진행하려면 이 도구를 활용하는 것이 좋습니다. 도구에서 문제나 버그가 발생하는 경우 Github 이슈를 생성해 주시기 바랍니다. 여러분의 피드백은 우리에게 매우 소중하며 서비스를 지속적으로 개선하는 데 도움이 됩니다.

+

추가 리소스

+ +

전송 암호화

+

PCI, HIPAA 또는 기타 규정을 준수해야 하는 애플리케이션은 전송 중에 데이터를 암호화해야 합니다. 오늘날 TLS는 유선 트래픽을 암호화하기 위한 사실상 표준 방식입니다. TLS는 이전 SSL과 마찬가지로 암호화 프로토콜을 사용하여 네트워크를 통해 보안 통신을 제공합니다. TLS는 세션 시작 시 협상되는 공유 암호를 기반으로 데이터를 암호화하는 키를 생성하는 대칭 암호화를 사용합니다. 다음은 쿠버네티스 환경에서 데이터를 암호화할 수 있는 몇 가지 방법입니다.

+

Nitro 인스턴스

+

다음 Nitro 인스턴스 유형 (예: C5n, G4, I3en, M5dn, M5n, P3dn, R5dn, R5n)간에 교환되는 트래픽은 기본적으로 자동 암호화됩니다. Transit Gateway 또는 로드밸런서와 같이 중간 홉이 있는 경우 트래픽은 암호화되지 않습니다. 전송 중 암호화에 대한 자세한 내용과 기본적으로 네트워크 암호화를 지원하는 인스턴스 유형의 전체 목록은 전송 암호화 문서를 참조하십시오.

+

컨테이너 네트워크 인터페이스(CNI)

+

WeAvenet은 슬리브 트래픽(더 느린 패킷 포워딩 접근 방식)에는 NaCL 암호화를 사용하고 빠른 데이터 경로 트래픽에는 IPsec ESP를 사용하여 모든 트래픽을 자동으로 암호화하도록 구성할 수 있습니다.

+

서비스 메시

+

전송 중 암호화는 AppMesh, Linkerd v2 및 Istio와 같은 서비스 메시를 사용하여 구현할 수도 있습니다. AppMesh는 X.509 인증서 또는 Envoy의 비밀 검색 서비스(SDS)를 사용하는 mTLS를 지원합니다. Linkerd와 Istio 모두 mTLS를 지원합니다.

+

aws-app-mesh-examples GitHub 리포지토리는 X.509 인증서 및 SPIRE를 Envoy 컨테이너와 함께 SDS 공급자로 사용하여 MTL을 구성하는 방법을 제공합니다:

+ +

또한 App Mesh는 AWS Certificate Manager(ACM)에서 발급한 사설 인증서 또는 가상 노드의 로컬 파일 시스템에 저장된 인증서를 사용하여 TLS 암호화를 지원합니다.

+ +

인그레스 컨트롤러 및 로드밸런서

+

인그레스 컨트롤러는 클러스터 외부에서 발생하는 HTTP/S 트래픽을 클러스터 내에서 실행되는 서비스로 지능적으로 라우팅하는 방법입니다. 이런 인그레스 앞에 CLB(Classic Load Balancer) 또는 NLB(Network Load Balancer)와 같은 레이어 4 로드밸런서가 있는 경우가 많습니다. 암호화된 트래픽은 네트워크 내 여러 위치 (예: 로드밸런서, 인그레스 리소스, 파드) 에서 종료될 수 있다. SSL 연결을 종료하는 방법과 위치는 궁극적으로 조직의 네트워크 보안 정책에 따라 결정됩니다. 예를 들어 엔드 투 엔드 암호화를 요구하는 정책이 있는 경우 Pod에서 트래픽을 해독해야 합니다. 이렇게 되면 파드가 초기 핸드셰이크를 설정하는 데 많은 시간을 소비해야 하므로 파드에 추가적인 부담이 가중됩니다. 전체 SSL/TLS 처리는 CPU 집약도가 매우 높습니다. 따라서 유연성이 있다면 인그레스 또는 로드밸런서에서 SSL 오프로드를 수행해 보세요.

+

AWS Elastic 로드밸런서를 통한 암호화 사용

+

AWS Application Load Balancer(ALB) 및 Network Load Balancer(NLB) 모두 전송 암호화(SSL 및 TLS)를 지원합니다. ALB에 대한 alb.ingress.kubernetes.io/certificate-arn 어노테이션을 사용하면 ALB에 추가할 인증서를 지정할 수 있습니다. 어노테이션을 생략하면 컨트롤러는 호스트 필드를 사용하여 사용 가능한 AWS Certificate Manager (ACM)인증서를 일치시켜 인증서를 필요로 하는 리스너에 인증서를 추가하려고 시도합니다. EKS v1.15부터 아래 예와 같이 NLB와 함께 service.beta.kubernetes.io/aws-load-balancer-ssl-cert 어노테이션을 사용할 수 있습니다.

+
apiVersion: v1
+kind: Service
+metadata:
+  name: demo-app
+  namespace: default
+  labels:
+    app: demo-app
+  annotations:
+     service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
+     service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "<certificate ARN>"
+     service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
+     service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
+spec:
+  type: LoadBalancer
+  ports:
+  - port: 443
+    targetPort: 80
+    protocol: TCP
+  selector:
+    app: demo-app
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+  name: nginx
+  namespace: default
+  labels:
+    app: demo-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: demo-app
+  template:
+    metadata:
+      labels:
+        app: demo-app
+    spec:
+      containers:
+        - name: nginx
+          image: nginx
+          ports:
+            - containerPort: 443
+              protocol: TCP
+            - containerPort: 80
+              protocol: TCP
+
+

다음은 SSL/TLS 종료에 대한 추가 예입니다.

+ +
+

Caution

+

AWS LB 컨트롤러와 같은 일부 인그레스는 인그레스 사양의 일부가 아닌 어노테이션을 사용하여 SSL/TLS를 구현합니다.

+
+

ACM Private CA 연동 (cert-manager)

+

인증서를 배포, 갱신 및 취소하는 인기 있는 쿠버네티스 애드온인 ACM Private Certificate Authority(CA) 및 cert-manager를 사용하여 수신, 파드, 파드 간에 EKS 애플리케이션 워크로드를 보호하도록 TLS와 mTLS를 활성화할 수 있습니다. ACM Private CA는 자체 CA를 관리하는 데 드는 선결제 및 유지 관리 비용이 없는 가용성이 높고 안전한 관리형 CA입니다. 기본 쿠버네티스 인증 기관을 사용하는 경우 ACM Private CA를 통해 보안을 개선하고 규정 준수 요구 사항을 충족할 수 있습니다. ACM Private CA는 FIPS 140-2 레벨 3 하드웨어 보안 모듈에서 프라이빗 키를 보호합니다(매우 안전함). 이는 메모리에 인코딩된 키를 저장하는 기본 CA (보안 수준이 낮음)와 비교하면 매우 안전합니다. 또한 중앙 집중식 CA를 사용하면 쿠버네티스 환경 내부 및 외부에서 사설 인증서를 더 잘 제어하고 감사 기능을 개선할 수 있습니다.

+

워크로드 간 상호 TLS를 위한 짧은 수명의 CA 모드

+

EKS 환경에서에서 mTLS용 ACM Private CA를 사용할 때는 _수명이 짧은 CA 모드_와 함께 수명이 짧은 인증서를 사용하는 것이 좋습니다. 범용 CA 모드에서 수명이 짧은 인증서를 발급할 수 있지만 새 인증서를 자주 발급해야 하는 사용 사례에서는 수명이 짧은 CA 모드를 사용하는 것이 더 비용 효율적입니다 (일반 모드보다 최대 75% 저렴). 이 외에도 사설 인증서의 유효 기간을 EKS 클러스터의 파드 수명에 맞춰 조정해야 합니다 여기에서 ACM Private CA와 그 이점에 대해 자세히 알아보십시오.

+

ACM 구성 가이드

+

먼저 ACM Private CA 문서에 제공된 절차에 따라 Private CA를 생성하십시오. Private CA를 만든 후에는 cert-manager 설치 가이드 절차에 따라 cert-manager를 설치하십시오. cert-manager를 설치한 후 GitHub의 설정 가이드에 따라 Private CA 쿠버네티스 인증서 관리자 플러그인을 설치합니다. 플러그인을 사용하면 인증서 관리자가 ACM Private CA에 사설 인증서를 요청할 수 있습니다.

+

이제 cert-manager와 플러그인이 설치된 사설 CA와 EKS 클러스터를 만들었으니, 권한을 설정하고 발급자를 생성할 차례입니다. ACM 사설 CA에 대한 액세스를 허용하도록 EKS 노드 역할의 IAM 권한을 업데이트하십시오. <CA_ARN>를 사설 CA의 값으로 바꾸십시오.

+
{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "awspcaissuer",
+            "Action": [
+                "acm-pca:DescribeCertificateAuthority",
+                "acm-pca:GetCertificate",
+                "acm-pca:IssueCertificate"
+            ],
+            "Effect": "Allow",
+            "Resource": "<CA_ARN>"
+        }
+    ]
+}
+
+

서비스 어카운트용 IAM 역할(IRSA)도 사용할 수 있습니다. 전체 예는 아래의 추가 리소스 섹션을 참조하십시오.

+

아래 예제의 내용을 포함한 cluster-issuer.yaml CRD 파일을 생성하고 <CA_ARN><Region> 영역을 Private CA 정보로 대체하여 Amazon EKS에서 발급자를 생성합니다.

+
apiVersion: awspca.cert-manager.io/v1beta1
+kind: AWSPCAClusterIssuer
+metadata:
+          name: demo-test-root-ca
+spec:
+          arn: <CA_ARN>
+          region: <Region>
+
+

Deploy the Issuer you created.

+
kubectl apply -f cluster-issuer.yaml
+
+

EKS 클러스터는 사설 CA에서 인증서를 요청하도록 구성되어 있습니다. 이제 cert-manager의 Certificate 리소스를 사용하여 IssuerRef 필드 값을 위에서 만든 사설 CA 발급자로 변경하여 인증서를 발급할 수 있습니다. 인증서 리소스를 지정하고 요청하는 방법에 대한 자세한 내용은 cert-manager의 인증서 리소스 가이드를 참조하십시오. 다음 예제를 참조하세요..

+

ACM Private CA 연동 (Istio 및 cert-manager)

+

EKS 클러스터에서 Istio를 실행하는 경우 Istio 컨트롤 플레인 (특히 'istiod') 이 루트 인증 기관 (CA) 으로 작동하지 않도록 설정하고 ACM Private CA를 워크로드 간 MTL의 루트 CA로 구성할 수 있습니다. 이 방법을 사용하려는 경우 ACM Private CA에서 _단기 CA 모드_를 사용하는 것이 좋습니다. 자세한 내용은 이전 섹션 및 이 블로그를 참조하십시오.

+

Istio에서 인증서 서명 작동 방식 (기본방식)

+

쿠버네티스의 워크로드는 서비스 어카운트를 사용하여 식별됩니다. 서비스 어카운트를 지정하지 않으면 쿠버네티스는 워크로드에 서비스 어카운트를 자동으로 할당합니다. 또한 서비스 어카운트는 관련 토큰을 자동으로 마운트합니다. 이 토큰은 서비스 어카운트에서 쿠버네티스 API에 대한 인증을 위한 워크로드에 사용됩니다. 서비스 어카운트는 쿠버네티스의 ID로 충분할 수 있지만 Istio에는 자체 ID 관리 시스템과 CA가 있습니다. Envoy 사이드카 프록시로 워크로드를 시작하는 경우 Istio에서 할당한 ID가 있어야 해당 워크로드가 신뢰할 수 있는 것으로 간주되고 메시의 다른 서비스와 통신할 수 있습니다.

+

Istio에서 이 ID를 가져오기 위해 'istio-agent'는 인증서 서명 요청(또는 CSR) 이라는 요청을 Istio 컨트롤 플레인에 보냅니다. 이 CSR에는 처리 전에 워크로드의 ID를 확인할 수 있도록 서비스 어카운트 토큰이 포함되어 있습니다. 이 확인 프로세스는 등록 기관(또는 RA)과 CA 역할을 모두 하는 'istiod'에 의해 처리됩니다. RA는 검증된 CSR만 CA에 전달되도록 하는 게이트키퍼 역할을 합니다. CSR이 확인되면 CA로 전달되며 CA는 서비스 어카운트과 함께 SPIFFE ID가 포함된 인증서를 발급합니다. 이 인증서를 SPIFE 검증 가능한 ID 문서(또는 SVID) 라고 합니다. SVID는 요청 서비스에 할당되어 식별을 목적으로 하고 통신 서비스 간에 전송되는 트래픽을 암호화합니다.

+

Istio 인증서 서명 요청의 기본 흐름

+

ACM Private CA를 사용하여 Istio에서 인증서 서명이 작동하는 방식

+

Istio 인증서 서명 요청 에이전트 (istio-csr)라는 인증서 관리 애드온을 사용하여 Istio를 ACM 사설 CA와 통합할 수 있습니다. 이 에이전트를 사용하면 인증서 관리자 발급자(이 경우 ACM Private CA)를 통해 Istio 워크로드와 컨트롤 플레인 구성 요소를 보호할 수 있습니다. istio-csr 에이전트는 수신 CSR을 검증하는 기본 구성에서 _istiod_가 제공하는 것과 동일한 서비스를 제공합니다. 단, 확인 후에는 요청을 인증서 관리자가 지원하는 리소스(예: 외부 CA 발급자와의 통합)로 변환합니다.

+

워크로드에서 CSR이 수신될 때마다 해당 CSR은 _istio-csr_로 전달되며, 이 CSR은 ACM 사설 CA로부터 인증서를 요청합니다. _istio-csr_와 ACM Private CA 간의 이런 통신은 AWS Private CA 발급자 플러그인을 통해 활성화됩니다. 인증서 관리자는 이 플러그인을 사용하여 ACM 사설 CA에 TLS 인증서를 요청합니다. 발급자 플러그인은 ACM 사설 CA 서비스와 통신하여 워크로드에 대한 서명된 인증서를 요청합니다. 인증서가 서명되면 이 인증서는 _istio-csr_로 반환되며, 그러면 서명된 요청을 읽고 CSR을 시작한 워크로드에 해당 요청을 반환합니다.

+

istio-csr를 사용한 Istio 인증서 서명 요청 흐름

+

사설 CA가 포함된 Istio 구성 가이드

+
    +
  1. 먼저 이 섹션의 설정 지침 에 따라 다음을 완료하십시오.:
  2. +
  3. 사설 CA 생성
  4. +
  5. cert-manager 설치
  6. +
  7. 발급자 플러그인 설치
  8. +
  9. 권한을 설정하고 발급자를 생성합니다. 발급자는 CA를 나타내며 'istiod' 및 메시 워크로드 인증서에 서명하는 데 사용됩니다. ACM 사설 CA와 통신합니다.
  10. +
  11. 'Istio-system' 네임스페이스를 생성합니다. 여기에 'istiod 인증서'와 기타 Istio 리소스가 배포됩니다.
  12. +
  13. +

    AWS 사설 CA 발급자 플러그인으로 구성된 Istio CSR을 설치합니다.워크로드에 대한 인증서 서명 요청을 보존하여 승인 및 서명 여부를 확인할 수 있습니다 (PerveCertificateRequests=true).

    +
    helm install -n cert-manager cert-manager-istio-csr jetstack/cert-manager-istio-csr \
    +  --set "app.certmanager.issuer.group=awspca.cert-manager.io" \
    +  --set "app.certmanager.issuer.kind=AWSPCAClusterIssuer" \
    +  --set "app.certmanager.issuer.name=<the-name-of-the-issuer-you-created>" \
    +  --set "app.certmanager.preserveCertificateRequests=true" \
    +  --set "app.server.maxCertificateDuration=48h" \
    +  --set "app.tls.certificateDuration=24h" \
    +  --set "app.tls.istiodCertificateDuration=24h" \
    +  --set "app.tls.rootCAFile=/var/run/secrets/istio-csr/ca.pem" \
    +  --set "volumeMounts[0].name=root-ca" \
    +  --set "volumeMounts[0].mountPath=/var/run/secrets/istio-csr" \
    +  --set "volumes[0].name=root-ca" \
    +  --set "volumes[0].secret.secretName=istio-root-ca"
    +
    +
  14. +
  15. +

    'istiod'를 'cert-manager istio-csr'로 바꾼 사용자 지정 설정으로 메시의 인증서 공급자로 Istio를 설치하세요. 이 프로세스는 Istio 오퍼레이터를 사용하여 수행할 수 있습니다.

    +
    apiVersion: install.istio.io/v1alpha1
    +kind: IstioOperator
    +metadata:
    +  name: istio
    +  namespace: istio-system
    +spec:
    +  profile: "demo"
    +  hub: gcr.io/istio-release
    +  values:
    +  global:
    +    # Change certificate provider to cert-manager istio agent for istio agent
    +    caAddress: cert-manager-istio-csr.cert-manager.svc:443
    +  components:
    +    pilot:
    +      k8s:
    +        env:
    +          # Disable istiod CA Sever functionality
    +        - name: ENABLE_CA_SERVER
    +          value: "false"
    +        overlays:
    +        - apiVersion: apps/v1
    +          kind: Deployment
    +          name: istiod
    +          patches:
    +
    +            # Mount istiod serving and webhook certificate from Secret mount
    +          - path: spec.template.spec.containers.[name:discovery].args[7]
    +            value: "--tlsCertFile=/etc/cert-manager/tls/tls.crt"
    +          - path: spec.template.spec.containers.[name:discovery].args[8]
    +            value: "--tlsKeyFile=/etc/cert-manager/tls/tls.key"
    +          - path: spec.template.spec.containers.[name:discovery].args[9]
    +            value: "--caCertFile=/etc/cert-manager/ca/root-cert.pem"
    +
    +          - path: spec.template.spec.containers.[name:discovery].volumeMounts[6]
    +            value:
    +              name: cert-manager
    +              mountPath: "/etc/cert-manager/tls"
    +              readOnly: true
    +          - path: spec.template.spec.containers.[name:discovery].volumeMounts[7]
    +            value:
    +              name: ca-root-cert
    +              mountPath: "/etc/cert-manager/ca"
    +              readOnly: true
    +
    +          - path: spec.template.spec.volumes[6]
    +            value:
    +              name: cert-manager
    +              secret:
    +                secretName: istiod-tls
    +          - path: spec.template.spec.volumes[7]
    +            value:
    +              name: ca-root-cert
    +              configMap:
    +                defaultMode: 420
    +                name: istio-ca-root-cert
    +
    +
  16. +
  17. +

    생성한 위의 사용자 지정 리소스(CRD)를 배포합니다.

    +
    istioctl operator init
    +kubectl apply -f istio-custom-config.yaml
    +
    +
  18. +
  19. +

    이제 EKS 클러스터의 메시에 워크로드를 배포하고 mTLS를 적용할 수 있습니다.

    +
  20. +
+

Istio 인증서 서명 요청

+

도구 및 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/pods/index.html b/ko/security/docs/pods/index.html new file mode 100644 index 000000000..b1be9be60 --- /dev/null +++ b/ko/security/docs/pods/index.html @@ -0,0 +1,3009 @@ + + + + + + + + + + + + + + + + + + + + + + + 파드 보안 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

파드 보안

+

파드 사양에는 전반적인 보안 태세를 강화하거나 약화시킬 수 있는 다양한 속성이 포함되어 있습니다. 쿠버네티스 실무자로서 주요 관심사는 컨테이너에서 실행 중인 프로세스가 컨테이너 런타임의 격리 경계를 벗어나 기본 호스트에 대한 액세스 권한을 얻지 못하도록 하는 것입니다.

+

리눅스 기능

+

컨테이너 내에서 실행되는 프로세스는 기본적으로 [Linux] 루트 사용자의 컨텍스트에서 실행됩니다. 컨테이너 내의 루트 작업은 컨테이너 런타임이 컨테이너에 할당하는 리눅스 기능 세트에 의해 부분적으로 제한되지만 이런 기본 권한을 통해 공격자는 권한을 에스컬레이션하거나 호스트에 바인딩된 민감한 정보에 액세스할 수 있습니다. 비밀 및 컨피그맵을 포함합니다. 다음은 컨테이너에 할당된 기본 기능 목록입니다. 각 기능에 대한 추가 정보는 해당 문서를 참조하십시오.

+

CAP_AUDIT_WRITE, CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_FOWNER, CAP_FSETID, CAP_KILL, CAP_MKNOD, CAP_NET_BIND_SERVICE, CAP_NET_RAW, CAP_SETGID, CAP_SETUID, CAP_SETFCAP, CAP_SETPCAP, CAP_SYS_CHROOT

+
+

Info

+
+

EC2 및 Fargate 파드에는 기본적으로 앞서 언급한 기능이 할당됩니다. 또한 Linux 기능은 Fargate 파드에서만 삭제할 수 있습니다.

+

Privileged 권한으로 실행되는 파드는 호스트의 루트와 연결된 Linux 기능의 모든 권한 을 상속합니다. 가능한 해당 권한으로 실행은 지양하여야 합니다.

+

노드 승인

+

모든 쿠버네티스 워커 노드는 노드 인증이라는 권한 부여 모드를 사용합니다.노드 인증은 kubelet에서 시작되는 모든 API 요청을 승인하고 노드가 다음 작업을 수행할 수 있도록 합니다.

+

읽기 작업:

+
    +
  • 서비스
  • +
  • 엔드포인트
  • +
  • 노드
  • +
  • 파드
  • +
  • kubelet의 노드에 바인딩된 파드와 관련된 시크릿, 컨피그맵, 퍼시스턴트 볼륨 클레임 및 퍼시스턴트 볼륨
  • +
+

쓰기 작업:

+
    +
  • 노드 및 노드 상태( NodeRestriction 어드미션 플러그인을 활성화하여 kubelet이 자신의 노드를 수정하도록 제한)
  • +
  • 파드 및 파드 상태( NodeRestriction 어드미션 플러그인을 활성화하여 kubelet이 자신에게 바인딩된 pod를 수정하도록 제한)
  • +
  • 이벤트
  • +
+

인증 관련 작업:

+
    +
  • TLS 부트스트래핑을 위한 인증서 서명 요청(CSR) API에 대한 읽기/쓰기 권한
  • +
  • 위임 인증/권한 확인을 위한 TokenReview 및 SubjectAccessReview 생성 권한
  • +
+

EKS는 노드 제한 어드미션 컨트롤러를 사용하여 노드가 제한된 노드 속성 및 파드 세트만 수정하도록 허용합니다. 노드에 바인딩된 개체입니다. 그럼에도 불구하고 호스트에 대한 액세스를 관리하는 공격자는 클러스터 내에서 측면 이동을 허용할 수 있는 쿠버네티스 API에서 환경에 대한 민감한 정보를 수집할 수 있습니다.

+

파드 보안 솔루션

+

파드 시큐리티 폴리시(PSP)

+

과거에는 파드 시큐리티 폴리시(PSP) 리소스를 사용하여 파드가 충족해야 하는 일련의 요구 사항을 지정했습니다. Kubernetes 버전 1.21부터 PSP는 더 이상 사용되지 않습니다. Kubernetes 버전 1.25에서 제거될 예정입니다.

+
+

Attention

+
+

Kubernetes 버전 1.21부터 PSP는 더 이상 사용되지 않습니다. P2P가 더이상 지원되지 않을 버전 1.25까지 대안 솔루션으로 전환하는데 대략 2년의 시간이 남았습니다. 이 문서는 이런 지원 중단의 동기에 대하여 설명합니다.

+

새로운 파드 보안 솔루션으로 마이그레이션

+

PSP는 제거될 예정이며 더 이상 활성 개발 중이 아니므로 클러스터 관리자와 운영자는 이런 보안 제어를 교체해야 합니다. 두 가지 솔루션으로 이런 요구를 충족할 수 있습니다.

+ +

PAC 및 PSS 솔루션은 모두 PSP와 공존할 수 있습니다. PSP가 제거되기 전에 클러스터에서 사용할 수 있습니다. 따라서 PSP에서 마이그레이션할 때 쉽게 채택할 수 있습니다. PSP에서 PSS로 마이그레이션을 고려하는 경우 이 문서를 참조하세요.

+

아래에 설명된 PAC 솔루션 중 하나인 Kyverno는 PSP에서 해당 솔루션으로 마이그레이션할 때 유사한 정책, 기능 비교 및 마이그레이션 절차를 포함하여 해당 블로그에 구체적인 지침을 제공합니다. 파드 시큐리티 어드미션 (PSA)과 관련된 Kyverno로의 마이그레이션에 대한 추가 정보 및 지침은 AWS 블로그에 게시되었습니다.

+

코드로서의 정책(PAC)

+

PAC(Policy-as-code) 솔루션은 규정된 자동 제어를 통해 클러스터 사용자를 안내하고 원치 않는 동작을 방지하는 가드레일을 제공합니다. PAC는 쿠버네티스 동적 어드미션 컨트롤러를 사용하여 웹훅 호출을 통해 쿠버네티스 API 서버 요청 흐름을 가로채고 변형 및 검증합니다. 코드로 작성되고 저장된 정책을 기반으로 페이로드를 요청합니다. 변형 및 유효성 검사는 API 서버 요청으로 인해 클러스터가 변경되기 전에 발생합니다. PAC 솔루션은 정책을 사용하여 분류 및 값을 기반으로 API 서버 요청 페이로드를 일치시키고 작동합니다.

+

Kubernetes에 사용할 수 있는 몇 가지 오픈 소스 PAC 솔루션이 있습니다. 이런 솔루션은 Kubernetes 프로젝트의 일부가 아닙니다. Kubernetes 생태계에서 제공됩니다. 일부 PAC 솔루션은 다음과 같습니다.

+ +

PAC 솔루션에 대한 자세한 내용과 필요에 맞는 적절한 솔루션을 선택하는 데 도움이 되는 방법은 아래 링크를 참조하십시오.

+ +

파드 시큐리티 스탠다드(PSS) 및 파드 시큐리티 어드미션(PSA)

+

PSP 지원 중단 및 즉시 사용 가능한 파드 보안을 제어해야 하는 지속적인 필요성에 대응하여 빌트인 쿠버네티스 솔루션으로 쿠버네티스 Auth Special Interest Group파드 시큐리티 스탠다드(PSS)파드 시큐리티 어드미션(PSA)을 만들었습니다. PSA 에는 PSS에 정의된 제어를 구현하는 어드미션 컨트롤러 웹훅 프로젝트가 포함됩니다. 이 허용 컨트롤러 접근 방식은 PAC 솔루션에서 사용되는 방식과 유사합니다.

+

쿠버네티스 문서에 따르면 PSS는 "보안 스펙트럼을 광범위하게 포괄하는 세 가지 다른 정책을 정의합니다. 이런 정책은 누적되며 매우 허용적인 것부터 매우 제한적인 것까지 다양합니다."

+

이런 정책은 다음과 같이 정의됩니다:

+
    +
  • +

    Privileged: 제한되지 않은(안전하지 않은) 정책으로 가능한 가장 광범위한 수준의 권한을 제공합니다. 이 정책은 알려진 권한 에스컬레이션을 허용합니다. 정책이 없다는 것입니다. 이는 로깅 에이전트, CNI, 스토리지 드라이버 및 권한 액세스가 필요한 기타 시스템 전체 애플리케이션과 같은 애플리케이션에 적합합니다.

    +
  • +
  • +

    Baseline: 알려진 권한 에스컬레이션을 방지하는 최소 제한 정책입니다. 기본(최소 지정) 파드 구성을 허용합니다. 베이스라인 정책은 hostNetwork, hostPID, hostIPC, hostPath, hostPort의 사용, Linux 기능을 추가 제한 등과 같은 기타 몇 가지 제한 사항을 포함합니다.

    +
  • +
  • +

    Restricted: 현재 파드 강화 모범 사례에 따라 엄격하게 제한된 정책입니다. 이 정책은 기준선에서 상속되며 루트 또는 루트 그룹으로 실행할 수 없는 것과 같은 추가 제한 사항을 추가합니다. 제한된 정책은 애플리케이션의 기능에 영향을 미칠 수 있습니다. 이들은 주로 보안에 중요한 응용 프로그램을 실행하는 것을 목표로 합니다.

    +
  • +
+

이런 정책은 파드 실행을 위한 프로파일을 정의하며, 세 가지 수준의 특권(Priviledged) 액세스에서부터 제한된(Restricted) 액세스로 정렬됩니다.

+

PSS에서 정의한 컨트롤을 구현하기 위해 PSA는 세 가지 모드로 작동합니다.

+
    +
  • +

    enforce: 정책을 위반하면 파드가 거부됩니다.

    +
  • +
  • +

    audit: 정책 위반은 감사 로그에 기록된 이벤트에 대한 감사 어노테이션 추가를 트리거하지만 그렇지 않으면 허용됩니다.

    +
  • +
  • +

    warn: 정책을 위반하면 사용자에게 경고가 표시되지만 그렇지 않으면 허용됩니다.

    +
  • +
+

이런 모드와 프로파일 (제한) 수준은 아래 예와 같이 레이블을 사용하여 쿠버네티스 네임스페이스 수준에서 구성됩니다.

+
apiVersion: v1
+kind: Namespace
+metadata:
+  name: policy-test
+  labels:
+    pod-security.kubernetes.io/enforce: restricted
+
+

독립적으로 사용하는 경우 이런 작동 모드는 다른 사용자 경험을 제공하는 다른 응답을 갖습니다. enforce 모드는 각 podSpec이 구성된 제한 수준을 위반하는 경우 파드가 생성되지 않도록 합니다. 그러나 이 모드에서는 PodSpec이 적용된 PSS를 위반하더라도 배포와 같이 파드를 생성하는 파드가 아닌 쿠버네티스 개체가 클러스터에 적용되는 것을 방지하지 않습니다. 이 경우 배포가 적용되지만 파드는 적용되지 않습니다.

+

성공적으로 적용된 디플로이머트 객체는 객체 내 파드 생성 실패에 속한다는 즉각적인 표시가 없기 때문에 이를 인지하지 쉽지 않습니다. 위반 podSpec은 파드를 생성하지 않습니다. kubectl get deploy <DEPLOYMENT_NAME>-oyaml로 디플로이먼트 리소스를 검사하면 아래와 같이 실패한 파드 .status.conditions 엘리먼트의 메시지가 표시된다.

+
...
+status:
+  conditions:
+    - lastTransitionTime: "2022-01-20T01:02:08Z"
+      lastUpdateTime: "2022-01-20T01:02:08Z"
+      message: 'pods "test-688f68dc87-tw587" is forbidden: violates PodSecurity "restricted:latest":
+        allowPrivilegeEscalation != false (container "test" must set securityContext.allowPrivilegeEscalation=false),
+        unrestricted capabilities (container "test" must set securityContext.capabilities.drop=["ALL"]),
+        runAsNonRoot != true (pod or container "test" must set securityContext.runAsNonRoot=true),
+        seccompProfile (pod or container "test" must set securityContext.seccompProfile.type
+        to "RuntimeDefault" or "Localhost")'
+      reason: FailedCreate
+      status: "True"
+      type: ReplicaFailure
+...
+
+

auditwarn 모드에서 파드 제한은 위반 파드가 생성되고 시작되는 것을 막지 않습니다 . 그러나 이런 모드에서는 API 서버 감사 로그 이벤트에 대한 감사 주석 및 API 서버 클라이언트에 대한 경고(예: kubectl )는 파드와 파드를 생성하는 개체에 위반이 있는 podSpec이 포함되어 있을 때 각각 트리거됩니다. kubectl 경고 메시지는 아래와 같습니다.

+
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "test" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "test" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "test" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "test" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
+deployment.apps/test created
+
+

PSA auditwarn 모드는 클러스터 작업에 부정적인 영향을 주지 않고 PSS를 도입할 때 유용합니다.

+

PSA 작동 모드는 상호 배타적이지 않으며 누적 방식으로 사용할 수 있습니다. 아래에서 볼 수 있듯이 단일 네임스페이스에서 여러 모드를 구성할 수 있습니다.

+
apiVersion: v1
+kind: Namespace
+metadata:
+  name: policy-test
+  labels:
+    pod-security.kubernetes.io/audit: restricted
+    pod-security.kubernetes.io/enforce: restricted
+    pod-security.kubernetes.io/warn: restricted
+
+

위의 예에서 배포를 적용할 때 사용자에게 친숙한 경고 및 감사 주석이 제공되는 반면 위반 적용도 파드 수준에서 제공됩니다. 실제로 여러 PSA 레이블은 아래와 같이 서로 다른 프로파일 수준을 사용할 수 있습니다.

+
apiVersion: v1
+kind: Namespace
+metadata:
+  name: policy-test
+  labels:
+    pod-security.kubernetes.io/enforce: baseline
+    pod-security.kubernetes.io/warn: restricted
+
+

위의 예에서 PSA는 baseline 프로파일 수준을 충족하는 모든 파드의 생성을 허용한 다음 restricted 프로파일 수준 을 위반하는 파드(및 파드를 생성하는 개체)는 경고 하도록 구성됩니다. 이는 baseline 에서 restricted 프로파일로 변경할 때 가능한 영향을 확인하는 데 유용한 접근 방식입니다.

+

기존 파드

+

기존 파드가 있는 네임스페이스가 더 제한적인 PSS 프로파일을 사용하도록 수정되면 auditwarn 모드가 적절한 메시지를 생성합니다. 그러나 enforce 모드는 파드를 삭제하지 않습니다. 경고 메시지는 아래와 같습니다.

+
Warning: existing pods in namespace "policy-test" violate the new PodSecurity enforce level "restricted:latest"
+Warning: test-688f68dc87-htm8x: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile
+namespace/policy-test configured
+
+

예외 처리 (Exemptions)

+

PSA는 Exemptions 를 사용하여 달리 적용되었을 파드에 대한 위반 집행을 제외합니다. 이런 면제는 아래에 나열되어 있습니다.

+
    +
  • +

    Usernames: 예외 처리된 인증(또는 사칭) 사용자 이름을 가진 사용자의 요청은 무시됩니다.

    +
  • +
  • +

    RuntimeClassNames: 예외 처리된 런타임 클래스 이름을 지정하는 파드 및 워크로드 리소스는 무시됩니다.

    +
  • +
  • +

    네임스페이스: 예외 처리된 네임스페이스의 파드 및 워크로드 리소스는 무시됩니다.

    +
  • +
+

이런 예외 처리는 PSA 어드미션 컨트롤러 구성에서 다음과 같이 API 서버 구성의 일부로 정적으로 적용됩니다.

+

Validating Webhook 구현에서 예외는 pod-security-webhook 컨테이너 내 볼륨으로 마운트되는 쿠버네티스 컨피그맵 리소스 내에서 구성할 수 있습니다.

+
apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: pod-security-webhook
+  namespace: pod-security-webhook
+data:
+  podsecurityconfiguration.yaml: |
+    apiVersion: pod-security.admission.config.k8s.io/v1
+    kind: PodSecurityConfiguration
+    defaults:
+      enforce: "restricted"
+      enforce-version: "latest"
+      audit: "restricted"
+      audit-version: "latest"
+      warn: "restricted"
+      warn-version: "latest"
+    exemptions:
+      # Array of authenticated usernames to exempt.
+      usernames: []
+      # Array of runtime class names to exempt.
+      runtimeClasses: []
+      # Array of namespaces to exempt.
+      namespaces: ["kube-system","policy-test1"]
+
+

위의 컨피그맵 YAML에서 볼 수 있듯이 audit , enforcewarn 와 같은 모든 PSA 모드에 대한 클러스터 전체의 기본 PSS 수준은 restricted 로 설정되었습니다. 이는 예외인 namespaces: ["kube-system","policy-test1"] 을 제외한 모든 네임스페이스에 영향을 미칩니다 . 또한 아래에 표시된 ValidatingWebhookConfiguration 리소스에서 pod-security-webhook 네임스페이스도 구성된 PSS에서 제외됩니다.

+
...
+webhooks:
+  # Audit annotations will be prefixed with this name
+  - name: "pod-security-webhook.kubernetes.io"
+    # Fail-closed admission webhooks can present operational challenges.
+    # You may want to consider using a failure policy of Ignore, but should 
+    # consider the security tradeoffs.
+    failurePolicy: Fail
+    namespaceSelector:
+      # Exempt the webhook itself to avoid a circular dependency.
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["pod-security-webhook"]
+...
+
+
+

Attention

+
+

파드 시큐리티 어드미션은 쿠버네티스 v1.25에서 안정 버전으로 전환되었습니다. 파드 시큐리티 어드미션 기능이 기본적으로 활성화되기 전에 사용하려면 동적 어드미션 컨트롤러 (뮤테이팅 웹훅) 를 설치해야 했습니다. 웹훅 설치 및 구성 지침은 본 문서에서 확인할 수 있습니다.

+

PAC(Policy-as-Code)과 파드 시큐리티 스탠다드 중에서(PSS) 선택

+

파드 시큐리티 스탠다드(PSS)는 쿠버네티스에 내장되어 있고 쿠버네티스 에코시스템의 솔루션이 필요하지 않은 솔루션을 제공함으로써 파드 시큐리티 폴리시(PSP)을 대체하기 위해 개발되었습니다. PAC(Policy-as-Code) 솔루션은 훨씬 더 유연합니다.

+

다음 장단점 목록은 파드 보안 솔루션에 대해 정보에 입각한 결정을 내리는 데 도움이 되도록 설계되었습니다.

+

PAC (파드 시큐리티 스탠다드과 비교)

+

장점:

+
    +
  • 보다 유연하고 세분화됨(필요한 경우 리소스 속성까지)
  • +
  • 파드에만 집중하는 것이 아니라 다양한 리소스 및 액션에 사용할 수 있음
  • +
  • 네임스페이스 수준에서만 적용되지 않음
  • +
  • 파드 시큐리티 스탠다드보다 더 성숙함
  • +
  • 의사결정은 기존 클러스터 리소스 및 외부 데이터(솔루션에 따라 다름)뿐만 아니라 API 서버 요청 페이로드의 모든 항목을 기반으로 할 수 있습니다.
  • +
  • 유효성 검사 전에 API 서버 요청 변경 지원(솔루션에 따라 다름)
  • +
  • 보완 정책 및 쿠버네티스 리소스 생성 가능 (솔루션에 따라 다름 - Kyverno는 파드 정책에서 디플로이먼트와 같은 상위 레벨 컨트롤러에 대한 정책을 자동 생성할 수 있다. 또한 Kyverno는 Generate Rules 를 사용하여 "새 리소스가 생성되거나 소스가 업데이트될 때" 쿠버네티스 리소스를 추가로 생성할 수 있습니다.)
  • +
  • 쿠버네티스 API 서버를 호출하기 전에 CICD 파이프라인상에서 보다 빠르게 보안을 도입할 수 있습니다(솔루션에 따라 다름).
  • +
  • 모범 사례, 조직 표준 등과 같이 반드시 보안과 관련되지 않은 동작을 구현하는 데 사용할 수 있습니다.
  • +
  • 쿠버네티스 이외의 사용 사례에서 사용 가능(솔루션에 따라 다름)
  • +
  • 유연성으로 인해 사용자 경험을 사용자의 요구에 맞게 조정할 수 있습니다.
  • +
+

단점:

+
    +
  • Kubernetes에 빌트인으로 제공되지 않음
  • +
  • 학습, 구성 및 지원이 더 복잡함
  • +
  • 정책 작성에는 새로운 기술/언어/기능이 필요할 수 있습니다.
  • +
+

파드 시큐리티 어드미션(PAC과 비교)

+

장점:

+
    +
  • 쿠버네티스에서 빌트인 제공
  • +
  • 더 간단한 구성
  • +
  • 사용할 새로운 언어나 작성할 정책이 없습니다.
  • +
  • 클러스터 기본 승인 수준이 privileged 로 구성된 경우 네임스페이스 레이블을 사용하여 파드 보안 프로파일에 네임스페이스를 옵트인할 수 있습니다.
  • +
+

단점:

+
    +
  • Policy-as-Code만큼 유연하거나 세분화되지 않음
  • +
  • 3개 단계의 제한 방식 제공
  • +
  • 주로 파드에 집중
  • +
+

요약

+

현재 PSP 이외의 파드 보안 솔루션이 없고 필요한 파드 보안 태세가 파드 시큐리티 스탠다드(PSS) 에 정의된 모델에 맞는다면 PAC 솔루션 대신 PSS를 채택하는 것이 더 쉬울 수 있습니다. 그러나 파드 보안 태세가 PSS 모델에 맞지 않거나 PSS에서 정의한 것 외에 추가 제어를 추가할 계획이라면 PAC 솔루션이 더 적합해 보입니다.

+

권장 사항

+

더 나은 사용자 경험을 위해 여러 파드 시큐리티 어드미션(PSA) 모드 사용

+

앞서 언급했듯이 PSA enforce 모드는 PSS 위반이 있는 파드가 적용되는 것을 방지하지만 디플로이먼트와 같은 상위 수준 컨트롤러를 중지하지는 않습니다. 실제로 파드 적용에 실패했다는 표시 없이 디플로이먼트가 성공적으로 적용됩니다. kubectl 을 사용 하여 디플로이먼트 객체를 검사하고 PSA에서 실패한 파드 메시지를 검색할 수 있지만 사용자 경험이 더 좋을 수 있습니다. 사용자 경험을 개선하려면 여러 PSA 모드(audit, enforce, warn)를 사용해야 합니다.

+
apiVersion: v1
+kind: Namespace
+metadata:
+  name: policy-test
+  labels:
+    pod-security.kubernetes.io/audit: restricted
+    pod-security.kubernetes.io/enforce: restricted
+    pod-security.kubernetes.io/warn: restricted
+
+

위의 예에서 enforce 모드가 정의된 경우 각 podSpec에서 PSS 위반이 있는 배포 매니페스트를 쿠버네티스 API 서버에 적용하려고 하면 배포가 성공적으로 적용되지만 파드는 적용되지 않습니다. 그리고 auditwarn 모드도 활성화되어 있으므로 API 서버 클라이언트는 경고 메시지를 수신하고 API 서버 감사 로그 이벤트에도 메시지가 표시됩니다.

+

Previleged 권한으로 실행할 수 있는 컨테이너 제한

+

언급한 바와 같이 Previleged 권한으로 실행되는 컨테이너는 호스트의 루트에 할당된 모든 Linux 기능을 상속합니다. 컨테이너가 제대로 작동하기 위해 이런 유형의 권한이 필요한 경우는 거의 없습니다. 컨테이너의 권한과 기능을 제한하는 데 사용할 수 있는 여러 가지 방법이 있습니다.

+
+

Attention

+
+

Fargate는 파드의 컨테이너가 AWS가 관리하는 인프라에서 실행되는 "서버리스"형태로 컨테이너를 실행할 수 있는 기능을 제공합니다. Fargate를 사용하면 Previleged 컨테이너를 실행하거나 hostNetwork 또는 hostPort를 사용하도록 파드를 구성할 수 없습니다.

+

컨테이너에서 루트로 프로세스를 실행하지 마십시오

+

모든 컨테이너는 기본적으로 루트로 실행됩니다. 이는 공격자가 애플리케이션의 취약성을 악용하고 실행 중인 컨테이너에 대한 셸 액세스 권한을 얻을 수 있는 경우 문제가 될 수 있습니다. 다양한 방법으로 이 위험을 완화할 수 있습니다. 먼저 컨테이너 이미지에서 셸(Shell)을 제거합니다. 둘째, Dockerfile에 USER 지시문을 추가하거나 루트가 아닌 사용자로 파드의 컨테이너를 실행합니다. Kubernetes podSpec에는 spec.securityContext 아래에 애플리케이션을 실행할 사용자 및/또는 그룹을 지정할 수 있는 일련의 필드가 포함되어 있습니다. 이런 필드는 각각 runAsUserrunAsGroup 입니다.

+

spec.securityContext 및 관련 요소 의 사용을 강제하기 위해 PAC 정책 또는 파드 시큐리티 스탠다드(PSS)를 클러스터에 추가할 수 있습니다. 이런 솔루션을 사용하면 etcd에 유지되기 전에 인바운드 Kubernetes API 서버 요청 페이로드를 검증할 수 있는 정책 또는 프로파일을 작성 및/또는 사용할 수 있습니다. 또한 PAC 솔루션은 인바운드 요청을 변경하고 경우에 따라 새 요청을 생성할 수 있습니다.

+

Docker에서 Docker를 실행하거나 컨테이너에 소켓을 마운트하지 마십시오

+

이렇게 하면 편리하게 Docker 컨테이너에서 이미지를 빌드/실행할 수 있지만 기본적으로 컨테이너에서 실행 중인 프로세스에 대한 노드의 완전한 제어를 양도하는 것입니다. 쿠버네티스에서 컨테이너 이미지를 빌드해야 하는 경우 Kaniko, buildah, img 또는 CodeBuild 같은 빌드 서비스 를 대신 사용할 수 있습니다.

+
+

Tip

+
+

컨테이너 이미지 빌드와 같은 CICD 처리에 사용되는 쿠버네티스 클러스터는 보다 일반화된 워크로드를 실행하는 클러스터와 격리되어야 합니다.

+

hostPath 사용을 제한하거나 hostPath가 필요한 경우 사용할 수 있는 접두사를 제한하고 볼륨을 읽기 전용으로 구성합니다

+

hostPath 는 호스트에서 컨테이너로 직접 디렉토리를 마운트하는 볼륨입니다. 파드에 이런 유형의 액세스가 필요한 경우는 거의 없지만 필요한 경우 위험을 인식해야 합니다. 기본적으로 루트로 실행되는 파드는 hostPath에 의해 노출된 파일 시스템에 대한 쓰기 액세스 권한을 갖습니다. 이를 통해 공격자는 kubelet 설정을 수정하고, /etc/shadow와 같이 hostPath에 의해 직접 노출되지 않는 디렉토리 또는 파일에 대한 심볼릭 링크를 생성하고, ssh 키를 설치하고, 호스트에 마운트된 비밀을 읽고, 기타 악의적인 것들을 할 수 있습니다. hostPath의 위험을 완화하려면 spec.containers.volumeMountsreadOnly 로 구성하십시오. 예를 들면 다음과 같습니다.

+
volumeMounts:
+- name: hostPath-volume
+    readOnly: true
+    mountPath: /host-path
+
+

또한 PAC 솔루션을 사용하여 hostPath 볼륨에서 사용할 수 있는 디렉토리를 제한하거나 hostPath 사용을 모두 방지해야 합니다. 파드 시큐리티 스탠다드 Baseline 또는 Restricted 정책을 사용하여 hostPath 사용을 방지할 수 있습니다.

+

권한 상승의 위험성에 대한 자세한 내용은 Seth Art의 블로그 Bad Pods: Kubernetes Pod Privilege Escalation를 참조하십시오.

+

리소스 경합 및 DoS 공격을 피하기 위해 각 컨테이너에 대한 요청 및 제한 설정

+

요청이나 제한이 없는 파드는 이론적으로 호스트에서 사용 가능한 모든 리소스를 소비할 수 있습니다. 추가 파드가 노드에 예약되면 노드에서 CPU 또는 메모리 압력이 발생하여 Kubelet이 종료되거나 노드에서 파드가 제거될 수 있습니다. 이런 일이 함께 발생하는 것을 방지할 수는 없지만 요청 및 제한을 설정하면 리소스 경합을 최소화하고 리소스를 과도하게 소비하는 잘못 작성된 애플리케이션으로 인한 위험을 완화하는 데 도움이 됩니다.

+

podSpec 을 사용하면 CPU 및 메모리에 대한 요청 및 제한을 지정할 수 있습니다. CPU는 초과 신청될 수 있기 때문에 압축 가능한 리소스로 간주됩니다. 메모리는 압축할 수 없습니다. 즉, 여러 컨테이너 간에 공유할 수 없습니다.

+

CPU 또는 메모리에 대해 requests 를 지정할 때 본질적으로 컨테이너가 확보할 수 있는 memory 의 양을 지정하는 것입니다. Kubernetes는 파드에 있는 모든 컨테이너의 요청을 집계하여 파드를 예약할 노드를 결정합니다. 컨테이너가 요청된 메모리 양을 초과하면 노드에 메모리 압력이 있으면 종료될 수 있습니다.

+

Limits 는 컨테이너가 사용할 수 있는 CPU 및 메모리 리소스의 최대량이며 컨테이너에 대해 생성된 cgroup 의 memory.limit_in_bytes 값에 직접 해당합니다. 메모리 제한을 초과하는 컨테이너는 OOM 종료됩니다. 컨테이너가 CPU 제한을 초과하면 제한됩니다.

+
+

Tip

+
+

컨테이너 resources.limits를 사용하는 경우 컨테이너 리소스 사용량(리소스 풋프린트라고도 함)은 부하 테스트를 기반으로 데이터 기반이고 정확해야 합니다. 정확하고 신뢰할 수 있는 리소스 공간이 없으면 컨테이너 'resources.limits'를 채울 수 있습니다. 예를 들어 'resources.limits.memory'는 잠재적인 메모리 리소스 제한 부정확성을 설명하기 위해 관찰 가능한 최대값보다 20-30% 높게 패딩될 수 있습니다.

+

Kubernetes는 세 가지 서비스 품질(QoS) 클래스를 사용하여 노드에서 실행되는 워크로드의 우선 순위를 지정합니다. 여기에는 다음이 포함됩니다.

+
    +
  • guaranteed
  • +
  • burstable
  • +
  • best-effort
  • +
+

제한 및 요청이 설정되지 않은 경우 파드는 best-effort (가장 낮은 우선 순위)로 구성됩니다. Best-effort 파드는 메모리가 부족할 때 가장 먼저 종료됩니다. 파드 내의 all 컨테이너 에 제한이 설정 되거나 요청 및 제한이 동일한 값으로 설정되고 0이 아닌 경우 파드는 guaranteed (가장 높은 우선 순위)로 구성됩니다. 보장된 파드는 구성된 메모리 제한을 초과하지 않는 한 종료되지 않습니다. 제한 및 요청이 0이 아닌 다른 값으로 구성되거나 파드 내의 한 컨테이너가 제한을 설정하고 다른 컨테이너는 다른 리소스에 대해 제한이 설정되지 않거나 설정된 경우 파드는 burstable (중간 우선 순위)로 구성됩니다. 이런 파드에는 일부 리소스 보장이 있지만 요청된 메모리를 초과하면 종료될 수 있습니다.

+
+

Attention

+
+

요청은 컨테이너 cgroup의 memory_limit_in_bytes 값에 영향을 미치지 않습니다. cgroup 제한은 호스트에서 사용 가능한 메모리 양으로 설정됩니다. 그럼에도 불구하고 요청 값을 너무 낮게 설정하면 노드가 메모리 압력을 받는 경우 파드가 kubelet에 의해 종료 대상이 될 수 있습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
클래스우선순위조건죽이기 조건
Guaranteed최고제한 = 요청 != 0메모리 제한만 초과
Burstable중간제한 != 요청 != 0요청 메모리를 초과하면 종료됨
Best-Effort최저한도 및 요청이 설정되지 않음메모리가 부족할 때 가장 먼저 강제 종료됨
+

리소스 QoS에 대한 추가 정보는 쿠버네티스 문서를 참조하십시오.

+

네임스페이스에 리소스 할당량을 설정하거나 제한 범위를 생성하여 리소스 요청(request) 및 리소스 제한(limit)을 강제로 사용할 수 있습니다. 리소스 할당량을 사용하면 네임스페이스에 할당된 총 리소스 양 (예: CPU 및 RAM)을 지정할 수 있습니다. 네임스페이스에 적용하면 해당 네임스페이스에 배포된 모든 컨테이너에 대한 리소스 요청 및 리소스 제한을 지정해야 합니다. 반대로 제한 범위를 사용하면 리소스 할당을 더 세밀하게 제어할 수 있습니다. 제한 범위를 사용하면 네임스페이스 내 파드 또는 컨테이너당 CPU 및 메모리 리소스를 최소/최대로 설정할 수 있습니다. 기본 리소스 요청(request)/리소스 제한(limit) 값이 제공되지 않은 경우 이를 사용하여 기본 요청/제한 값을 설정할 수도 있습니다.

+

코드형 정책(PAC) 솔루션을 사용하여 요청 및 제한을 적용하거나 네임스페이스를 생성할 때 리소스 할당량 및 제한 범위를 만들 수도 있습니다.

+

특권 에스컬레이션을 허용하지 않음

+

특권 에스컬레이션을 통해 프로세스는 실행 중인 보안 컨텍스트를 변경할 수 있습니다. Sudo는 SUID 또는 SGID 비트가 있는 바이너리와 마찬가지로 이에 대한 좋은 예입니다. 특권 에스컬레이션은 기본적으로 사용자가 다른 사용자 또는 그룹의 권한으로 파일을 실행하는 방법입니다. allowPrivilegeEscalationfalse로 설정하는 정책 코드 변경 정책을 구현하거나 podSpec에서 securityContext.allowPrivilegeEscalation을 설정하여 컨테이너가 권한 있는 에스컬레이션을 사용하지 못하도록 방지 할 수 있습니다 . Policy-as-code 정책을 사용하여 잘못된 설정이 감지된 경우 API 서버 요청이 성공하지 못하도록 할 수도 있습니다. 파드 시큐리티 스탠다드을 사용하여 파드가 권한 에스컬레이션을 사용하지 못하도록 할 수도 있습니다.

+

ServiceAccount 토큰 탑재 비활성화

+

쿠버네티스 API에 액세스할 필요가 없는 파드의 경우, 파드 스펙 또는 특정 서비스어카운트를 사용하는 모든 파드에 대해 서비스어카운트 토큰의 자동 마운트를 비활성화할 수 있다.

+
+

Attention

+
+

서비스어카운트 마운트를 비활성화해도 파드가 쿠버네티스 API에 네트워크로 액세스하는 것을 막을 수는 없습니다. 파드가 쿠버네티스 API에 네트워크로 액세스하는 것을 방지하려면 EKS 클러스터 엔드포인트 액세스를 수정하고 네트워크정책를 사용하여 파드 액세스를 차단합니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-no-automount
+spec:
+  automountServiceAccountToken: false
+
+
apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: sa-no-automount
+automountServiceAccountToken: false
+
+

서비스 검색 비활성화

+

클러스터 내 서비스를 조회하거나 호출할 필요가 없는 파드의 경우 파드에 제공되는 정보의 양을 줄일 수 있다. CoreDNS를 사용하지 않도록 Pod의 DNS 정책을 설정하고 파드 네임스페이스의 서비스를 환경 변수로 노출하지 않도록 설정할 수 있습니다. 서비스 링크에 대한 자세한 내용은 환경 변수에 관한 쿠버네티스 문서를 참조하십시오. 파드 DNS 정책의 기본값은 클러스터 내 DNS를 사용하는 "ClusterFirst"이고, 기본값이 아닌 "Default"는 기본 노드의 DNS 확인을 사용합니다. 자세한 내용은 파드 DNS 정책에 관한 쿠버네티스 문서 를 참조하십시오.

+
+

Attention

+
+

서비스 링크를 비활성화하고 파드의 DNS 정책을 변경해도 파드가 클러스터 내 DNS 서비스에 네트워크로 액세스하는 것을 막을 수는 없습니다. + 공격자는 여전히 클러스터 내 DNS 서비스에 접속하여 클러스터의 서비스를 열거할 수 있습니다.(예: dig SRV *.*.svc.cluster.local @$CLUSTER_DNS_IP) 클러스터 내 서비스 검색을 방지하려면 NetworkPolicy 를 사용하여 파드 액세스를 차단합니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-no-service-info
+spec:
+    dnsPolicy: Default # "Default" is not the true default value
+    enableServiceLinks: false
+
+

읽기 전용 루트 파일 시스템으로 이미지 구성

+

읽기 전용 루트 파일 시스템으로 이미지를 구성하면 공격자가 애플리케이션에서 사용하는 파일 시스템의 바이너리를 덮어쓰는 것을 방지할 수 있습니다. 애플리케이션이 파일 시스템에 기록해야 하는 경우 임시 디렉터리에 쓰거나 볼륨을 연결하고 마운트하는 것을 고려해 보세요.다음과 같이 파드의 SecurityContext를 설정하여 이를 적용할 수 있다.

+
...
+securityContext:
+  readOnlyRootFilesystem: true
+...
+
+

코드형 정책(PaC) 및 파드 시큐리티 스탠다드를 사용하여 이 동작을 시행할 수 있습니다.

+
+

Info

+
+

쿠버네티스의 Windows 컨테이너 에 따르면 Windows에서 실행되는 컨테이너의 경우 SecurityContext.readOnlyRootFileSystemtrue로 설정할 수 없습니다. + 레지스트리 및 시스템 프로세스를 컨테이너 내에서 실행하려면 쓰기 권한이 필요하기 때문입니다.

+

도구 및 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/security/docs/runtime/index.html b/ko/security/docs/runtime/index.html new file mode 100644 index 000000000..2e0d12dc5 --- /dev/null +++ b/ko/security/docs/runtime/index.html @@ -0,0 +1,2358 @@ + + + + + + + + + + + + + + + + + + + + + + + 런타임 보안 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

런타임 보안

+

런타임 보안은 컨테이너가 실행되는 동안 컨테이너를 능동적으로 보호합니다. 컨테이너 내부에서 발생하는 악의적인 활동을 탐지 및 방지하는 것이 관건입니다. 이는 리눅스 기능, 보안 컴퓨팅 (seccomp), AppArmor 또는 SELinux와 같이 쿠버네티스와 통합된 리눅스 커널 또는 커널 익스텐션의 여러 메커니즘을 통해 달성할 수 있습니다. Amazon GuardDuty 및 타사 도구와 같은 옵션도 있습니다. 이러한 도구를 사용하면 Linux 커널 메커니즘을 수동으로 구성하지 않고도 기준을 설정하고 이상 활동을 탐지하는 데 도움을 줄 수 있습니다.

+
+

Attention

+
+

쿠버네티스는 현재 seccomp, AppArmor 또는 SELinux 프로파일을 노드에 로드하기 위한 네이티브 메커니즘을 제공하지 않는다. 수동으로 로드하거나 부트스트랩할 때 노드에 설치해야 합니다. 스케줄러가 어떤 노드에 프로파일이 있는지 인식하지 못하기 때문에 파드에서 참조하기 전에 이 작업을 수행해야 한다.Security Profiles Operator와 같은 도구를 사용하여 프로파일을 노드에 자동으로 프로비저닝하는 방법을 아래에서 확인하세요.

+

보안 컨텍스트 및 빌트인 쿠버네티스 컨트롤

+

많은 리눅스 런타임 보안 메커니즘은 쿠버네티스와 긴밀하게 통합되어 있으며 쿠버네티스 보안 컨텍스트를 통해 구성할 수 있습니다. 이러한 옵션 중 하나는 privileged 플래그인데, 이 플래그는 기본적으로 false이며 활성화되면 기본적으로 호스트의 루트와 동일합니다. 프로덕션 워크로드에서 권한 모드를 활성화하는 것은 거의 항상 부적절하지만, 컨테이너에 적절한 권한을 더 세부적으로 제공할 수 있는 컨트롤은 훨씬 더 많습니다.

+

리눅스 기능

+

Linux 기능을 사용하면 루트 사용자의 모든 기능을 제공하지 않고도 파드 또는 컨테이너에 특정 기능을 부여할 수 있습니다. 네트워크 인터페이스 또는 방화벽을 구성할 수 있는 CAP_NET_ADMIN이나 시스템 클럭을 조작할 수 있는 CAP_SYS_TIME이 그런 예입니다.

+

Seccomp

+

Seccomp를 사용하면 컨테이너식 애플리케이션이 기본 호스트 운영 체제의 커널에 특정 시스템 호출을 수행하는 것을 방지할 수 있습니다. Linux 운영 체제에는 수백 개의 시스템 호출이 있지만 그 중 대부분은 컨테이너를 실행하는 데 필요하지 않습니다. 컨테이너에서 생성할 수 있는 시스템 호출을 제한하면 애플리케이션의 공격 범위를 효과적으로 줄일 수 있습니다.

+

Seccomp는 시스템 호출을 가로채고 허용 목록에 있는 시스템만 통과하도록 하는 방식으로 작동합니다.Docker에는 대부분의 범용 워크로드에 적합한 디폴트 seccomp 프로파일이 있으며, containerd와 같은 다른 컨테이너 런타임도 비슷한 디폴트값을 제공합니다. 파드 사양의 SecurityContext 섹션에 다음을 추가하여 컨테이너 또는 파드가 컨테이너 런타임의 디폴트 seccomp 프로파일을 사용하도록 구성할 수 있다.

+
securityContext:
+  seccompProfile:
+    type: RuntimeDefault
+
+

1.22 (Alpha, 1.27부터 Stable)부터, 위의 '런타임디폴트'는 단일 kubelet 플래그, `--seccomp-default'를 사용하여 노드의 모든 파드에 사용할 수 있다. 그러면 'SecurityContext'에 지정된 프로파일은 다른 프로파일에만 필요합니다.

+

또한 추가 권한이 필요한 항목에 대해 프로파일을 직접 만들 수도 있습니다. 수동으로 작업하기에는 매우 지루할 수 있지만 eBPF 또는 logs와 같은 도구를 사용하여 기본 권한 요구 사항을 seccomp 프로파일로 기록하는 것을 지원하는 Inspektor Gadget(네트워크 정책을 생성을 위해 네트워크 보안 섹션 문서에서도 권장) 및 Security Profiles Operator와 같은 도구가 있습니다. 또한 Security Profiles Operator를 사용하면 기록된 프로파일을 파드와 컨테이너에서 사용할 수 있도록 노드에 배포하는 작업을 자동화할 수 있다.

+

AppArmor와 SELinux

+

AppMor와 SELinux는 필수 액세스 제어(MAC 시스템)로 알려져 있습니다. 이들은 seccomp와 개념적으로는 비슷하지만 API와 기능이 다르기 때문에 특정 파일 시스템 경로 또는 네트워크 포트 등에 대한 액세스 제어가 가능합니다. 이러한 도구에 대한 지원은 리눅스 배포판에 따라 달라지는데, 데비안/우분투는 AppArmor를 지원하고 RHEL/Centos/BottleRocket/Amazon Linux 2023은 SELinux를 지원합니다. SELinux에 대한 자세한 내용은 인프라 보안 섹션를 참조하십시오.

+

AppArmor와 SELinux는 모두 쿠버네티스와 통합되어 있지만, 쿠버네티스 1.28부터 AppArmor 프로파일은 어노테이션 을 통해 지정해야 하며, SELinux 레이블은 보안 컨텍스트의 SELinuxOptions 필드를 통해 직접 설정할 수 있습니다.

+

seccomp 프로파일과 마찬가지로 위에서 언급한 보안 프로파일 운영자는 클러스터의 노드에 프로파일을 배포하는 데 도움을 줄 수 있습니다. (향후 이 프로젝트는 seccomp와 마찬가지로 AppMor 및 SELinux용 프로파일도 생성하는 것을 목표로 하고 있습니다.)

+

권장 사항

+

Amazon GuardDuty를 사용하여 런타임 모니터링 및 EKS 환경에 대한 위협을 탐지하십시오

+

현재 EKS 런타임을 지속적으로 모니터링하고, EKS 감사 로그를 분석하고, 멀웨어 및 기타 의심스러운 활동을 검사할 수 있는 솔루션이 없는 경우, Amazon은 AWS 환경을 보호하는 간단하고, 빠르고, 안전하고, 확장 가능하고, 비용 효율적인 원클릭 방법을 원하는 고객에게 Amazon GuardDuty를 사용할 것을 강력히 권장합니다. Amazon GuardDuty는 AWS CloudTrail 관리 이벤트, AWS CloudTrail 이벤트 로그, VPC 플로우 로그(Amazon EC2 인스턴스 트래픽), 쿠버네티스 감사 로그 및 DNS 로그와 같은 기본 데이터 소스를 분석하고 처리하는 보안 모니터링 서비스입니다. 또한 EKS 런타임 모니터링도 포함됩니다. 지속적으로 업데이트되는 위협 인텔리전스 피드(예: 악성 IP 주소 및 도메인 목록), 기계 학습을 사용하여 AWS 환경 내에서 예상치 못한, 잠재적으로 승인되지 않은 악의적인 활동을 식별합니다. 여기에는 권한 상승, 노출된 자격 증명 사용, 악성 IP 주소, 도메인과의 통신, Amazon EC2 인스턴스 및 EKS 컨테이너 워크로드에 악성코드가 존재하거나 의심스러운 API 활동 발견과 같은 문제가 포함될 수 있습니다. GuardDuty는 GuardDuty 콘솔이나 Amazon EventBridge를 통해 확인할 수 있는 보안 조사 결과를 생성하여 AWS 환경의 상태를 알려줍니다. 또한 GuardDuty는 조사 결과를 Amazon 심플 스토리지 서비스 (S3) 버킷으로 내보내고 AWS Security Hub 및 Detective과 같은 다른 서비스와 통합할 수 있도록 지원합니다.

+

위와 관련된 AWS 온라인 테크 토크인 "Amazon GuardDuty를 통한 Amazon EKS의 향상된 위협 탐지 — AWS 온라인 테크 토크"를 시청하여 이러한 추가 EKS 보안 기능을 몇 분 만에 단계별로 활성화하는 방법을 알아보십시오.

+

런타임 방어를 위해 타사 솔루션 사용

+

Linux 보안에 익숙하지 않은 경우 seccomp 및 Apparmor 프로파일을 만들고 관리하기 어려울 수 있습니다. 능숙해질 시간이 없다면 상용 솔루션 사용을 고려하십시오. 그들 중 다수는 Apparmor 및 seccomp와 같은 정적 프로파일을 넘어 기계 학습을 사용하여 의심스러운 활동을 차단하거나 경고하기 시작했습니다. 이런 솔루션 중 일부는 아래의 도구 섹션에서 찾을 수 있습니다. 추가 옵션은 AWS Marketplace for Containers에서 찾을 수 있습니다.

+

seccomp 정책을 작성하기 전에 Linux 기능 추가/삭제 고려

+

기능에는 시스템 콜이 도달할 수 있는 커널 기능의 다양한 검사가 포함됩니다. 확인에 실패하면 시스템 콜은 일반적으로 오류를 반환합니다. 확인은 특정 syscall의 시작 부분에서 바로 수행하거나 여러 다른 syscall을 통해 도달할 수 있는 커널의 더 깊은 영역(예: 특정 특권 파일에 쓰기)에서 수행할 수 있습니다. 반면에 Seccomp는 실행되기 전에 모든 시스템 콜에 적용되는 시스템 콜 필터입니다. 프로세스는 특정 시스템 콜 또는 특정 시스템 콜에 대한 특정 인수를 실행할 권한을 취소할 수 있는 필터를 설정할 수 있습니다.

+

seccomp를 사용하기 전에 Linux 기능 추가/제거가 필요한 제어 기능을 제공하는지 고려하십시오. 자세한 내용은 를 참조하십시오.

+

파드 시큐리티 폴리시(PSP)을 사용하여 목표를 달성할 수 있는지 확인하십시오

+

PSP는 과도한 복잡성을 유발하지 않으면서 보안 태세를 개선할 수 있는 다양한 방법을 제공합니다. seccomp 및 Apparmor 프로파일을 구축하기 전에 PSP에서 사용할 수 있는 옵션을 살펴보세요.

+
+

Warning

+

쿠버네티스 1.25부터 PSP가 제거되고 Pod Security Admission 컨트롤러로 대체되었습니다. 현재 존재하는 타사 대안으로는 OPA/게이트키퍼 및 Kyverno가 있습니다. PSP에서 흔히 볼 수 있는 정책을 구현하기 위한 게이트키퍼 제약 조건 및 제약 조건 템플릿 모음은 GitHub의 Gatekeeper 라이브러리 저장소에서 가져올 수 있습니다. 또한 Kyverno 정책 라이브러리에서 파드 시큐리티 스탠다드(PSS)의 전체 컬렉션을 포함하여 PSP를 대체할 수 있는 다양한 제품을 찾을 수 있습니다.

+
+

도구 및 리소스

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/upgrades/index.html b/ko/upgrades/index.html new file mode 100644 index 000000000..2205615f5 --- /dev/null +++ b/ko/upgrades/index.html @@ -0,0 +1,3207 @@ + + + + + + + + + + + + + + + + + + + + + + + 클러스터 업그레이드 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

클러스터 업그레이드 모범 사례

+

이 안내서는 클러스터 관리자에게 Amazon EKS 업그레이드 전략을 계획하고 실행하는 방법을 보여줍니다. 또한 자체 관리형 노드, 관리형 노드 그룹, Karpenter 노드 및 Fargate 노드를 업그레이드하는 방법도 설명합니다. EKS Anywhere, 자체 관리형 쿠버네티스, AWS Outposts 또는 AWS Local Zone에 대한 지침은 포함되지 않습니다.

+

개요

+

쿠버네티스 버전은 컨트롤 플레인과 데이터 플레인을 모두 포함합니다.원활한 작동을 위해 컨트롤 플레인과 데이터 플레인 모두 동일한 쿠버네티스 마이너 버전, 예: 1.24 를 실행해야 합니다. AWS가 컨트롤 플레인을 관리하고 업그레이드하는 동안 데이터 플레인에서 워커 노드를 업데이트하는 것은 사용자의 책임입니다.

+
    +
  • 컨트롤 플레인 — 컨트롤 플레인 버전은 쿠버네티스 API 서버에서 정의합니다.EKS 클러스터에서는 AWS에서 이를 관리합니다.컨트롤 플레인 버전으로의 업그레이드는 AWS API를 사용하여 시작됩니다.
  • +
  • 데이터 플레인 — 데이터 플레인 버전은 노드에서 실행되는 Kubelet 버전을 참조합니다.동일한 클러스터의 여러 노드라도 버전이 다를 수 있습니다. kubectl get nodes 명령어로 있는 모든 노드의 버전을 확인하세요.
  • +
+

업그레이드 전

+

Amazon EKS에서 쿠버네티스 버전을 업그레이드하려는 경우 업그레이드를 시작하기 전에 몇 가지 중요한 정책, 도구 및 절차를 마련해야 합니다.

+
    +
  • 지원 중단 정책 이해쿠버네티스 지원 중단 정책이 어떻게 작동하는지 자세히 알아보세요. 기존 애플리케이션에 영향을 미칠 수 있는 향후 변경 사항을 숙지하세요. 최신 버전의 Kubernetes는 특정 API 및 기능을 단계적으로 중단하는 경우가 많으며, 이로 인해 애플리케이션 실행에 문제가 발생할 수 있습니다.
  • +
  • 쿠버네티스 변경 로그 검토Amazon EKS Kubernetes 버전 과 함께 Kubernetes 변경 로그 를 철저히 검토하여 워크로드에 영향을 미칠 수 있는 주요 변경 사항 등 클러스터에 미칠 수 있는 영향을 파악하십시오.
  • +
  • 클러스터 추가 기능 호환성 평가 — Amazon EKS는 새 버전이 출시되거나 클러스터를 새 Kubernetes 마이너 버전으로 업데이트한 후에 추가 기능을 자동으로 업데이트하지 않습니다.업그레이드하려는 클러스터 버전과 기존 클러스터 애드온의 호환성을 이해하려면 애드온 업데이트 를 검토하십시오.
  • +
  • 컨트롤 플레인 로깅 활성화 — 업그레이드 프로세스 중에 발생할 수 있는 로그, 오류 또는 문제를 캡처하려면 컨트롤 플레인 로깅을 활성화합니다. 이러한 로그에 이상이 있는지 검토해 보십시오. 비프로덕션 환경에서 클러스터 업그레이드를 테스트하거나 자동화된 테스트를 지속적 통합 워크플로에 통합하여 애플리케이션, 컨트롤러 및 사용자 지정 통합과의 버전 호환성을 평가하세요.
  • +
  • 클러스터 관리를 위한 eksctl 살펴보기eksctl을 사용하여 EKS 클러스터를 관리하는 것을 고려해 보십시오. 기본적으로 컨트롤 플레인 업데이트, 애드온 관리, 워커 노드 업데이트 처리 기능을 제공합니다.
  • +
  • EKS에서 관리형 노드 그룹 또는 Fargate를 선택하세요EKS 관리형 노드 그룹 또는 EKS Fargate를 사용하여 워커 노드 업그레이드를 간소화하고 자동화합니다. 이러한 옵션을 사용하면 프로세스를 간소화하고 수동 개입을 줄일 수 있습니다.
  • +
  • kubectl Convert 플러그인 활용kubectl convert 플러그인을 활용하여 서로 다른 API 버전 간에 쿠버네티스 매니페스트 파일 변환을 용이하게 합니다. 이를 통해 구성이 새로운 쿠버네티스 버전과의 호환성을 유지할 수 있습니다.
  • +
+

클러스터를 최신 상태로 유지

+

Amazon EKS의 공동 책임 모델을 반영하면 안전하고 효율적인 EKS 환경을 위해서는 쿠버네티스 업데이트를 최신 상태로 유지하는 것이 무엇보다 중요합니다.이러한 전략을 운영 워크플로에 통합하면 최신 기능 및 개선 사항을 최대한 활용하는 안전한 최신 클러스터를 유지할 수 있는 입지를 다질 수 있습니다. 전략:

+
    +
  • 지원되는 버전 정책 — 쿠버네티스 커뮤니티에 따라 Amazon EKS는 일반적으로 세 가지 활성 쿠버네티스 버전을 제공하고 매년 네 번째 버전은 지원 중단합니다. 지원 중단 통지는 버전이 지원 종료일에 도달하기 최소 60일 전에 발행됩니다. 자세한 내용은 EKS 버전 FAQ를 참조하십시오.
  • +
  • 자동 업그레이드 정책 — EKS 클러스터에서 쿠버네티스 업데이트를 계속 동기화하는 것이 좋습니다. 버그 수정 및 보안 패치를 포함한 쿠버네티스 커뮤니티 지원은 일반적으로 1년 이상 된 버전의 경우 중단됩니다. 또한 지원 중단된 버전에는 취약성 보고가 부족하여 잠재적 위험이 발생할 수 있습니다. 버전의 수명이 끝나기 전에 사전 업그레이드를 하지 못하면 자동 업그레이드가 트리거되어 워크로드와 시스템이 중단될 수 있습니다. 자세한 내용은 EKS 버전 지원 정책을 참조하십시오.
  • +
  • 업그레이드 런북 생성 — 업그레이드 관리를 위한 문서화된 프로세스를 수립하십시오. 사전 예방적 접근 방식의 일환으로 업그레이드 프로세스에 맞는 런북 및 특수 도구를 개발하십시오. 이를 통해 대비 능력이 향상될 뿐만 아니라 복잡한 전환도 간소화됩니다. 적어도 1년에 한 번 클러스터를 업그레이드하는 것을 표준 관행으로 삼으세요. 이 방법을 통해 지속적인 기술 발전에 발맞추어 환경의 효율성과 보안을 강화할 수 있습니다.
  • +
+

EKS 출시 일정 검토

+

EKS 쿠버네티스 릴리스 캘린더 검토를 통해 새 버전이 출시되는 시기와 특정 버전에 대한 지원이 종료되는 시기를 알아보십시오. 일반적으로 EKS는 매년 세 개의 마이너 버전의 쿠버네티스를 릴리스하며 각 마이너 버전은 약 14개월 동안 지원됩니다.

+

또한 업스트림 쿠버네티스 릴리스 정보 도 검토하십시오.

+

공동 책임 모델이 클러스터 업그레이드에 어떻게 적용되는지 이해

+

클러스터 컨트롤 플레인과 데이터 플레인 모두에 대한 업그레이드를 시작하는 것은 사용자의 책임입니다. 업그레이드 시작 방법에 대해 알아보십시오. 클러스터 업그레이드를 시작하면 AWS가 클러스터 컨트롤 플레인 업그레이드를 관리합니다. Fargate 파드 및 [기타 애드온] 을 포함한 데이터 플레인 업그레이드는 사용자의 책임입니다.(#upgrade -애드온 및 구성 요소 - kubernetes-api 사용) 클러스터 업그레이드 후 가용성과 운영에 영향을 미치지 않도록 클러스터에서 실행되는 워크로드의 업그레이드를 검증하고 계획해야 합니다.

+

클러스터를 인플레이스 업그레이드

+

EKS는 인플레이스 클러스터 업그레이드 전략을 지원합니다.이렇게 하면 클러스터 리소스가 유지되고 클러스터 구성 (예: API 엔드포인트, OIDC, ENIS, 로드밸런서) 이 일관되게 유지됩니다.이렇게 하면 클러스터 사용자의 업무 중단이 줄어들고, 워크로드를 재배포하거나 외부 리소스 (예: DNS, 스토리지) 를 마이그레이션할 필요 없이 클러스터의 기존 워크로드와 리소스를 사용할 수 있습니다.

+

전체 클러스터 업그레이드를 수행할 때는 한 번에 하나의 마이너 버전 업그레이드만 실행할 수 있다는 점에 유의해야 합니다 (예: 1.24에서 1.25까지).

+

즉, 여러 버전을 업데이트해야 하는 경우 일련의 순차적 업그레이드가 필요합니다.순차적 업그레이드를 계획하는 것은 더 복잡하며 다운타임이 발생할 위험이 더 높습니다.이 상황에서는 블루/그린 클러스터 업그레이드 전략을 평가하십시오.

+

컨트롤 플레인과 데이터 플레인을 순서대로 업그레이드

+

클러스터를 업그레이드하려면 다음 조치를 취해야 합니다.

+
    +
  1. 쿠버네티스 및 EKS 릴리스 노트를 검토하십시오.
  2. +
  3. 클러스터를 백업하십시오.(선택 사항)
  4. +
  5. 워크로드에서 더 이상 사용되지 않거나 제거된 API 사용을 식별하고 수정하십시오.
  6. +
  7. 관리형 노드 그룹을 사용하는 경우 컨트롤 플레인과 동일한 Kubernetes 버전에 있는지 확인하십시오. EKS Fargate 프로파일에서 생성한 EKS 관리형 노드 그룹 및 노드는 컨트롤 플레인과 데이터 플레인 간의 마이너 버전 스큐를 1개만 지원합니다.
  8. +
  9. AWS 콘솔 또는 CLI를 사용하여 클러스터 컨트롤 플레인을 업그레이드하십시오.
  10. +
  11. 애드온 호환성을 검토하세요. 필요에 따라 쿠버네티스 애드온과 커스텀 컨트롤러를 업그레이드하십시오.
  12. +
  13. kubectl 업데이트하기.
  14. +
  15. 클러스터 데이터 플레인을 업그레이드합니다. 업그레이드된 클러스터와 동일한 쿠버네티스 마이너 버전으로 노드를 업그레이드하십시오.
  16. +
+

EKS 문서를 활용하여 업그레이드 체크리스트 생성

+

EKS 쿠버네티스 버전 문서에는 각 버전에 대한 자세한 변경 목록이 포함되어 있습니다.각 업그레이드에 대한 체크리스트를 작성하십시오.

+

특정 EKS 버전 업그레이드 지침은 문서에서 각 버전의 주요 변경 사항 및 고려 사항을 검토하십시오.

+ +

쿠버네티스 API를 사용하여 애드온 및 컴포넌트 업그레이드

+

클러스터를 업그레이드하기 전에 사용 중인 Kubernetes 구성 요소의 버전을 이해해야 합니다. 클러스터 구성 요소의 인벤토리를 작성하고 Kubernetes API를 직접 사용하는 구성 요소를 식별하십시오.여기에는 모니터링 및 로깅 에이전트, 클러스터 오토스케일러, 컨테이너 스토리지 드라이버 (예: EBS CSI, EFS CSI), 인그레스 컨트롤러, 쿠버네티스 API를 직접 사용하는 기타 워크로드 또는 애드온과 같은 중요한 클러스터 구성 요소가 포함됩니다.

+
+

Tip

+

중요한 클러스터 구성 요소는 대개 *-system 네임스페이스에 설치됩니다.

+
kubectl get ns | grep '-system'
+
+
+

Kubernetes API를 사용하는 구성 요소를 식별한 후에는 해당 설명서에서 버전 호환성 및 업그레이드 요구 사항을 확인하십시오. 예를 들어 버전 호환성에 대해서는 AWS 로드밸런서 컨트롤러 설명서를 참조하십시오.클러스터 업그레이드를 진행하기 전에 일부 구성 요소를 업그레이드하거나 구성을 변경해야 할 수 있습니다. 확인해야 할 몇 가지 중요한 구성 요소로는 CoreDNS, kube-proxy, VPC CNI, 스토리지 드라이버 등이 있습니다.

+

클러스터에는 Kubernetes API를 사용하는 많은 워크로드가 포함되는 경우가 많으며 인그레스 컨트롤러, 지속적 전달 시스템, 모니터링 도구와 같은 워크로드 기능에 필요합니다.EKS 클러스터를 업그레이드할 때는 애드온과 타사 도구도 업그레이드하여 호환되는지 확인해야 합니다.

+

일반적인 애드온의 다음 예와 관련 업그레이드 설명서를 참조하십시오.

+
    +
  • Amazon VPC CNI: 각 클러스터 버전에 대한 Amazon VPC CNI 애드온의 권장 버전은 쿠버네티스 자체 관리형 애드온용 Amazon VPC CNI 플러그인 업데이트를 참조하십시오. Amazon EKS 애드온으로 설치한 경우 한 번에 하나의 마이너 버전만 업그레이드할 수 있습니다.
  • +
  • kube-proxy: 쿠버네티스 kube-proxy 자체 관리형 애드온 업데이트를 참조하십시오.
  • +
  • CoreDNS: CoreDNS 자체 관리형 애드온 업데이트를 참조하십시오.
  • +
  • AWS Load Balancer Controller: AWS Load Balancer Controller는 배포한 EKS 버전과 호환되어야 합니다. 자세한 내용은 설치 가이드를 참조하십시오.
  • +
  • Amazon Elastic Block Store (아마존 EBS) 컨테이너 스토리지 인터페이스 (CSI) 드라이버: 설치 및 업그레이드 정보는 Amazon EKS 애드온으로 Amazon EBS CSI 드라이버 관리를 참조하십시오.
  • +
  • Amazon Elastic File System (Amazon EFS) 컨테이너 스토리지 인터페이스 (CSI) 드라이버: 설치 및 업그레이드 정보는 Amazon EFS CSI 드라이버 를 참조하십시오.
  • +
  • 쿠버네티스 메트릭 서버: 자세한 내용은 GitHub의 metrics-server를 참조하십시오.
  • +
  • 쿠버네티스 Cluster Autoscaler: 쿠버네티스 Cluster Autoscaler 버전을 업그레이드하려면 배포 시 이미지 버전을 변경하십시오. Cluster Autoscaler는 쿠버네티스 스케줄러와 밀접하게 연결되어 있습니다. 클러스터를 업그레이드할 때는 항상 업그레이드해야 합니다. GitHub 릴리스를 검토하여 쿠버네티스 마이너 버전에 해당하는 최신 릴리스의 주소를 찾으십시오.
  • +
  • Karpenter: 설치 및 업그레이드 정보는 Karpenter 설명서를 참조하십시오.
  • +
+

업그레이드 전에 기본 EKS 요구 사항 확인

+

AWS에서 업그레이드 프로세스를 완료하려면 계정에 특정 리소스가 필요합니다.이런 리소스가 없는 경우 클러스터를 업그레이드할 수 없습니다.컨트롤 플레인 업그레이드에는 다음 리소스가 필요합니다.

+
    +
  1. 사용 가능한 IP 주소: 클러스터를 업데이트하려면 Amazon EKS에서 클러스터를 생성할 때 지정한 서브넷의 사용 가능한 IP 주소가 최대 5개까지 필요합니다.
  2. +
  3. EKS IAM 역할: 컨트롤 플레인 IAM 역할은 필요한 권한으로 계정에 계속 존재합니다.
  4. +
  5. 클러스터에 시크릿 암호화가 활성화되어 있는 경우 클러스터 IAM 역할에 AWS KMS키를 사용할 권한이 있는지 확인하십시오.
  6. +
+

사용 가능한 IP 주소 확인

+

클러스터를 업데이트하려면 Amazon EKS에서 클러스터를 생성할 때 지정한 서브넷의 사용 가능한 IP 주소가 최대 5개까지 필요합니다.

+

다음 명령을 실행하여 서브넷에 클러스터를 업그레이드할 수 있는 충분한 IP 주소가 있는지 확인할 수 있습니다.

+
CLUSTER=<cluster name>
+aws ec2 describe-subnets --subnet-ids \
+  $(aws eks describe-cluster --name ${CLUSTER} \
+  --query 'cluster.resourcesVpcConfig.subnetIds' \
+  --output text) \
+  --query 'Subnets[*].[SubnetId,AvailabilityZone,AvailableIpAddressCount]' \
+  --output table
+
+----------------------------------------------------
+|                  DescribeSubnets                 |
++---------------------------+--------------+-------+
+|  subnet-067fa8ee8476abbd6 |  us-east-1a  |  8184 |
+|  subnet-0056f7403b17d2b43 |  us-east-1b  |  8153 |
+|  subnet-09586f8fb3addbc8c |  us-east-1a  |  8120 |
+|  subnet-047f3d276a22c6bce |  us-east-1b  |  8184 |
++---------------------------+--------------+-------+
+
+

VPC CNI Metrics Helper 를 사용하여 VPC 지표에 대한 CloudWatch 대시보드를 만들 수 있습니다. +클러스터 생성 시 처음 지정한 서브넷의 IP 주소가 부족한 경우, Amazon EKS는 쿠버네티스 버전 업그레이드를 시작하기 전에 “UpdateClusterConfiguration” API를 사용하여 클러스터 서브넷을 업데이트할 것을 권장합니다. 신규 서브넷이 아래 항목을 만족하는지 확인하십시오:

+
    +
  • 신규 서브넷은 클러스터 생성 중에 선택한 동일 가용영역에 속합니다.
  • +
  • 신규 서브넷은 클러스터 생성 시 제공된 동일 VPC에 속합니다.
  • +
+

기존 VPC CIDR 블록의 IP 주소가 부족한 경우 추가 CIDR 블록을 연결하는 것을 고려해 보십시오. AWS를 사용하면 추가 CIDR 블록을 기존 클러스터 VPC와 연결하여 IP 주소 풀을 효과적으로 확장할 수 있습니다. 이러한 확장은 추가 프라이빗 IP 범위 (RFC 1918) 를 도입하거나, 필요한 경우 퍼블릭 IP 범위 (비 RFC 1918) 를 도입하여 수행할 수 있습니다. Amazon EKS에서 새 CIDR을 사용하려면 먼저 새 VPC CIDR 블록을 추가하고 VPC 새로 고침이 완료될 때까지 기다려야 합니다. 그런 다음 새로 설정된 CIDR 블록을 기반으로 서브넷을 VPC로 업데이트할 수 있습니다.

+

EKS IAM 역할 확인

+

다음 명령을 실행하여 IAM 역할을 사용할 수 있고 계정에 올바른 역할 수임 정책이 적용되었는지 확인할 수 있습니다.

+
CLUSTER=<cluster name>
+ROLE_ARN=$(aws eks describe-cluster --name ${CLUSTER} \
+  --query 'cluster.roleArn' --output text)
+aws iam get-role --role-name ${ROLE_ARN##*/} \
+  --query 'Role.AssumeRolePolicyDocument'
+
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Principal": {
+                "Service": "eks.amazonaws.com"
+            },
+            "Action": "sts:AssumeRole"
+        }
+    ]
+}
+
+

EKS 애드온으로 마이그레이션

+

Amazon EKS는 모든 클러스터에 쿠버네티스 용 Amazon VPC CNI 플러그인, kube-proxy, CoreDNS와 같은 애드온을 자동으로 설치합니다. 애드온은 자체 관리하거나 Amazon EKS 애드온으로 설치할 수 있습니다. Amazon EKS 애드온은 EKS API를 사용하여 애드온을 관리하는 또 다른 방법입니다.

+

Amazon EKS 애드온을 사용하여 단일 명령으로 버전을 업데이트할 수 있습니다. 예:

+
aws eks update-addon —cluster-name my-cluster —addon-name vpc-cni —addon-version version-number \
+--service-account-role-arn arn:aws:iam::111122223333:role/role-name —configuration-values '{}' —resolve-conflicts PRESERVE
+
+

다음과 같은 EKS 애드온이 있는지 확인:

+
aws eks list-addons --cluster-name <cluster name>
+
+
+

Warning

+

EKS 애드온은 컨트롤 플레인 업그레이드 중에 자동으로 업그레이드되지 않습니다. EKS 애드온 업데이트를 시작하고 원하는 버전을 선택해야 합니다.

+ +
+

EKS 애드온으로 사용할 수 있는 구성 요소와 시작 방법에 대해 자세히 알아보세요.

+

EKS 애드온에 사용자 지정 구성을 제공하는 방법을 알아봅니다.

+

컨트롤 플레인을 업그레이드하기 전에 제거된 API 사용을 식별하고 수정하기

+

EKS 컨트롤 플레인을 업그레이드하기 전에 제거된 API의 API 사용을 확인해야 합니다. 이를 위해서는 실행 중인 클러스터 또는 정적으로 렌더링된 Kubernetes 매니페스트 파일을 확인할 수 있는 도구를 사용하는 것이 좋습니다.

+

일반적으로 정적 매니페스트 파일을 대상으로 검사를 실행하는 것이 더 정확합니다. 라이브 클러스터를 대상으로 실행하면 이런 도구가 오탐을 반환할 수 있습니다.

+

쿠버네티스 API가 더 이상 사용되지 않는다고 해서 API가 제거된 것은 아닙니다. 쿠버네티스 지원 중단 정책을 확인하여 API 제거가 워크로드에 미치는 영향을 이해해야 합니다.

+

클러스터 인사이트

+

클러스터 인사이트는 EKS 클러스터를 최신 버전의 쿠버네티스로 업그레이드하는 기능에 영향을 미칠 수 있는 문제에 대한 결과를 제공하는 기능입니다. 이러한 결과는 Amazon EKS에서 선별 및 관리하며 문제 해결 방법에 대한 권장 사항을 제공합니다. 클러스터 인사이트를 활용하면 최신 쿠버네티스 버전으로 업그레이드하는 데 드는 노력을 최소화할 수 있습니다.

+

EKS 클러스터의 인사이트를 보려면 다음 명령을 실행할 수 있습니다. +

aws eks list-insights --region <region-code> --cluster-name <my-cluster>
+
+{
+    "insights": [
+        {
+            "category": "UPGRADE_READINESS", 
+            "name": "Deprecated APIs removed in Kubernetes v1.29", 
+            "insightStatus": {
+                "status": "PASSING", 
+                "reason": "No deprecated API usage detected within the last 30 days."
+            }, 
+            "kubernetesVersion": "1.29", 
+            "lastTransitionTime": 1698774710.0, 
+            "lastRefreshTime": 1700157422.0, 
+            "id": "123e4567-e89b-42d3-a456-579642341238", 
+            "description": "Checks for usage of deprecated APIs that are scheduled for removal in Kubernetes v1.29. Upgrading your cluster before migrating to the updated APIs supported by v1.29 could cause application impact."
+        }
+    ]
+}
+

+

인사이트에 대해 더 자세한 내용을 출력하려면 다음 명령을 실행할 수 있습니다: +

aws eks describe-insight --region <region-code> --id <insight-id> --cluster-name <my-cluster>
+

+

Amazon EKS 콘솔에서 인사이트를 볼 수도 있습니다.클러스터 목록에서 클러스터를 선택하면 인사이트 결과가 업그레이드 인사이트 탭 아래에 표시됩니다.

+

"status": ERROR인 클러스터 인사이트를 찾은 경우 클러스터 업그레이드를 수행하기 전에 문제를 해결해야 합니다. aws eks describe-insight 명령을 실행하면 다음과 같은 개선 권고 사항을 공유할 수 있습니다:

+

영향을 받는 리소스: +

"resources": [
+      {
+        "insightStatus": {
+          "status": "ERROR"
+        },
+        "kubernetesResourceUri": "/apis/policy/v1beta1/podsecuritypolicies/null"
+      }
+]
+

+

APIs deprecated: +

"deprecationDetails": [
+      {
+        "usage": "/apis/flowcontrol.apiserver.k8s.io/v1beta2/flowschemas", 
+        "replacedWith": "/apis/flowcontrol.apiserver.k8s.io/v1beta3/flowschemas", 
+        "stopServingVersion": "1.29", 
+        "clientStats": [], 
+        "startServingReplacementVersion": "1.26"
+      }
+]
+

+

취해야 할 권장 조치: +

"recommendation": "Update manifests and API clients to use newer Kubernetes APIs if applicable before upgrading to Kubernetes v1.26."
+

+

EKS 콘솔 또는 CLI를 통해 클러스터 통찰력을 활용하면 EKS 클러스터 버전을 성공적으로 업그레이드하는 프로세스를 가속화할 수 있습니다. 다음 리소스를 통해 자세히 알아보십시오: +* 공식 EKS 문서 +* 클러스터 인사이트 출시 블로그.

+

Kube-no-Trouble

+

Kube-no-Troublekubent 명령을 사용하는 오픈소스 커맨드라인 유틸리티입니다. 인수 없이 kubent를 실행하면 현재 KubeConfig 컨텍스트를 사용하여 클러스터를 스캔하고 더 이상 사용되지 않고 제거될 API가 포함된 보고서를 생성합니다

+
kubent
+
+4:17PM INF >>> Kube No Trouble `kubent` <<<
+4:17PM INF version 0.7.0 (git sha d1bb4e5fd6550b533b2013671aa8419d923ee042)
+4:17PM INF Initializing collectors and retrieving data
+4:17PM INF Target K8s version is 1.24.8-eks-ffeb93d
+4:l INF Retrieved 93 resources from collector name=Cluster
+4:17PM INF Retrieved 16 resources from collector name="Helm v3"
+4:17PM INF Loaded ruleset name=custom.rego.tmpl
+4:17PM INF Loaded ruleset name=deprecated-1-16.rego
+4:17PM INF Loaded ruleset name=deprecated-1-22.rego
+4:17PM INF Loaded ruleset name=deprecated-1-25.rego
+4:17PM INF Loaded ruleset name=deprecated-1-26.rego
+4:17PM INF Loaded ruleset name=deprecated-future.rego
+__________________________________________________________________________________________
+>>> Deprecated APIs removed in 1.25 <<<
+------------------------------------------------------------------------------------------
+KIND                NAMESPACE     NAME             API_VERSION      REPLACE_WITH (SINCE)
+PodSecurityPolicy   <undefined>   eks.privileged   policy/v1beta1   <removed> (1.21.0)
+
+

정적 매니페스트 파일 및 헬름 패키지를 스캔하는 데에도 사용할 수 있습니다.매니페스트를 배포하기 전에 문제를 식별하려면 지속적 통합 (CI) 프로세스의 일부로 kubent 를 실행하는 것이 좋습니다.또한 매니페스트를 스캔하는 것이 라이브 클러스터를 스캔하는 것보다 더 정확합니다.

+

Kube-no-Trouble은 클러스터를 스캔하기 위한 적절한 권한이 있는 샘플 서비스 어카운트 및 역할을 제공합니다.

+

풀루토(pluto)

+

또 다른 옵션은 pluto인데, 이는 라이브 클러스터, 매니페스트 파일, 헬름 차트 스캔을 지원하고 CI 프로세스에 포함할 수 있는 GitHub Action이 있다는 점에서 kubent와 비슷합니다.

+
pluto detect-all-in-cluster
+
+NAME             KIND                VERSION          REPLACEMENT   REMOVED   DEPRECATED   REPL AVAIL  
+eks.privileged   PodSecurityPolicy   policy/v1beta1                 false     true         true
+
+

리소스

+

업그레이드 전에 클러스터가 지원 중단된 API를 사용하지 않는지 확인하려면 다음을 모니터링해야 합니다:

+
    +
  • 쿠버네티스 v1.19 apiserver_requested_deprecated_apis 메트릭:
  • +
+
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis
+
+apiserver_requested_deprecated_apis{group="policy",removed_release="1.25",resource="podsecuritypolicies",subresource="",version="v1beta1"} 1
+
+
    +
  • k8s.io/deprecatedtrue로 표시된 감사 로그 내 이벤트:
  • +
+
CLUSTER="<cluster_name>"
+QUERY_ID=$(aws logs start-query \
+ --log-group-name /aws/eks/${CLUSTER}/cluster \
+ --start-time $(date -u --date="-30 minutes" "+%s") # or date -v-30M "+%s" on MacOS \
+ --end-time $(date "+%s") \
+ --query-string 'fields @message | filter `annotations.k8s.io/deprecated`="true"' \
+ --query queryId --output text)
+
+echo "Query started (query id: $QUERY_ID), please hold ..." && sleep 5 # give it some time to query
+
+aws logs get-query-results --query-id $QUERY_ID
+
+

더 이상 사용되지 않는 API를 사용하는 경우 다음 행이 출력됩니다:

+
{
+    "results": [
+        [
+            {
+                "field": "@message",
+                "value": "{\"kind\":\"Event\",\"apiVersion\":\"audit.k8s.io/v1\",\"level\":\"Request\",\"auditID\":\"8f7883c6-b3d5-42d7-967a-1121c6f22f01\",\"stage\":\"ResponseComplete\",\"requestURI\":\"/apis/policy/v1beta1/podsecuritypolicies?allowWatchBookmarks=true\\u0026resourceVersion=4131\\u0026timeout=9m19s\\u0026timeoutSeconds=559\\u0026watch=true\",\"verb\":\"watch\",\"user\":{\"username\":\"system:apiserver\",\"uid\":\"8aabfade-da52-47da-83b4-46b16cab30fa\",\"groups\":[\"system:masters\"]},\"sourceIPs\":[\"::1\"],\"userAgent\":\"kube-apiserver/v1.24.16 (linux/amd64) kubernetes/af930c1\",\"objectRef\":{\"resource\":\"podsecuritypolicies\",\"apiGroup\":\"policy\",\"apiVersion\":\"v1beta1\"},\"responseStatus\":{\"metadata\":{},\"code\":200},\"requestReceivedTimestamp\":\"2023-10-04T12:36:11.849075Z\",\"stageTimestamp\":\"2023-10-04T12:45:30.850483Z\",\"annotations\":{\"authorization.k8s.io/decision\":\"allow\",\"authorization.k8s.io/reason\":\"\",\"k8s.io/deprecated\":\"true\",\"k8s.io/removed-release\":\"1.25\"}}"
+            },
+[...]
+
+

쿠버네티스 워크로드 업데이트. kubectl-convert를 사용하여 매니페스트를 업데이트

+

업데이트가 필요한 워크로드와 매니페스트를 식별한 후에는 매니페스트 파일의 리소스 유형을 변경해야 할 수 있습니다 (예: 파드시큐리티폴리시를 파드시큐리티스탠다드로).이를 위해서는 리소스 사양을 업데이트하고 교체할 리소스에 따른 추가 조사가 필요합니다.

+

리소스 유형은 동일하게 유지되지만 API 버전을 업데이트해야 하는 경우, kubectl-convert 명령을 사용하여 매니페스트 파일을 자동으로 변환할 수 있습니다. 예전 디플로이먼트를 apps/v1으로 변환하는 경우를 예로 들 수 있다. 자세한 내용은 쿠버네티스 웹 사이트의 kubectl convert 플러그인 설치 를 참조하십시오.

+

kubectl-convert -f <file> --output-version <group>/<version>

+

데이터 플레인이 업그레이드되는 동안 워크로드의 가용성을 보장하도록 PodDisruptionBudget 및 topologySpreadConstraints 조건을 구성.

+

데이터 플레인이 업그레이드되는 동안 워크로드의 가용성을 보장하려면 워크로드에 적절한 PodDisruptionBudget(파드디스럽션 예산)토폴로지 스프레드 제약 조건 이 있는지 확인하십시오.모든 워크로드에 동일한 수준의 가용성이 필요한 것은 아니므로 워크로드의 규모와 요구 사항을 검증해야 합니다.

+

워크로드가 여러 가용영역에 분산되어 있고 토폴로지가 분산된 여러 호스트에 분산되어 있는지 확인하면 워크로드가 문제 없이 새 데이터 플레인으로 자동 마이그레이션될 것이라는 신뢰도가 높아집니다.

+

다음은 항상 80% 의 복제본을 사용할 수 있고 여러 영역과 호스트에 걸쳐 복제본을 분산시키는 워크로드의 예입니다.

+
apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: myapp
+spec:
+  minAvailable: "80%"
+  selector:
+    matchLabels:
+      app: myapp
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: myapp
+spec:
+  replicas: 10
+  selector:
+    matchLabels:
+      app: myapp
+  template:
+    metadata:
+      labels:
+        app: myapp
+    spec:
+      containers:
+      - image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
+        name: myapp
+        resources:
+          requests:
+            cpu: "1"
+            memory: 256M
+      topologySpreadConstraints:
+      - labelSelector:
+          matchLabels:
+            app: host-zone-spread
+        maxSkew: 2
+        topologyKey: kubernetes.io/hostname
+        whenUnsatisfiable: DoNotSchedule
+      - labelSelector:
+          matchLabels:
+            app: host-zone-spread
+        maxSkew: 2
+        topologyKey: topology.kubernetes.io/zone
+        whenUnsatisfiable: DoNotSchedule
+
+

AWS Resilience Hub 는 아마존 엘라스틱 쿠버네티스 서비스 (Amazon EKS) 를 지원 리소스로 추가했습니다. Resilience Hub는 애플리케이션 복원력을 정의, 검증 및 추적할 수 있는 단일 장소를 제공하므로 소프트웨어, 인프라 또는 운영 중단으로 인한 불필요한 다운타임을 피할 수 있습니다.

+

관리형 노드 그룹 또는 Karpenter를 사용하여 데이터 플레인 업그레이드를 간소화.

+

관리형 노드 그룹과 Karpenter는 모두 노드 업그레이드를 단순화하지만 접근 방식은 다릅니다.

+

관리형 노드 그룹은 노드의 프로비저닝 및 라이프사이클 관리를 자동화합니다.즉, 한 번의 작업으로 노드를 생성, 자동 업데이트 또는 종료할 수 있습니다.

+

기본 구성에서 Karpenter는 호환되는 최신 EKS 최적화 AMI를 사용하여 새 노드를 자동으로 생성합니다. EKS가 업데이트된 EKS 최적화 AMI를 출시하거나 클러스터가 업그레이드되면 Karpenter는 자동으로 이런 이미지를 사용하기 시작합니다.Karpenter는 노드 업데이트를 위한 노드 만료도 구현합니다.

+

Karpenter는 사용자 지정 AMI를 사용하도록 구성할 수 있습니다. Karpenter에서 사용자 지정 AMI를 사용하는 경우 kubelet 버전에 대한 책임은 사용자에게 있습니다.

+

기존 노드 및 컨트롤 플레인과의 버전 호환성 확인

+

Amazon EKS에서 쿠버네티스 업그레이드를 진행하기 전에 관리형 노드 그룹, 자체 관리형 노드 및 컨트롤 플레인 간의 호환성을 보장하는 것이 중요합니다. 호환성은 사용 중인 쿠버네티스 버전에 따라 결정되며 다양한 시나리오에 따라 달라집니다. 전략:

+
    +
  • 쿠버네티스 v1.28+ — **** 쿠버네티스 버전 1.28 이상부터는 핵심 구성 요소에 대한 보다 관대한 버전 정책이 있습니다. 특히, 쿠버네티스 API 서버와 kubelet 간에 지원되는 스큐(skew)가 하나의 마이너 버전 (n-2에서 n-3으로) 으로 확장되었습니다. 예를 들어, 사용 중인 EKS 컨트롤 플레인 버전이 1.28인 경우, 1.25까지의 kubelet 버전을 안전하게 사용할 수 있다. 이 버전 스큐는 AWS Fargate, 관리형 노드 그룹자체 관리형 노드 에서 지원됩니다. 보안상의 이유로 Amazon 머신 이미지 (AMI) 버전을 최신 상태로 유지하는 것이 좋습니다. 이전 kubelet 버전은 잠재적인 공통 취약성 및 노출 (CVE) 으로 인해 보안 위험을 초래할 수 있으며, 이는 이전 kubelet 버전 사용의 이점보다 클 수 있습니다.
  • +
  • 쿠버네티스 < v1.28 — v1.28 이전 버전을 사용하는 경우, API 서버와 kubelet 간에 지원되는 스큐는 n-2이다. 예를 들어, 사용 중인 EKS 버전이 1.27인 경우, 사용할 수 있는 가장 오래된 kubelet 버전은 1.25이다. 이 버전 차이는 AWS Fargate, 관리형 노드 그룹자체 관리형 노드에 적용됩니다.
  • +
+

Karpenter 관리형 노드의 노드 만료 활성화

+

Karpenter가 노드 업그레이드를 구현하는 한 가지 방법은 노드 만료라는 개념을 사용하는 것입니다. 이렇게 하면 노드 업그레이드에 필요한 계획이 줄어듭니다. 프로비저너에서 ttlSecondsUntilExpired 의 값을 설정하면 노드 만료가 활성화됩니다. 노드가 몇 초 만에 정의된 연령에 도달하면 노드가 안전하게 비워지고 삭제됩니다. 이는 사용 중인 경우에도 마찬가지이므로 노드를 새로 프로비저닝된 업그레이드된 인스턴스로 교체할 수 있습니다. 노드가 교체되면 Karpenter는 EKS에 최적화된 최신 AMI를 사용합니다. 자세한 내용은 Karpenter 웹 사이트의 디프로비저닝(Deprovisioning)를 참조하십시오.

+

Karpenter는 이 값에 지터를 자동으로 추가하지 않습니다. 과도한 워크로드 중단을 방지하려면 Kubernetes 설명서에 나와 있는 대로 Pod Disruption Budget (PDB) 을 정의하십시오.

+

프로비저너에서 ttlSecondsUntilExpired 값을 설정하는 경우 이는 프로비저너와 연결된 기존 노드에 적용됩니다.

+

Karpenter 관리 노드에 드리프트 기능 사용

+

Karpenter's Drift 기능은 Karpenter가 프로비저닝한 노드를 자동으로 업그레이드하여 EKS 컨트롤 플레인과 동기화 상태를 유지할 수 있습니다. Karpenter 드리프트는 현재 기능 게이트를 사용하여 활성화해야 합니다. Karpenter의 기본 구성은 EKS 클러스터의 컨트롤 플레인과 동일한 메이저 및 마이너 버전에 대해 EKS에 최적화된 최신 AMI를 사용합니다.

+

EKS 클러스터 업그레이드가 완료되면 Karpenter의 Drift 기능은 Karpenter가 프로비저닝한 노드가 이전 클러스터 버전용 EKS 최적화 AMI를 사용하고 있음을 감지하고 해당 노드를 자동으로 연결, 드레인 및 교체합니다. 새 노드로 이동하는 파드를 지원하려면 적절한 파드 리소스 할당량을 설정하고 Pod Disruption Budgets(PDB) 을 사용하여 쿠버네티스 모범 사례를 따르세요. Karpenter의 디프로비저닝은 파드 리소스 요청을 기반으로 대체 노드를 미리 가동하고 노드 디프로비저닝을 할 때 PDB를 존중합니다.

+

eksctl을 사용하여 자체 관리형 노드 그룹의 업그레이드를 자동화

+

자체 관리형 노드 그룹은 사용자 계정에 배포되고 EKS 서비스 외부의 클러스터에 연결된 EC2 인스턴스입니다. 이들은 일반적으로 일종의 자동화 도구를 통해 배포되고 관리됩니다. 자체 관리형 노드 그룹을 업그레이드하려면 도구 설명서를 참조해야 합니다.

+

예를 들어 eksctl은 자체 관리형 노드 삭제 및 드레인을 지원합니다.

+

몇 가지 일반적인 도구는 다음과 같습니다.

+ +

업그레이드 전 클러스터 백업

+

새 버전의 쿠버네티스는 Amazon EKS 클러스터를 크게 변경합니다. 클러스터를 업그레이드한 후에는 다운그레이드할 수 없습니다.

+

Velero 는 커뮤니티에서 지원하는 오픈 소스 도구로, 기존 클러스터를 백업하고 새 클러스터에 백업을 적용하는 데 사용할 수 있습니다.

+

현재 EKS에서 지원하는 쿠버네티스 버전에서만 새 클러스터를 생성할 수 있다는 점에 유의하십시오. 클러스터가 현재 실행 중인 버전이 계속 지원되고 업그레이드가 실패할 경우 원래 버전으로 새 클러스터를 생성하고 데이터 플레인을 복원할 수 있습니다. 참고로 IAM을 포함한 AWS 리소스는 Velero의 백업에 포함되지 않습니다. 이런 리소스는 다시 생성해야 합니다.

+

컨트롤 플레인을 업그레이드한 후 Fargate 배포를 다시 시작.

+

Fargate 데이터 플레인 노드를 업그레이드하려면 워크로드를 재배포해야 합니다. 모든 파드를 -o wide 옵션으로 나열하여 파게이트 노드에서 실행 중인 워크로드를 식별할 수 있습니다.'fargate-'로 시작하는 모든 노드 이름은 클러스터에 재배포해야 합니다.

+

인플레이스 클러스터 업그레이드의 대안으로 블루/그린 클러스터 평가

+

일부 고객은 블루/그린 업그레이드 전략을 선호합니다. 여기에는 이점이 있을 수 있지만 고려해야 할 단점도 있습니다.

+

혜택은 다음과 같습니다:

+
    +
  • 여러 EKS 버전을 한 번에 변경할 수 있습니다 (예: 1.23에서 1.25).
  • +
  • 이전 클러스터로 다시 전환 가능
  • +
  • 최신 시스템 (예: terraform) 으로 관리할 수 있는 새 클러스터를 생성합니다.
  • +
  • 워크로드를 개별적으로 마이그레이션할 수 있습니다.
  • +
+

몇 가지 단점은 다음과 같습니다.

+
    +
  • 소비자 업데이트가 필요한 API 엔드포인트 및 OIDC 변경 (예: kubectl 및 CI/CD)
  • +
  • 마이그레이션 중에 2개의 클러스터를 병렬로 실행해야 하므로 비용이 많이 들고 지역 용량이 제한될 수 있습니다.
  • +
  • 워크로드가 서로 종속되어 함께 마이그레이션되는 경우 더 많은 조정이 필요합니다.
  • +
  • 로드밸런서와 외부 DNS는 여러 클러스터에 쉽게 분산될 수 없습니다.
  • +
+

이 전략은 가능하지만 인플레이스 업그레이드보다 비용이 많이 들고 조정 및 워크로드 마이그레이션에 더 많은 시간이 필요합니다.상황에 따라 필요할 수 있으므로 신중하게 계획해야 합니다.

+

높은 수준의 자동화와 GitOps와 같은 선언형 시스템을 사용하면 이 작업을 더 쉽게 수행할 수 있습니다.데이터를 백업하고 새 클러스터로 마이그레이션하려면 상태 저장 워크로드에 대한 추가 예방 조치를 취해야 합니다.

+

자세한 내용은 다음 블로그 게시물을 검토하세요.

+ +

쿠버네티스 프로젝트에서 계획된 주요 변경 사항 추적 — 미리 생각해 보세요

+

쿠버네티스 다음 버전만 고려하지 마세요. 쿠버네티스의 새 버전이 출시되면 이를 검토하고 주요 변경 사항을 확인하십시오. 예를 들어, 일부 애플리케이션은 도커 API를 직접 사용했고, 도커용 컨테이너 런타임 인터페이스 (CRI) (Dockershim이라고도 함) 에 대한 지원은 쿠버네티스 1.24에서 제거되었습니다. 이런 종류의 변화에는 대비하는 데 더 많은 시간이 필요합니다.

+

업그레이드하려는 버전에 대해 문서화된 모든 변경 사항을 검토하고 필요한 업그레이드 단계를 기록해 두십시오.또한 Amazon EKS 관리형 클러스터와 관련된 모든 요구 사항 또는 절차를 기록해 두십시오.

+ +

기능 제거에 대한 구체적인 지침

+

1.25에서 Dockershim 제거 - Docker Socket(DDS) 용 검출기 사용

+

1.25용 EKS 최적화 AMI에는 더 이상 Dockershim에 대한 지원이 포함되지 않습니다. Dockershim에 대한 종속성이 있는 경우 (예: Docker 소켓을 마운트하는 경우) 워커 노드를 1.25로 업그레이드하기 전에 이런 종속성을 제거해야 합니다.

+

1.25로 업그레이드하기 전에 도커 소켓에 종속 관계가 있는 인스턴스를 찾아보세요. kubectl 플러그인인 Detector for Docker Socket (DDS) 를 사용하는 것이 좋습니다.

+

1.25에서 파드 시큐리티 폴리시(PSP) 제거 - 파드 시큐리티 스탠다드(PSS) 또는 코드형 정책(PaC) 솔루션으로 마이그레이션

+

PodSecurityPolicy쿠버네티스 1.21에서 지원 중단이 발표이 되었고, 쿠버네티스 1.25에서는 제거되었습니다. 클러스터에서 PSP를 사용하는 경우, 워크로드 중단을 방지하기 위해 클러스터를 버전 1.25로 업그레이드하기 전에 내장된 쿠버네티스 파드 시큐리티 스탠다드 (PSS) 또는 코드형 정책(PaC) 솔루션으로 마이그레이션해야 합니다.

+

AWS는 EKS 설명서에 자세한 FAQ를 게시했습니다.

+

파드 시큐리티 스탠다드(PSS) 및 파드 시큐리티 어드미션(PSA) 모범 사례를 검토하십시오.

+

쿠버네티스 웹사이트에서 파드 시큐리티 폴리시(PSP) 지원 중단 블로그 게시물을 검토하십시오.

+

1.23에서 In-tree 스토리지 드라이버 지원 중단 - 컨테이너 스토리지 인터페이스 (CSI) 드라이버로 마이그레이션

+

컨테이너 스토리지 인터페이스(CSI)는 쿠버네티스가 기존의 In-tree 스토리지 드라이버 메커니즘을 대체할 수 있도록 설계되었습니다. Amazon EBS 컨테이너 스토리지 인터페이스 (CSI) 마이그레이션 기능은 Amazon EKS 1.23 이상 클러스터에서 기본적으로 활성화됩니다. 파드가 버전 1.22 또는 이전 클러스터에서 실행되는 경우, 서비스 중단을 방지하려면 클러스터를 버전 1.23으로 업데이트하기 전에 Amazon EBS CSI 드라이버를 설치해야 합니다.

+

Amazon EBS CSI 마이그레이션 자주 묻는 질문을 검토하십시오.

+

추가 리소스

+

ClowdHaus EKS 업그레이드 지침

+

ClowdHaus EKS 업그레이드 지침 은 Amazon EKS 클러스터를 업그레이드하는 데 도움이 되는 CLI입니다.클러스터를 분석하여 업그레이드 전에 해결해야 할 잠재적 문제를 찾아낼 수 있습니다.

+

GoNoGo

+

GoNoGo 는 클러스터 애드온의 업그레이드 신뢰도를 결정하는 알파 단계 도구입니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/ami/index.html b/ko/windows/docs/ami/index.html new file mode 100644 index 000000000..5c620892a --- /dev/null +++ b/ko/windows/docs/ami/index.html @@ -0,0 +1,2224 @@ + + + + + + + + + + + + + + + + + + + + + + + AMI 관리 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Amazon EKS 최적화 윈도우 AMI 관리

+

윈도우 Amazon EKS 최적화 AMI는 윈도우 서버 2019와 윈도우 서버 2022를 기반으로 구축되었습니다. 아마존 EKS 윈도우 노드의 기본 이미지 역할을 하도록 구성되어 있습니다. 해당 AMI는 Amazon EKS 노드의 기본 이미지로 사용하도록 구성되어 있습니다. 기본적으로 AMI에는 다음 구성 요소가 포함됩니다: +- kubelet +- kube-proxy +- AWS IAM Authenticator for Kubernetes +- csi-proxy +- containerd

+

AWS Systems Manager Parameter Store API를 쿼리하여 Amazon EKS 최적화 AMI용 아마존 머신 이미지 (AMI) ID를 프로그래밍 방식으로 검색할 수 있습니다. 이 파라미터를 사용하면 Amazon EKS 최적화 AMI ID를 수동으로 찾아볼 필요가 없습니다. Systems Manager Parameter Store API에 대한 자세한 내용은 GetParameter를 참조하십시오. Amazon EKS 최적화 AMI 메타데이터를 검색하려면 사용자 계정에 SSM:GetParameter IAM 권한이 있어야 합니다.

+

다음 예제는 윈도우 서버 2019 LTSC 코어용 최신 아마존 EKS 최적화 AMI의 AMI ID를 검색합니다. AMI 이름에 나열된 버전 번호는 준비된 해당 쿠버네티스 빌드와 관련이 있습니다.

+
aws ssm get-parameter --name /aws/service/ami-windows-latest/Windows_Server-2019-English-Core-EKS_Optimized-1.21/image_id --region us-east-1 --query "Parameter.Value" --output text
+
+

출력 예:

+
ami-09770b3eec4552d4e
+
+

Amazon EKS 최적화 자체적인 윈도우 AMI 관리

+

프로덕션 환경을 향한 필수 단계는 Amazon EKS 클러스터 전체에서 동일한 Amazon EKS 최적화 윈도우 AMI 및 kubelet 버전을 유지 관리하는 것입니다.

+

Amazon EKS 클러스터 전체에서 동일한 버전을 사용하면 문제 해결 시간이 단축되고 클러스터 일관성이 향상됩니다.Amazon EC2 Image Builder를 사용하면 Amazon EKS 클러스터에서 사용할 사용자 지정 Amazon EKS 최적화 윈도우 AMI를 생성하고 유지 관리할 수 있습니다.

+

Amazon EC2 Image Builder를 사용하여 윈도우 서버 버전, AWS 윈도우 서버 AMI 출시일 및/또는 OS 빌드 버전 중에서 선택할 수 있습니다. 구성 요소 빌드 단계에서는 기존 EKS 최적화 윈도우 아티팩트와 kubelet 버전 중에서 선택할 수 있습니다. 자세한 내용은 이 문서에서 확인 가능합니다.

+

+

참고: 기본 이미지를 선택하기 전에 윈도우 서버 버전 및 라이선스 섹션에서 릴리스 채널 업데이트와 관련된 중요한 세부 정보를 참조하십시오.

+

사용자 지정 EKS 최적화 AMI를 위한 더 빠른 시작 구성

+

사용자 지정 Windows Amazon EKS 최적화 AMI를 사용하는 경우 빠른 실행 기능을 활성화하여 Windows 워커 노드를 최대 65% 더 빠르게 시작할 수 있습니다.이 기능은 Sysprep specialize, Windows Out of Box Experience (OOBE) 단계를 수행하고 필요한 재부팅이 이미 완료된 사전 프로비저닝된 스냅샷 세트를 유지 관리합니다. 이 스냅샷은 이후 실행 시 사용되므로 노드를 확장하거나 교체하는 데 걸리는 시간을 줄일 수 있습니다. Fast Launch는 EC2 콘솔 또는 AWS CLI를 통해 소유한 AMI에 대해서만 활성화할 수 있으며 유지되는 스냅샷 수는 구성할 수 있습니다.

+

NOTE: 빠른 실행은 Amazon에서 제공하는 기본 EKS 최적화 AMI와 호환되지 않습니다. 활성화하기 전에 위와 같이 사용자 지정 AMI를 생성하십시오.

+

자세한 내용은 다음을 참조하십시오. AWS 윈도우 AMI - 더 빠른 시작을 위한 AMI 구성

+

사용자 지정 AMI에 윈도우 베이스 레이어 캐싱

+

윈도우 컨테이너 이미지는 리눅스 이미지보다 큽니다.컨테이너화된.NET Framework 기반 애플리케이션을 실행하는 경우 평균 이미지 크기는 약 8.24GB입니다.파드 스케줄링 중에는 파드가 Running 상태에 도달하기 전에 컨테이너 이미지를 완전히 가져와서 디스크에서 추출해야 합니다.

+

이 프로세스 동안 컨테이너 런타임 (containerd) 은 디스크의 전체 컨테이너 이미지를 가져와 추출합니다.pull 작업은 병렬 프로세스입니다. 즉, 컨테이너 런타임은 컨테이너 이미지 레이어를 병렬로 가져옵니다.반면 추출 작업은 순차적 프로세스로 진행되며 I/O가 많이 소요됩니다.이로 인해 컨테이너 이미지가 완전히 추출되어 컨테이너 런타임 (containerd) 에서 사용할 준비가 되기까지 8분 이상 걸릴 수 있으며, 그 결과 파드 시작 시간이 몇 분 정도 걸릴 수 있습니다.

+

Windows Server 및 컨테이너 패치 항목에서 언급한 것처럼 EKS로 사용자 지정 AMI를 구축할 수 있는 옵션이 있습니다. AMI를 준비하는 동안 EC2 이미지 빌더 구성 요소를 추가하여 필요한 Windows 컨테이너 이미지를 모두 로컬로 가져온 다음 AMI를 생성할 수 있습니다. 이 전략을 사용하면 파드가 Running 상태에 도달하는 시간을 크게 줄일 수 있습니다.

+

Amazon EC2 이미지 빌더에서 컴퍼넌트를 생성하여 필요한 이미지를 다운로드하고 이미지 레시피에 첨부하십시오. 다음 예제는 ECR 저장소에서 특정 이미지를 가져옵니다.

+
name: ContainerdPull
+description: This component pulls the necessary containers images for a cache strategy.
+schemaVersion: 1.0
+
+phases:
+  - name: build
+    steps:
+      - name: containerdpull
+        action: ExecutePowerShell
+        inputs:
+          commands:
+            - Set-ExecutionPolicy Unrestricted -Force
+            - (Get-ECRLoginCommand).Password | docker login --username AWS --password-stdin 111000111000.dkr.ecr.us-east-1.amazonaws.com
+            - ctr image pull mcr.microsoft.com/dotnet/framework/aspnet:latest
+            - ctr image pull 111000111000.dkr.ecr.us-east-1.amazonaws.com/myappcontainerimage:latest
+
+

다음 구성 요소가 예상대로 작동하는지 확인하려면 EC2 Image builder (EC2InstanceProfileForImageBuilder)에서 사용하는 IAM 역할에 연결된 정책이 있는지 확인하십시오:

+

+

관련 블로그

+

다음 블로그에서는 사용자 지정 Amazon EKS Windows AMI를 위한 캐싱 전략을 구현하는 방법을 단계별로 확인할 수 있습니다:

+

EC2 이미지 빌더 및 이미지 캐시 전략을 통한 윈도우 컨테이너 시작 시간 단축

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/gmsa/index.html b/ko/windows/docs/gmsa/index.html new file mode 100644 index 000000000..8a657da7c --- /dev/null +++ b/ko/windows/docs/gmsa/index.html @@ -0,0 +1,2230 @@ + + + + + + + + + + + + + + + + + + + + + + + gMSA 윈도우 컨테이너 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

윈도우 파드와 컨테이너를 위한 gMSA 설정

+

gMSA 계정이란 무엇입니까?

+

.NET 응용 프로그램과 같은 윈도우 기반 응용 프로그램은 대개 Active Directory를 ID 공급자로 사용하여 NTLM 또는 Kerberos 프로토콜을 사용하여 권한 부여/인증을 제공합니다.

+

Kerberos 티켓을 Active Directory와 교환하려면 애플리케이션 서버가 도메인에 가입되어 있어야 합니다. 윈도우 컨테이너는 도메인 조인을 지원하지 않으며 컨테이너는 임시 리소스이므로 의미가 없어 Active Directory RID 풀에 부담이 됩니다.

+

하지만 관리자는 gMSA Active Directory 계정을 활용하여 윈도우 컨테이너, NLB 및 서버팜과 같은 리소스에 대한 윈도우 인증을 협상할 수 있습니다.

+

윈도우 컨테이너 및 gMSA 사용 사례

+

윈도우 인증을 활용하고 윈도우 컨테이너로 실행되는 애플리케이션은 윈도우 노드가 컨테이너 대신 Kerberos 티켓을 교환하는 데 사용되기 때문에 gMSA 이점을 활용할 수 있습니다. gMSA 통합을 지원하도록 윈도우 워커 노드를 설정하는 데 사용할 수 있는 두 가지 옵션이 있습니다.

+

1 - 도메인에 가입된 윈도우 워커 노드

+

이 설정에서는 윈도우 워커 노드가 Active Directory 도메인에 가입되고 윈도우 워커 노드의 AD 컴퓨터 계정을 사용하여 Active Directory에 대해 인증하고 파드에 사용할 gMSA ID를 검색합니다.

+

도메인 조인 접근 방식에서는 기존 Active Directory GPO를 사용하여 Windows 워커 노드를 쉽게 관리하고 강화할 수 있습니다. 하지만 이 경우 노드 시작 시 추가 재부팅이 필요하고 쿠버네티스 클러스터가 노드를 종료한 후 Active Directory Garage Cleaning이 필요하기 때문에 쿠버네티스 클러스터에서 윈도우 워커 노드를 조인하는 동안 추가적인 운영 오버헤드와 지연이 발생합니다.

+

다음 블로그 게시물에서는 도메인에 가입된 윈도우 워커 노드 접근 방식을 구현하는 방법을 단계별로 자세히 설명합니다.

+

아마존 EKS 윈도우 파드에서의 윈도우 인증

+

2 - 도메인이 없는 윈도우 워커 노드

+

이 설정에서는 윈도우 워커 노드가 Active Directory 도메인에 연결되지 않으며 "휴대용" ID (사용자/암호) 를 사용하여 Active Directory에 대해 인증하고 파드와 함께 사용할 gMSA ID를 검색합니다.

+

+

이동식 자격 증명은 Active Directory 사용자입니다; 자격 증명 (사용자/암호)은 AWS Secrets Manager 또는 AWS System Manager Parameter Store에 저장되며, ccg_plugin라는 AWS에서 개발한 플러그인을 사용하여 AWS Secrets Manager 또는 AWS System Manager Parameter Store에서 이 자격 증명을 검색하고 이를 컨테이너에 전달하여 gMSA ID를 검색하고 파드에서 사용할 수 있도록 합니다.

+

도메인이 없는 이 접근 방식을 사용하면 gMSA를 사용할 때 윈도우 워커 노드를 시작할 때 Active Directory 상호 작용이 전혀 발생하지 않고 Active Directory 관리자의 운영 오버헤드가 줄어드는 이점을 얻을 수 있습니다.

+

다음 블로그 게시물에서는 도메인이 없는 윈도우 워커 노드 접근 방식을 구현하는 방법을 단계별로 자세히 설명합니다.

+

아마존 EKS 윈도우 파드를 위한 도메인리스 윈도우 인증

+

중요 참고 사항

+

파드가 gMSA 계정을 사용할 수 있음에도 불구하고 Windows 인증을 지원하도록 애플리케이션 또는 서비스도 적절히 설정해야 합니다. 예를 들어 Windows 인증을 지원하도록 Microsoft IIS를 설정하려면 dockerfile을 통해 준비해야 합니다.

+
RUN Install-WindowsFeature -Name Web-Windows-Auth -IncludeAllSubFeature
+RUN Import-Module WebAdministration; Set-ItemProperty 'IIS:\AppPools\SiteName' -name processModel.identityType -value 2
+RUN Import-Module WebAdministration; Set-WebConfigurationProperty -Filter '/system.webServer/security/authentication/anonymousAuthentication' -Name Enabled -Value False -PSPath 'IIS:\' -Location 'SiteName'
+RUN Import-Module WebAdministration; Set-WebConfigurationProperty -Filter '/system.webServer/security/authentication/windowsAuthentication' -Name Enabled -Value True -PSPath 'IIS:\' -Location 'SiteName'
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/hardening/index.html b/ko/windows/docs/hardening/index.html new file mode 100644 index 000000000..b866faa22 --- /dev/null +++ b/ko/windows/docs/hardening/index.html @@ -0,0 +1,2252 @@ + + + + + + + + + + + + + + + + + + + + + + + 윈도우 서버 하드닝 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

윈도우 워커 노드 하드닝

+

OS 하드닝은 OS 설정, 패치 적용 및 불필요한 소프트웨어 패키지 제거의 조합으로, 시스템을 잠그고 공격 표면을 줄이는 것을 목표로 합니다. 회사에서 요구하는 강화 구성을 사용하여 EKS 최적화 윈도우 AMI를 직접 준비하는 것이 가장 좋습니다.

+

AWS는 최신 윈도우 서버 보안 패치가 포함된 새로운 EKS 최적화 윈도우 AMI를 매달 제공합니다. 하지만 자체 관리형 노드 그룹을 사용하거나 관리형 노드 그룹을 사용하는 경우와 관계없이 필요한 OS 구성을 적용하여 AMI를 강화하는 것은 여전히 사용자의 책임입니다.

+

Microsoft는 보안 정책 요구 사항에 따라 보안을 강화하는 데 도움이 되는 Microsoft 보안 규정 준수 툴킷보안 기준과 같은 다양한 도구를 제공합니다. CIS 벤치마크도 사용할 수 있으며 프로덕션 환경을 위해 Amazon EKS에 최적화된 윈도우 AMI를 기반으로 구현해야 합니다.

+

윈도우 Server Core를 통한 공격 대상 감소

+

윈도우 Server Core는 EKS 최적화 윈도우 AMI 의 일부로 사용할 수 있는 최소 설치 옵션입니다. 윈도우 Server Core를 배포하면 몇 가지 이점이 있습니다. 먼저, 데스크톱 환경을 지원하는 윈도우 서버에서는 10GB에 비해 Server Core에서는 6GB라는 디스크 사용량이 비교적 작습니다.둘째, 코드베이스가 작기 때문에 공격 대상 영역이 더 작습니다.

+

AWS는 Amazon EKS 지원 버전과 상관없이 최신 Microsoft 보안 패치가 포함된 새로운 Amazon EKS 최적화 윈도우 AMI를 매달 고객에게 제공합니다. 윈도우 워커 노드를 최신 Amazon EKS에 최적화된 AMI를 기반으로 하는 새 노드로 교체하는 것이 가장 좋습니다. 업데이트나 노드 교체 없이 45일 이상 실행되는 모든 노드는 보안 모범 사례를 준수한다고 보기 힘듭니다.

+

RDP 연결 최소화

+

RDP(Remote Desktop Protocol, 원격 데스크톱 프로토콜)는 사용자가 네트워크를 통해 다른 윈도우 컴퓨터에 연결할 수 있는 그래픽 인터페이스를 제공하기 위해 Microsoft에서 개발한 연결 프로토콜입니다.

+

모범사례로서 윈도우 워커 노드를 변경할 수 없는 것처럼 처리하는 것이 가장 좋습니다. 즉, 관리 연결도, 업데이트도 필요 없고 문제 해결도 필요 없습니다.모든 수정 및 업데이트는 새 사용자 지정 AMI로 구현하고 Auto Scaling 그룹을 업데이트하는 것으로 대체해야 합니다.윈도우 서버 및 컨테이너 패치 적용Amazon EKS 최적화 윈도우 AMI 관리를 참조하십시오.

+

아래 예와 같이 ssh 속성에 false 값을 전달하여 배포 중에 윈도우 노드에서 RDP 연결을 비활성화합니다.

+
nodeGroups:
+- name: windows-ng
+  instanceType: c5.xlarge
+  minSize: 1
+  volumeSize: 50
+  amiFamily: WindowsServer2019CoreContainer
+  ssh:
+    allow: false
+
+

윈도우 노드에 대한 액세스가 필요한 경우 AWS System Manager Session Manager를 사용하여 AWS 콘솔 및 SSM 에이전트를 통해 안전한 PowerShell 세션을 연결합니다. 솔루션을 구현하는 방법을 보려면 AWS System Manager Session Manager를 사용하여 윈도우 인스턴스에 안전하게 액세스하기를 참조하세요.

+

System Manager Session Manager를 사용하려면 윈도우 노드에 추가 IAM 정책을 적용해야 합니다. 아래는 eksctl 클러스터 매니페스트에 AmazonSSMManagedInstanceCore가 지정된 예입니다:

+
 nodeGroups:
+- name: windows-ng
+  instanceType: c5.xlarge
+  minSize: 1
+  volumeSize: 50
+  amiFamily: WindowsServer2019CoreContainer
+  ssh:
+    allow: false
+  iam:
+    attachPolicyARNs:
+      - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
+      - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
+      - arn:aws:iam::aws:policy/ElasticLoadBalancingFullAccess
+      - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
+      - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
+
+

Amazon Inspector

+
+

Amazon Inspector는 AWS에 배포된 애플리케이션의 보안 및 규정 준수를 개선하는 데 도움이 되는 자동 보안 평가 서비스입니다. Amazon Inspector는 애플리케이션의 노출, 취약성 및 모범 사례와의 편차를 자동으로 평가합니다. 평가를 수행한 후 Amazon Inspector는 심각도 수준에 따라 우선 순위가 지정된 보안 조사 결과의 세부 목록을 생성합니다. 이런 결과는 직접 검토하거나 Amazon Inspector 콘솔 또는 API를 통해 제공되는 세부 평가 보고서의 일부로 검토할 수 있습니다.

+
+

Amazon Inspector를 사용하여 윈도우 워커 노드에서 CIS 벤치마크 평가를 실행할 수 있으며, 다음 작업을 수행하여 윈도우 Server Core에 설치할 수 있습니다.

+
    +
  1. 다음 .exe 파일을 다운로드합니다. +https://inspector-agent.amazonaws.com/windows/installer/latest/AWSAgentInstall.exe
  2. +
  3. 에이전트를 윈도우 워커 노드로 전송합니다.
  4. +
  5. PowerShell에서 다음 명령을 실행하여 아마존 인스펙터 에이전트를 설치합니다: .\ AWSAgentInstall.exe /install
  6. +
+

아래는 첫 실행 후의 출력입니다. 보시다시피 CVE 데이터베이스를 기반으로 검색 결과를 생성했습니다. 이를 사용하여 워커 노드를 강화하거나 강화된 구성을 기반으로 AMI를 생성할 수 있습니다.

+

+

Amazon Inspector 에이전트 설치, CIS 벤치마크 평가 설정 및 보고서 생성 방법을 포함하여 Amazon Inspector에 대한 자세한 내용은 Amazon Inspector를 통한 윈도우 워크로드의 보안 및 규정 준수 개선 비디오를 시청하십시오.

+

Amazon GuardDuty

+
+

Amazon GuardDuty는 악의적인 활동 및 무단 행동을 지속적으로 모니터링하여 AWS 계정, 워크로드 및 Amazon S3에 저장된 데이터를 보호하는 위협 탐지 서비스입니다. 클라우드를 사용하면 계정 및 네트워크 활동의 수집 및 집계가 단순화되지만 보안 팀이 이벤트 로그 데이터를 지속적으로 분석하여 잠재적 위협을 찾아내려면 시간이 많이 걸릴 수 있습니다.

+
+

Amazon GuardDuty를 사용하면 RDP 무차별 대입 공격 및 포트 프로브 공격과 같은 윈도우 워커 노드에 대한 악의적인 활동을 가시화할 수 있습니다.

+

Amazon GuardDuty를 사용한 윈도우 워크로드의 위협 탐지 비디오를 시청하여 최적화된 EKS 윈도우 AMI에서 CIS 벤치마크를 구현하고 실행하는 방법을 알아보십시오.

+

윈도우 Amazon EC2의 보안

+

모든 계층에서 보안 제어를 구현하려면 Amazon EC2 윈도우 인스턴스의 보안 모범 사례를 읽어보십시오.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/images/index.html b/ko/windows/docs/images/index.html new file mode 100644 index 000000000..7dafb11ef --- /dev/null +++ b/ko/windows/docs/images/index.html @@ -0,0 +1,2078 @@ + + + + + + + + + + + + + + + + + + + + + + + 윈도우 이미지 스캔 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

컨테이너 이미지 스캔

+

이미지 스캔은 광범위한 운영체제 취약성을 검사하여 애플리케이션 컨테이너 이미지의 보안을 개선하는 데 도움이 되는 자동화된 취약성 평가 기능입니다.

+

현재 Amazon Elastic Container Registry (ECR)는 Linux 컨테이너 이미지만 스캔하여 취약점을 찾아낼 수 있습니다. 하지만 윈도우 컨테이너 이미지 스캔을 위해 기존 CI/CD 파이프라인과 통합할 수 있는 타사 도구가 있습니다.

+ +

이런 솔루션을 Amazon Elastic Container Registry (ECR) 와 통합하는 방법에 대해 자세히 알아보려면 다음을 확인하십시오:

+ + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/licensing/index.html b/ko/windows/docs/licensing/index.html new file mode 100644 index 000000000..c8eff876a --- /dev/null +++ b/ko/windows/docs/licensing/index.html @@ -0,0 +1,2163 @@ + + + + + + + + + + + + + + + + + + + + + + + 윈도우 버전 및 라이선스 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

윈도우 서버 버전 및 라이선스

+

윈도우 서버 버전

+

Amazon EKS 최적화 윈도우 AMI는 장기 서비스 채널 (LTSC)의 윈도우 서버 2019 및 2022 데이터센터 에디션을 기반으로 합니다. 데이터센터 버전에는 워커 노드에서 실행되는 컨테이너 수에 제한이 없습니다. 자세한 내용은 다음 링크를 참조하십시오: https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/faq

+

장기 서비스 채널 (LTSC)

+

이전에 "장기 서비스 분기"라고 불렸던 이 모델은 이미 잘 알고 있는 릴리스 모델로, 2-3년마다 윈도우 서버 새 메이저 버전이 릴리스됩니다. 사용자는 5년간의 일반 지원과 5년의 추가 지원을 받을 수 있습니다.

+

라이선싱

+

Windows Server 기반 AMI를 사용하여 Amazon EC2 인스턴스를 시작할 때 Amazon은 라이선스 비용과 라이선스 규정 준수를 부담합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/logging/index.html b/ko/windows/docs/logging/index.html new file mode 100644 index 000000000..68db8ec56 --- /dev/null +++ b/ko/windows/docs/logging/index.html @@ -0,0 +1,2151 @@ + + + + + + + + + + + + + + + + + + + + + + + 로깅 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

로깅

+

컨테이너화된 애플리케이션은 일반적으로 애플리케이션 로그를 STDOUT으로 전달합니다. 컨테이너 런타임은 이런 로그를 트랩하여 특정 작업(일반적으로 파일에 쓰기)을 수행합니다. 이런 파일이 저장되는 위치는 컨테이너 런타임 및 구성에 따라 다릅니다.

+

윈도우 파드와의 근본적인 차이점 중 하나는 STDOUT을 생성하지 않는다는 것입니다. LogMonitor를 실행하여 실행 중인 윈도우 컨테이너에서 ETW (윈도우용 이벤트 추적), 윈도우 이벤트 로그 및 기타 애플리케이션별 로그를 검색하고 형식이 지정된 로그 출력을 STDOUT으로 전송할 수 있습니다. 그런 다음 플루언트비트 또는 fluentd를 사용하여 Amazon CloudWatch와 같은 원하는 목적지로 이런 로그를 스트리밍할 수 있습니다.

+

로그 수집 메커니즘은 쿠버네티스 파드에서 STDOUT/STDERR 로그를 검색한다. 데몬셋은 컨테이너에서 로그를 수집하는 일반적인 방법입니다. 애플리케이션과 독립적으로 로그 라우팅/필터링/강화를 관리할 수 있습니다. fluentd 데몬셋을 사용하여 이런 로그와 기타 애플리케이션 생성 로그를 원하는 로그 수집기로 스트리밍할 수 있습니다.

+

윈도우 워크로드에서 CloudWatch로의 로그 스트리밍에 대한 자세한 내용은 AWS 블로그를 참조하세요.

+

로깅 권장 사항

+

쿠버네티스에서 윈도우 워크로드를 운영할 때 일반적인 로깅 모범 사례는 크게 다르지 않습니다.

+
    +
  • 항상 구조화된 로그 항목 (JSON/SYSLOG)을 기록하면 이런 구조화된 형식에 대해 미리 작성된 파서가 많기 때문에 로그 항목을 더 쉽게 처리할 수 있습니다.
  • +
  • 로그 중앙 집중화 - 전용 로깅 컨테이너를 사용하여 모든 컨테이너에서 로그 메시지를 수집하고 목적지로 전달할 수 있습니다.
  • +
  • 디버깅할 때를 제외하고는 로그 상세 정보를 줄이십시오. 상세 정보는 로깅 인프라에 많은 부담을 주고 노이즈로 인해 중요한 이벤트가 손실될 수 있습니다.
  • +
  • +

    추적을 위해 항상 애플리케이션 정보트랜잭션/요청 ID와 함께 기록하십시오. 쿠버네티스 오브젝트에는 애플리케이션 이름이 포함되지 않으므로, 예를 들어 로그를 디버깅할 때 파드 이름 windows-twryrqyw는 아무런 의미가 없을 수 있습니다. 이렇게 하면 추적성이 향상되고 집계된 로그로 애플리케이션 문제를 해결하는 데 도움이 됩니다.

    +

    이런 트랜잭션/상관 관계 ID를 생성하는 방법은 프로그래밍 구조에 따라 다릅니다. 하지만 매우 일반적인 패턴은 다음과 같이 MDC(매핑된 진단 컨텍스트)를 사용하여 들어오는 모든 요청에 고유한 트랜잭션/상관 관계 ID를 삽입할 수 있는 로깅 Aspect/Interceptor를 사용하는 것입니다.

    +
  • +
+
import org.slf4j.MDC;
+import java.util.UUID;
+Class LoggingAspect { //interceptor
+
+    @Before(value = "execution(* *.*(..))")
+    func before(...) {
+        transactionId = generateTransactionId();
+        MDC.put(CORRELATION_ID, transactionId);
+    }
+
+    func generateTransactionId() {
+        return UUID.randomUUID().toString();
+    }
+}
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/monitoring/index.html b/ko/windows/docs/monitoring/index.html new file mode 100644 index 000000000..a7c956496 --- /dev/null +++ b/ko/windows/docs/monitoring/index.html @@ -0,0 +1,2165 @@ + + + + + + + + + + + + + + + + + + + + + + + 윈도우 컨테이너 모니터링 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

모니터링

+

프로메테우스(Prometheus)는, CNCF 졸업 프로젝트로서 쿠버네티스에 기본적으로 통합되는 가장 인기 있는 모니터링 시스템입니다. 프로메테우스는 컨테이너, 파드, 노드 및 클러스터와 관련된 메트릭을 수집합니다. 또한 프로메테우스는 AlertsManager를 활용합니다. AlertsManager를 사용하면 클러스터에서 문제가 발생할 경우 경고를 프로그래밍하여 경고할 수 있습니다. 프로메테우스는 지표 데이터를 지표 이름 및 키/값 쌍으로 식별되는 시계열 데이터로 저장합니다. 프로메테우스에는 프로메테우스 쿼리 언어의 줄임말인 PromQL이라는 언어를 사용하여 쿼리하는 방법이 포함되어 있습니다.

+

프로메테우스 메트릭 수집의 상위 수준 아키텍처는 다음과 같습니다.

+

프로메테우스 메트릭 컬렉션

+

프로메테우스는 풀 메커니즘을 사용하고 익스포터(Exporter)를 사용하여 타겟에서 메트릭을 스크랩하고 kube state metrics를 사용하여 쿠버네티스 API에서 메트릭을 스크랩합니다. 즉, 애플리케이션과 서비스는 프로메테우스 형식의 메트릭이 포함된 HTTP(S) 엔드포인트를 노출해야 합니다. 그러면 프로메테우스는 구성에 따라 주기적으로 이런 HTTP(S) 엔드포인트에서 메트릭을 가져옵니다.

+

익스포터를 사용하면 타사 지표를 프로메테우스 형식의 지표로 사용할 수 있습니다. 프로메테우스 익스포터는 일반적으로 각 노드에 배포됩니다. 익스포터 전체 목록은 프로메테우스 익스포터 문서를 참조하십시오. node exporter는 Linux 노드 용 호스트 하드웨어 및 OS 메트릭을 내보내는 데 적합하지만 윈도우 노드에서는 작동하지 않습니다.

+

윈도우 노드가 있는 혼합 노드 EKS 클러스터에서 안정적인 프로메테우스 헬름 차트를 사용하면 윈도우 노드에 장애가 발생한 파드가 표시됩니다. 이 익스포터는 윈도우용이 아니기 때문입니다. 윈도우 작업자 풀을 별도로 처리하고 대신 윈도우 워커 노드 그룹에 윈도우 익스포터를 설치해야 합니다.

+

윈도우 노드에 대해 프로메테우스 모니터링을 설정하려면 윈도우 서버 자체에 WMI 익스포터를 다운로드하여 설치한 다음 프로메테우스 구성 파일의 스크랩 구성 내에서 대상을 설정해야 합니다. +릴리스 페이지는 사용 가능한 모든.msi 설치 프로그램을 각 기능 세트 및 버그 수정과 함께 제공합니다. 설치 프로그램은 windows_exporter를 윈도우 서비스로 설정하고 윈도우 방화벽에서 예외를 생성합니다. 파라미터 없이 설치 프로그램을 실행하는 경우 익스포터는 활성화된 컬렉터, 포트 등에 대한 기본 설정으로 실행됩니다.

+

이 가이드의 스케줄링 모범 사례 섹션은 테인트/톨러레이션 또는 RuntimeClass를 사용하여 노드 익스포터를 Linux 노드에만 선택적으로 배포하는 방법을 제안하는 반면, 윈도우 익스포터는 노드를 부트스트랩하거나 원하는 구성 관리 도구(예: chef, Ansible, SSM 등)를 사용하여 노드 익스포터를 설치하도록 제안합니다.

+

참고로, 노드 익스포터가 데몬셋으로 설치되는 Linux 노드와 달리 윈도우 노드에서는 WMI 익스포터가 호스트 자체에 설치됩니다. 익스포터는 CPU 사용량, 메모리 및 디스크 I/O 사용량과 같은 메트릭을 내보내고 IIS 사이트 및 응용 프로그램, 네트워크 인터페이스 및 서비스를 모니터링하는 데에도 사용할 수 있습니다.

+

windows_exporter는 기본적으로 활성화된 컬렉터의 모든 메트릭을 노출합니다. 오류를 방지하기 위해 지표를 수집하는 데 권장되는 방법입니다. 하지만 고급 사용을 위해 windows_exporter에 선택적 수집기 목록을 전달하여 메트릭을 필터링할 수 있습니다.프로메테우스 구성의 collect[] 파라미터를 사용하면 이 작업을 수행할 수 있습니다.

+

윈도우의 기본 설치 단계에는 부트스트랩 프로세스 중에 필터링하려는 컬렉터와 같은 인수를 사용하여 익스포터를 서비스로 다운로드하고 시작하는 작업이 포함됩니다.

+
> Powershell Invoke-WebRequest https://github.com/prometheus-community/windows_exporter/releases/download/v0.13.0/windows_exporter-0.13.0-amd64.msi -OutFile <DOWNLOADPATH> 
+
+> msiexec /i <DOWNLOADPATH> ENABLED_COLLECTORS="cpu,cs,logical_disk,net,os,system,container,memory"
+
+

기본적으로 메트릭은 포트 9182의 /metrics 엔드포인트에서 스크랩할 수 있습니다. +이제 프로메테우스는 다음 scrape_config를 프로메테우스 구성에 추가하여 메트릭을 사용할 수 있습니다.

+
scrape_configs:
+    - job_name: "prometheus"
+      static_configs: 
+        - targets: ['localhost:9090']
+    ...
+    - job_name: "wmi_exporter"
+      scrape_interval: 10s
+      static_configs: 
+        - targets: ['<windows-node1-ip>:9182', '<windows-node2-ip>:9182', ...]
+
+

프로메테우스 구성을 다음과 같이 다시 로드합니다.

+
> ps aux | grep prometheus
+> kill HUP <PID> 
+
+

대상을 추가하는 더 좋고 권장되는 방법은 ServiceMonitor라는 사용자 지정 리소스 정의를 사용하는 것입니다. 이 정의는 ServiceMonitor 개체에 대한 정의와 우리가 정의한 ServiceMonitor를 활성화하고 필요한 프로메테우스 구성을 자동으로 빌드하는 컨트롤러를 제공하는 Prometheus 운영자의 일부로 제공됩니다.

+

쿠버네티스 서비스 그룹을 모니터링하는 방법을 선언적으로 지정하는 ServiceMonitor는 쿠버네티스 내에서 메트릭을 스크랩하려는 애플리케이션을 정의하는 데 사용됩니다.ServiceMonitor 내에서 운영자가 쿠버네티스 서비스를 식별하는 데 사용할 수 있는 쿠버네티스 레이블을 지정합니다. 쿠버네티스 서비스는 쿠버네티스 서비스를 식별하고, 쿠버네티스 서비스는 다시 우리가 모니터링하고자 하는 파드를 식별합니다.

+

ServiceMonitor를 활용하려면 특정 윈도우 대상을 가리키는 엔드포인트 객체, 윈도우 노드용 헤드리스 서비스 및 ServiceMontor를 생성해야 합니다.

+
apiVersion: v1
+kind: Endpoints
+metadata:
+  labels:
+    k8s-app: wmiexporter
+  name: wmiexporter
+  namespace: kube-system
+subsets:
+- addresses:
+  - ip: NODE-ONE-IP
+    targetRef:
+      kind: Node
+      name: NODE-ONE-NAME
+  - ip: NODE-TWO-IP
+    targetRef:
+      kind: Node
+      name: NODE-TWO-NAME
+  - ip: NODE-THREE-IP
+    targetRef:
+      kind: Node
+      name: NODE-THREE-NAME
+  ports:
+  - name: http-metrics
+    port: 9182
+    protocol: TCP
+
+---
+apiVersion: v1
+kind: Service ##Headless Service
+metadata:
+  labels:
+    k8s-app: wmiexporter
+  name: wmiexporter
+  namespace: kube-system
+spec:
+  clusterIP: None
+  ports:
+  - name: http-metrics
+    port: 9182
+    protocol: TCP
+    targetPort: 9182
+  sessionAffinity: None
+  type: ClusterIP
+
+---
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor ##Custom ServiceMonitor Object
+metadata:
+  labels:
+    k8s-app: wmiexporter
+  name: wmiexporter
+  namespace: monitoring
+spec:
+  endpoints:
+  - interval: 30s
+    port: http-metrics
+  jobLabel: k8s-app
+  namespaceSelector:
+    matchNames:
+    - kube-system
+  selector:
+    matchLabels:
+      k8s-app: wmiexporter
+
+

운영자 및 ServiceMonitor 사용에 대한 자세한 내용은 공식 오퍼레이터 설명서를 참조하십시오. 참고로 프로메테우스는 다양한 서비스 디스커버리 옵션을 사용한 동적 타겟 디스커버리를 지원합니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/networking/index.html b/ko/windows/docs/networking/index.html new file mode 100644 index 000000000..296835444 --- /dev/null +++ b/ko/windows/docs/networking/index.html @@ -0,0 +1,2230 @@ + + + + + + + + + + + + + + + + + + + + + + + 윈도우 네트워크 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

윈도우 네트워크

+

윈도우 컨테이너 네트워크 개요

+

윈도우 컨테이너는 리눅스 컨테이너와 근본적으로 다릅니다. 리눅스 컨테이너는 네임스페이스, 유니온 파일 시스템 그리고 cgroup 등의 구성 요소로 이루어져 있습니다. 윈도우에서는 이런 구성 요소를 Host Compute Service (HCS)에 의해 도코에서 추상화 됩니다. HCS는 윈도우에서 컨테이너 구현을 위한 API 레이어 역할을 합니다. 또한 윈도우 컨테이너는 노드에서 네트워크 토폴로지를 정의하는데 Host Network Service(HNS)를 활용합니다.

+

+

네트워킹 관점에서 보면 HCS와 HNS는 윈도우 컨테이너가 가상 머신처럼 작동하게 만듭니다. 예를 들어, 위 다이어그램에 표시된 것 처럼 각 컨테이너에는 Hyper-V virtual switch(vSwitch)에 연결된 가상 네트워크 어댑터(vNIC)가 있습니다.

+

IP 주소 관리

+

Amazon EKS의 노드는 Elastic Network Interface(ENI)를 사용하여 AWS VPC 네트워크에 연결합니다. 현재, 윈도우 워커 노드당 단일 ENI만 지원 됩니다. 윈도우 노드의 IP 주소 관리는 컨트롤 플레인에서 실행되는 VPC Resource Controller에서 수행됩니다. 윈도우 노드의 IP 주소 관리 워크플로에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

+

윈도우 워커 노드가 지원할 수 있는 파드 수는 노드의 크기와 사용 가능한 IPv4 개수에 따라 달라집니다. 노드에서 사용 가능한 IPv4 개수는 아래와 같이 계산할 수 있습니다: +- 기본적으로 ENI에는 보조(Secondary) IPv4 주소만 할당 됩니다. 다음과 같은 경우 - +

파드에 사용 가능한 총 IPv4 개수 = 인터페이스당 지원되는 IPv4 개수 - 1
+
+ 하나의 IPv4 주소가 ENI의 primary 주소로 사용되므로 파드에 할당할 수 없으므로 총 개수에서 1을 뺍니다. +- 만약 prefix delegation feature을 활성화하여 파드의 밀집도를 높이도록 구성된 경우에는 - +
파드에 사용 가능한 총 IPv4 개수 = (인터페이스당 지원되는 IPv4 개수 - 1) * 16
+
+ VPC Resource Controller는 보조 IPv4 주소를 할당하는 대신 /28 prefixes를 할당하므로 사용 가능한 전체 IPv4 개수가 16배 증가합니다.

+

위 공식을 사용하면 아래와 같이 m5.large 인스턴스의 최대 파드 수를 계산할 수 있습니다- +- 기본적으로 보조 IP 모드에서 실행하는 경우 - +

인터페이스당 10개 보조 IPv4 개수 - 1 = 9개 사용 가능한 IPv4 개수
+
+- prefix delegation를 사용하는 경우 - +
(인터페이스당 10개 보조 IPv4 개수 - 1) * 16 = 144개 사용 가능한 IPv4 개수
+

+

인스턴스 유형에 따른 사용 가능한 IP 개수에 대한 자세한 내용은 IP addresses per network interface per instance type를 참조 바랍니다.

+
+

또 다른 주요 고려 사항은 네트워크 트래픽의 흐름입니다. 윈도우를 사용하면 100개가 넘는 서비스가 있는 노드에서 포트가 고갈 될 위험이 있습니다. 이 상태가 발생하면 노드에서 다음 메시지와 함께 오류가 발생하기 시작합니다:

+

"Policy creation failed: hcnCreateLoadBalancer failed in Win32: The specified port already exists."

+

이 문제를 해결하기 위해 Direct Server Return(DSR)을 활용합니다. DSR은 비대칭 네트워크 부하 분산을 구현한 것입니다. 즉, 요청 트래픽과 응답 트래픽은 서로 다른 네트워크 경로를 사용합니다. 이 기능은 Pod 간의 통신 속도를 높이고 포트 고갈 위험을 줄입니다. 따라서 윈도우 노드에서 DSR을 활성화 하는 것이 좋습니다.

+

DSR은 Windows Server SAC EKS Optimized AMIs에서 기본적으로 활성화 됩니다. Windows Server 2019 LTSC EKS Optimized AMI의 경우, 아래 스크립트를 사용하고 eksctl 노드그룹의 amiFamily로 Windows Server 2019 Full 또는 Core를 사용하여 인스턴스 프로비저닝 중에 이를 활성화해야 합니다. 자세한 내용은 eksctl custom AMI를 참조 바랍니다.

+

nodeGroups:
+- name: windows-ng
+  instanceType: c5.xlarge
+  minSize: 1
+  volumeSize: 50
+  amiFamily: WindowsServer2019CoreContainer
+  ssh:
+    allow: false
+
+윈도우 서버 2019 이상에서 DSR을 활용하려면 인스턴스 시작 시 다음과 같은 kube-proxy 플래그를 지정해야 합니다. 자체 관리형 노드 그룹 시작 템플릿과 관련된 사용자 데이터 스크립트를 조정하여 이 작업을 수행할 수 있습니다.

+
<powershell>
+[string]$EKSBinDir = "$env:ProgramFiles\Amazon\EKS"
+[string]$EKSBootstrapScriptName = 'Start-EKSBootstrap.ps1'
+[string]$EKSBootstrapScriptFile = "$EKSBinDir\$EKSBootstrapScriptName"
+(Get-Content $EKSBootstrapScriptFile).replace('"--proxy-mode=kernelspace",', '"--proxy-mode=kernelspace", "--feature-gates WinDSR=true", "--enable-dsr",') | Set-Content $EKSBootstrapScriptFile 
+& $EKSBootstrapScriptFile -EKSClusterName "eks-windows" -APIServerEndpoint "https://<REPLACE-EKS-CLUSTER-CONFIG-API-SERVER>" -Base64ClusterCA "<REPLACE-EKSCLUSTER-CONFIG-DETAILS-CA>" -DNSClusterIP "172.20.0.10" -KubeletExtraArgs "--node-labels=alpha.eksctl.io/cluster-name=eks-windows,alpha.eksctl.io/nodegroup-name=windows-ng-ltsc2019 --register-with-taints=" 3>&1 4>&1 5>&1 6>&1
+</powershell>
+
+

DSR 활성화 절차에 대한 자세한 내용은 Microsoft 네트워크 블로그AWS 윈도우 컨테이너 워크샵에서 확인할 수 있습니다.

+

+

이전 버전의 윈도우를 사용하면 DSR을 지원하지 않으므로 포트가 고갈될 위험이 높아집니다.

+

Container Network Interface (CNI) 옵션

+

AWS VPC CNI는 윈도우 및 리눅스 워커 노드를 위한 사실상 표준(de facto)의 CNI 플러그인입니다. AWS VPC CNI는 많은 고객의 요구를 충족하지만, IP 주소 고갈을 방지하기 위해 오버레이 네트워크와 같은 대안을 고려해야 하는 경우가 있을 수 있습니다. 이런 경우 AWS VPC CNI 대신 Calico CNI를 사용할 수 있습니다. Project CalicoTigera에서 개발한 오픈 소스 소프트웨어로, EKS와 함께 작동하는 CNI를 포함하고 있습니다. EKS에 Calico CNI를 설치하는 방법은 Project Calico EKS 설치 페이지에서 확인 할 수 있습니다.

+

네트워크 정책

+

Kubernetes 클러스터의 파드 간 개방형 통신의 기본 모드에서 네트워크 정책을 기반으로 액세스를 제한하는 것으로 변경하는 것이 모범 사례로 간주됩니다. 오픈소스 Project Calico는 Linux와 Windows 노드 모두에서 작동하는 네트워크 정책을 강력하게 지원합니다. 이 기능은 별개이며 Calico CNI 사용에 의존하지 않습니다. 따라서 Calico를 설치하여 네트워크 정책 관리에 사용하는 것이 좋습니다.

+

EKS에 Calico를 설치하는 방법에 대한 내용은 Amazon EKS Calico 설치에서 확인할 수 있습니다.

+

또한, 보안 모범 사례 - 네트워크 섹션의 내용은 Windows 워커 노드가 있는 EKS 클러스터에도 동일하게 적용되지만, "파드용 보안 그룹"과 같은 일부 기능은 현재 Windows에서 지원되지 않습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/oom/index.html b/ko/windows/docs/oom/index.html new file mode 100644 index 000000000..30726bd39 --- /dev/null +++ b/ko/windows/docs/oom/index.html @@ -0,0 +1,2183 @@ + + + + + + + + + + + + + + + + + + + + + + + 메모리 및 시스템 관리 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+ +
+ + + +
+
+ + + + + + + +

OOM(Out of Memory) 에러 방지

+

윈도우에는 Linux처럼 OOM 프로세스 킬러가 없습니다. 윈도우는 항상 모든 사용자 모드(user-mode) 메모리 할당을 가상으로 취급하며 pagefiles 가 필수입니다. 결과적으로 윈도우는 Linux와 같은 방식으로 OOM 상태에 도달하지 않습니다. 프로세스는 OOM로 종료되는 대신 디스크로 페이징됩니다. 메모리가 과도하게 프로비저닝되고 물리적 메모리가 모두 소진되면 페이징으로 인해 성능이 저하될 수 있습니다.

+

시스템 및 kubelet 메모리 예약

+

--kubelet-reserve 는 kubelet, 컨테이너 런타임 등과 같은 쿠버네티스 시스템 데몬에 대한 리소스 예약을 캡처하고 --system-reserve 는 sshd, udev 등과 같은 OS 시스템 데몬에 대한 리소스 예약을 캡처하고 설정하는 리눅스와는 다릅니다. 윈도우에서 이런 플래그는 kubelet 또는 노드에서 실행되는 프로세스에 대한 메모리 제한을 캡처하거나 설정하지 않습니다.

+

하지만 이런 플래그를 조합하여 NodeAllocatable을 관리하여 파드 매니페스트에 메모리 리소스 한도(memory resource limit)가 있는 노드의 용량을 줄여 파드별로 메모리 할당을 제어할 수 있습니다. 이 전략을 사용하면 메모리 할당을 더 잘 제어할 수 있을 뿐만 아니라 윈도우 노드의 OOM을 최소화하는 메커니즘도 확보할 수 있습니다.

+

윈도우 노드에서는 OS 및 프로세스에 사용할 최소 2GB의 메모리를 예약하는 것이 가장 좋습니다. --kubelet-reserve 또는 --system-reserve 파라미터를 활용하여 NodeAllocatable을 줄일 수 있습니다.

+

Amazon EKS 자체 관리형 윈도우 노드 설명에 따라 CloudFormation 템플릿을 사용하여 kubelet 구성을 커스터마이제이션 하여 새 윈도우 노드 그룹으로 시작할 수 있습니다. CloudFormation에는 BootstraArguments라는 요소가 있는데, 이는 KubeletExtraArgs와 동일합니다. 다음 플래그 및 값과 함께 사용할 수 있습니다.

+
--kube-reserved memory=0.5Gi,ephemeral-storage=1Gi --system-reserved memory=1.5Gi,ephemeral-storage=1Gi --eviction-hard memory.available<200Mi,nodefs.available<10%"
+
+

eksctl을 배포 도구로 사용하는 경우, 다음 https://eksctl.io/usage/customizing-the-kubelet/ 문서를 참조하여 kublet을 커스터마이즈 할 수 있습니다.

+

윈도우 컨테이너 메모리 요구 사항

+

Microsoft 문서에 따르면 NANO용 Windows Server 베이스 이미지에는 최소 30MB가 필요한 반면 윈도우 Server Core 이미지에는 45MB가 필요합니다. 이 수치는 .NET 프레임워크, IIS 웹 서비스 및 응용 프로그램과 같은 윈도우 구성 요소를 추가함에 따라 증가합니다.

+

윈도우 컨테이너 이미지(예: 기본 이미지와 해당 응용 프로그램 계층)에 필요한 최소 메모리 양을 알고 파드 스펙에 컨테이너의 리소스 requests를 설정하는 것이 중요합니다. 또한 애플리케이션 문제 발생 시 파드가 가용 노드 메모리를 모두 사용하지 않도록 limit을 설정해야 합니다.

+

아래 예제에서, 쿠버네티스 스케줄러가 노드에 파드를 배치하려고 할 때, 파드의 requests을 통해 충분한 리소스가 있는 노드를 결정하는데 사용합니다.

+
 spec:
+  - name: iis
+    image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
+    resources:
+      limits:
+        cpu: 1
+        memory: 800Mi
+      requests:
+        cpu: .1
+        memory: 128Mi
+
+

결론

+

위와 같은 접근 방식을 사용해도 메모리 고갈 위험이 최소화되지만 발생 자체를 방지할 수는 없습니다. Amazon CloudWatch 메트릭 지표를 사용하면 메모리 소진이 발생할 경우 알림 및 해결 방법을 설정할 수 있습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/patching/index.html b/ko/windows/docs/patching/index.html new file mode 100644 index 000000000..e31fa05ca --- /dev/null +++ b/ko/windows/docs/patching/index.html @@ -0,0 +1,2173 @@ + + + + + + + + + + + + + + + + + + + + + + + 인프라 관리 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

윈도우 서버 및 컨테이너 패치

+

윈도우 서버 패치 작업은 윈도우 관리자를 위한 표준 관리 작업입니다. Amazon System Manager - Patch Manager, WSUS, System Center Configuration Manager 등과 같은 다양한 도구를 사용하여 이 작업을 수행할 수 있습니다. 하지만 Amazon EKS 클러스터의 윈도우 노드를 일반 윈도우 서버로 취급해서는 안 됩니다. 이들은 변경할 수 없는 서버로 취급되어야 합니다. 간단히 말해, 기존 노드를 업데이트하지 말고 시작 템플릿에 새로 업데이트된 AMI를 기반으로 새 노드를 시작하기만 하면 됩니다.

+

EC2 Image Builder를 사용하여 레시피를 생성하고 구성 요소를 추가하여 AMI 빌드를 자동화할 수 있습니다.

+

다음 예제는 구성 요소를 보여줍니다. 구성 요소는 AWS에서 구축한 기존 구성 요소(Amazon-managed)일 수도 있고 사용자가 생성한 구성 요소(Owned by me)일 수도 있습니다. Amazon에서 관리하는 update-windows라는 구성 요소에 주목하십시오. 이렇게 하면 EC2 Image Builder 파이프라인을 통해 AMI를 생성하기 전에 윈도우 서버가 업데이트됩니다.

+

+

EC2 Image Builder를 사용하면 Amazon 제공 퍼블릭 AMI를 기반으로 AMI를 구축하고 비즈니스 요구 사항에 맞게 커스터마이징할 수 있습니다. 그런 다음 해당 AMI를 EKS 노드 그룹에서 생성한 Auto Scaling 그룹의 시작 템플릿(Launch Template)에 연결 할 수 있습니다. 이 작업이 완료되면 기존 윈도우 노드를 종료할 수 있으며 새로 업데이트된 AMI를 기반으로 새 윈도우 노드가 시작됩니다.

+

윈도우 이미지 푸싱(Pushing)와 풀링(Pulling)

+

Amazon은 2개의 캐시된 윈도우 컨테이너 이미지를 포함하는 EKS 최적화 AMI를 제공합니다.

+
mcr.microsoft.com/windows/servercore
+mcr.microsoft.com/windows/nanoserver
+
+ +

+

캐시된 이미지는 main OS 업데이트에 따라 업데이트 됩니다. Microsoft가 윈도우 컨테이너 베이스 이미지에 직접적인 영향을 미치는 새로운 윈도우 업데이트를 출시하면 해당 업데이트는 main OS에서 일반적인 윈도우 업데이트(ordinary Windows Update)로 시작 됩니다. 환경을 최신 상태로 유지하면 노드 및 컨테이너 수준에서 보다 안전한 환경이 제공됩니다.

+

윈도우 컨테이너 이미지의 크기는 푸시/풀 수행에 영향을 미치므로 컨테이너 시작 시간(conatiner startup time)이 느려질 수 있습니다. 윈도우 컨테이너 이미지 캐싱에 방식으로 컨테이너 이미지를 캐싱하면 컨테이너 시작 대신 AMI 빌드 생성시 비용이 많이 드는 I/O 작업(파일 추출)이 발생할 수 있습니다. 따라서 필요한 모든 이미지 레이어가 AMI에서 추출되어 바로 사용할 수 있게 되므로 윈도우 컨테이너가 시작되고 트래픽 수신을 시작할 수 있는 시간이 단축됩니다. 푸시 작업 중에는 이미지를 구성하는 레이어만 저장소에 업로드됩니다.

+

다음 예제에서는 Amazon ECR에서 fluentd-windows-sac2004 이미지의 크기가 390.18MB에 불과하다는 것을 보여줍니다. 푸시 작업 중에 발생한 업로드 양입니다.

+

다음 예제에서는 Amazon ECR 리포지토리에 푸시된 fluentd Windows ltsc 이미지를 보여줍니다. ECR에 저장되는 레이어의 크기는 533.05MB입니다.

+

+

아래 docker image ls 출력에서는 fluentd v1.14-windows-ltsc2019-1의 크기가 디스크에서 6.96GB이지만, 해당 양의 데이터를 다운로드하고 추출했다는 의미는 아닙니다.

+

실제로 풀 수행시에는 compressed 533.05MB만 다운로드되어 추출됩니다.

+
REPOSITORY                                                              TAG                        IMAGE ID       CREATED         SIZE
+111122223333.dkr.ecr.us-east-1.amazonaws.com/fluentd-windows-coreltsc   latest                     721afca2c725   7 weeks ago     6.96GB
+fluent/fluentd                                                          v1.14-windows-ltsc2019-1   721afca2c725   7 weeks ago     6.96GB
+amazonaws.com/eks/pause-windows                                         latest                     6392f69ae6e7   10 months ago   255MB
+
+

size 컬럼에 이미지의 전체 크기로 6.96GB가 표시됩니다. 이에 대한 상세한 내역입니다:

+
    +
  • Windows Server Core 2019 LTSC Base image = 5.74GB
  • +
  • Fluentd Uncompressed Base Image = 6.96GB
  • +
  • Difference on disk = 1.2GB
  • +
  • Fluentd compressed final image ECR = 533.05MB
  • +
+

기본 이미지가 로컬 디스크에 이미 있으므로 디스크의 총 용량은 1.2GB가 추가됩니다. 다음에 size 컬럼에 GB 용량이 표시되더라도 너무 걱정할 필요 없습니다. 이미 70% 이상이 캐시된 컨테이너 이미지로 디스크에 저장되어 있을 것입니다.

+

참고 자료

+

EC2 Image builder 및 이미지 캐시 전략으로 윈도우 컨테이너 시작 시간 단축

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/scheduling/index.html b/ko/windows/docs/scheduling/index.html new file mode 100644 index 000000000..9d64cbf37 --- /dev/null +++ b/ko/windows/docs/scheduling/index.html @@ -0,0 +1,2206 @@ + + + + + + + + + + + + + + + + + + + + + + + 스케줄링 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

이기종 워크로드 실행¶

+

쿠버네티스는 동일한 클러스터에 리눅스 및 윈도우 노드를 혼합하여 사용할 수 있는 이기종(Heterogeneous) 클러스터를 지원합니다. 해당 클러스터 내에는 리눅스에서 실행되는 파드와 윈도우에서 실행되는 파드를 혼합하여 사용할 수 있습니다. 동일한 클러스터에서 여러 버전의 윈도우를 실행할 수도 있습니다. 하지만 이 결정을 내릴 때 고려해야 할 몇 가지 요소(아래 설명 참조)가 있습니다.

+

노드 내 파드 할당 모범 사례

+

리눅스 및 윈도우 워크로드를 각각의 OS별 노드에 유지하려면 노드 셀렉터(NodeSelector)와 테인트(Taint)/톨러레이션(Toleration)을 조합하여 사용해야 합니다. 이기종 환경에서 워크로드를 스케줄링하는 주된 목적은 기존 리눅스 워크로드와의 호환성이 깨지지 않도록 하는 것입니다.

+

특정 OS 워크로드가 적절한 컨테이너 노드에서 실행 보장

+

사용자는 노드셀렉터를 사용하여 윈도우 컨테이너를 적절한 호스트에서 스케줄링 할 수 있습니다. 현재 모든 쿠버네티스 노드에는 다음과 같은 default labels 이 있습니다:

+
kubernetes.io/os = [windows|linux]
+kubernetes.io/arch = [amd64|arm64|...]
+
+ +

파드 스펙에 "kubernetes.io/os": windows 와 같은 노드셀렉터가 포함되지 않는 경우, 파드는 윈도우 또는 리눅스 어느 호스트에서나 스케줄링 될 수 있습니다. 윈도우 컨테이너는 윈도우에서만 실행할 수 있고 리눅스 컨테이너는 리눅스에서만 실행할 수 있기 때문에 문제가 될 수 있습니다.

+

엔터프라이즈 환경에서는 리눅스 컨테이너에 대한 기존 배포가 많을 뿐만 아니라 헬름 차트와 같은 기성 구성 에코시스템(off-the-shelf configurations)을 갖는 것이 일반적입니다. 이런 상황에서는 디플로이먼트의 노드셀렉터를 변경하는 것을 주저할 수 있습니다. 대안은 테인트를 사용하는 것입니다.

+

예를 들어: --register-with-taints='os=windows:NoSchedule'

+

EKS를 사용하는 경우, eksctl은 clusterConfig를 통해 테인트를 적용하는 방법을 제공합니다:

+
NodeGroups:
+  - name: windows-ng
+    amiFamily: WindowsServer2022FullContainer
+    ...
+    labels:
+      nodeclass: windows2022
+    taints:
+      os: "windows:NoSchedule"
+
+

모든 윈도우 노드에 테인트를 추가하는 경우, 스케줄러는 테인트를 허용하지 않는 한 해당 노드에서 파드를 스케줄링하지 않습니다. 다음은 파드 매니페스트의 예시입니다:

+
nodeSelector:
+    kubernetes.io/os: windows
+tolerations:
+    - key: "os"
+      operator: "Equal"
+      value: "windows"
+      effect: "NoSchedule"
+
+

동일한 클러스터에서 여러 윈도우 빌드 처리

+

각 파드에서 사용하는 윈도우 컨테이너 베이스 이미지는 노드와 동일한 커널 빌드 버전과 일치해야 합니다. 동일한 클러스터에서 여러 윈도우 Server 빌드를 사용하려면 추가 노드 레이블인 노드셀렉터를 설정하거나 windows-build 레이블을 활용해야 합니다.

+

쿠버네티스 1.17 버전에서는 node.kubernetes.io/windows-build 라는 새로운 레이블을 자동으로 추가하여 동일한 클러스터에서 여러 윈도우 빌드의 관리를 단순화 합니다. 이전 버전을 실행 중인 경우 이 레이블을 윈도우 노드에 수동으로 추가하는 것이 좋습니다.

+

이 레이블에는 호환성을 위해 일치해야 하는 윈도우 메이저, 마이너, 그리고 빌드 번호가 반영되어 있습니다. 다음은 현재 각 윈도우 서버 버전에 사용되는 값입니다.

+

중요한 점은 윈도우 서버가 장기 서비스 채널(LTSC)를 기본 릴리스 채널로 이동하고 있다는 것입니다. 윈도우 서버 반기 채널(SAC)은 2022년 8월 9일에 사용 중지되었습니다. 윈도우 서버의 향후 SAC 릴리스는 없습니다.

+ + + + + + + + + + + + + + + + + +
Product NameBuild Number(s)
Server full 2022 LTSC10.0.20348
Server core 2019 LTSC10.0.17763
+

다음 명령을 통해 OS 빌드 버전을 확인할 수 있습니다:

+
kubectl get nodes -o wide
+
+

KERNEL-VERSION 출력은 윈도우 OS 빌드 버전을 나타냅니다.

+
NAME                          STATUS   ROLES    AGE   VERSION                INTERNAL-IP   EXTERNAL-IP     OS-IMAGE                         KERNEL-VERSION                  CONTAINER-RUNTIME
+ip-10-10-2-235.ec2.internal   Ready    <none>   23m   v1.24.7-eks-fb459a0    10.10.2.235   3.236.30.157    Windows Server 2022 Datacenter   10.0.20348.1607                 containerd://1.6.6
+ip-10-10-31-27.ec2.internal   Ready    <none>   23m   v1.24.7-eks-fb459a0    10.10.31.27   44.204.218.24   Windows Server 2019 Datacenter   10.0.17763.4131                 containerd://1.6.6
+ip-10-10-7-54.ec2.internal    Ready    <none>   31m   v1.24.11-eks-a59e1f0   10.10.7.54    3.227.8.172     Amazon Linux 2                   5.10.173-154.642.amzn2.x86_64   containerd://1.6.19
+
+

아래 예제에서는 다양한 윈도우 노드 그룹 OS 버전을 실행할 때 올바른 윈도우 빌드 버전을 일치시키기 위해 추가 노드셀렉터를 파드 스펙에 적용합니다.

+
nodeSelector:
+    kubernetes.io/os: windows
+    node.kubernetes.io/windows-build: '10.0.20348'
+tolerations:
+    - key: "os"
+    operator: "Equal"
+    value: "windows"
+    effect: "NoSchedule"
+
+

RuntimeClass를 사용하여 파드 매니페스트의 노드셀렉터와 톨러레이션 단순화

+

RuntimeClass를 사용하여 테인트와 톨러레이션을 사용하는 프로세스를 간소화할 수 있습니다. 이런 테인트와 톨러레이션을 캡슐화하는 RuntimeClass 오브젝트를 만들어 이 작업을 수행할 수 있습니다.

+

다음 매니페스트를 통해 RuntimeClass를 생성합니다:

+
apiVersion: node.k8s.io/v1beta1
+kind: RuntimeClass
+metadata:
+  name: windows-2022
+handler: 'docker'
+scheduling:
+  nodeSelector:
+    kubernetes.io/os: 'windows'
+    kubernetes.io/arch: 'amd64'
+    node.kubernetes.io/windows-build: '10.0.20348'
+  tolerations:
+  - effect: NoSchedule
+    key: os
+    operator: Equal
+    value: "windows"
+
+

RuntimeClass가 생성되면, 파드 매니페스트의 스펙을 통해 사용합니다.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: iis-2022
+  labels:
+    app: iis-2022
+spec:
+  replicas: 1
+  template:
+    metadata:
+      name: iis-2022
+      labels:
+        app: iis-2022
+    spec:
+      runtimeClassName: windows-2022
+      containers:
+      - name: iis
+
+

관리형 노드 그룹 지원

+

고객이 윈도우 애플리케이션을 보다 간소화된 방식으로 실행할 수 있도록 AWS에서는 2022년 12월 15일에 윈도우 컨테이너에 대한 EKS 관리형 노드 그룹 (MNG) 지원을 시작 했습니다. 윈도우 관리형 노드 그룹리눅스 관리형 노드 그룹과 동일한 워크플로우와 도구를 사용하여 활성화됩니다. 윈도우 서버 2019 및 2022 패밀리의 Full, Core AMI(Amazon Machine Image)가 지원 됩니다.

+

관리형 노드 그룹(MNG)에 지원되는 AMI 패밀리는 다음과 같습니다:

+ + + + + + + + + + + + + + + + + + + + +
AMI Family
WINDOWS_CORE_2019_x86_64
WINDOWS_FULL_2019_x86_64
WINDOWS_CORE_2022_x86_64
WINDOWS_FULL_2022_x86_64
+

추가 문서

+

AWS 공식 문서: +https://docs.aws.amazon.com/eks/latest/userguide/windows-support.html

+

파드 네트워킹(CNI)의 작동 방식을 더 잘 이해하려면 다음 링크를 확인하십시오: https://docs.aws.amazon.com/eks/latest/userguide/pod-networking.html

+

EKS 기반 윈도우용 관리형 노드 그룹 배포에 관한 AWS 블로그: +https://aws.amazon.com/blogs/containers/deploying-amazon-eks-windows-managed-node-groups/

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/security/index.html b/ko/windows/docs/security/index.html new file mode 100644 index 000000000..2c27699d0 --- /dev/null +++ b/ko/windows/docs/security/index.html @@ -0,0 +1,2122 @@ + + + + + + + + + + + + + + + + + + + + + + + 윈도우 컨테이너 파드 보안 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

파드 보안 컨텍스트

+

파드 시큐리티 폴리시(PSP)파드 시큐리티 스탠다드(PSS)는 쿠버네티스에서 보안을 강화하는 두 가지 주요 방법입니다. PSP는 쿠버네티스 v1.21부터 더 이상 사용되지 않으며 v1.25에서 제거될 예정입니다. 향후 보안을 강화하기 위해 쿠버네티스가 권장하는 접근 방식은 PSS입니다.

+

PSP는 보안 정책을 구현하기 위한 쿠버네티스의 네이티브 솔루션입니다. PSP는 파드 스펙에서 정의하고 보안에 민감한 측면을 제어하는 클러스터 레벨의 리소스 입니다. PSP를 사용하면 클러스터에서 승인 되기 위해 파드가 충족해야 하는 일련의 조건을 정의할 수 있습니다. +PSP 기능은 쿠버네티스 초기부터 사용 가능했으며 특정 클러스터에서 잘못 구성된 파드가 생성되는 것을 차단하도록 설계되었습니다.

+

PSP에 대한 자세한 내용은 쿠버네티스 문서를 참조하십시오. 쿠버네티스 지원 중단 정책에 따라 이전 버전은 기능 지원 중단 후 9개월이 지나면 지원이 중단됩니다.

+

반면, 일반적으로 보안 컨텍스트를 사용하여 구현되는 권장 보안 접근 방식인 PSS는 파드 매니페스트에서 파드 및 컨테이너 스펙에 정의됩니다. PSS는 쿠버네티스 프로젝트 팀이 파드의 보안 관련 모범 사례를 다루기 위해 정의한 공식 표준입니다. baseline (최소 제한, 기본값), privileged (비제한) 그리고 restricted(가장 제한적) 등의 정책을 정의합니다.

+

baseline 프로파일부터 시작하는 것이 좋습니다. PSS baseline 프로파일은 최소한의 예외 항목을 처리하고 보안과 잠재적 요소 사이의 적절한 균형을 제공하여 워크로드 보안을 위한 좋은 출발점 역할을 합니다. 현재 PSP를 사용하고 있다면 PSS로 전환하는 것이 좋습니다. PSS 정책에 대한 자세한 내용은 쿠버네티스 문서에서 확인할 수 있습니다. 이런 정책은 OPAKyverno 같은 도구를 포함한 여러 도구를 사용하여 강제할 수 있습니다. 예를 들어, Kyverno는 여기에서 PSS 정책의 전체 컬렉션을 제공합니다.

+

보안 컨텍스트 설정을 통해 프로세스를 선택할 수 있는 권한을 부여하고, 프로그램 프로파일을 사용하여 개별 프로그램의 기능을 제한하고, 권한 상승을 허용하고, 시스템 콜을 필터링하는 등의 작업을 수행할 수 있습니다.

+

쿠버네티스의 윈도우 파드에는 보안 컨텍스트와 관련하여 표준 리눅스 기반 워크로드와 몇 가지 제한 사항 및 차별화 요소가 있습니다.

+

윈도우는 시스템 네임스페이스 필터와 함께 컨테이너당 작업 개체를 사용하여 컨테이너의 모든 프로세스를 포함하고 호스트로부터 논리적 격리를 제공합니다. 네임스페이스 필터링 없이 윈도우 컨테이너를 실행할 수 있는 방법은 없습니다. 이는 호스트의 컨텍스트에서 시스템 권한을 취득할 수 없으므로 윈도우에서는 권한이 있는 컨테이너를 사용할 수 없습니다.

+

다음 windowsOptions 은 문서화된 유일한 Windows Security Context options이고, 나머지는 일반적인 Security Context options 입니다.

+

윈도우와 리눅스에서 지원되는 보안 컨텍스트 속성 목록은 여기의 공식 문서를 참조하십시오.

+

Pod별 설정은 모든 컨테이너에 적용 됩니다. 지정하지 않으면 PodSecurityContext의 옵션이 사용됩니다. SecurityContext와 PodSecurityContext가 모두 설정된 경우 SecurityContext에 지정된 값이 우선 적용됩니다.

+

예를 들어, 윈도우 옵션인 파드 및 컨테이너에 대한 runAsUserName 설정은 리눅스 관련 runAsUser 설정과 거의 동일하며 다음 매니페스트에서는 파드 관련 보안 컨텍스트가 모든 컨테이너에 적용됩니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: run-as-username-pod-demo
+spec:
+  securityContext:
+    windowsOptions:
+      runAsUserName: "ContainerUser"
+  containers:
+  - name: run-as-username-demo
+    ...
+  nodeSelector:
+    kubernetes.io/os: windows
+
+

다음에서는 컨테이너 수준 보안 컨텍스트가 파드 수준 보안 컨텍스트보다 우선 적용됩니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: run-as-username-container-demo
+spec:
+  securityContext:
+    windowsOptions:
+      runAsUserName: "ContainerUser"
+  containers:
+  - name: run-as-username-demo
+    ..
+    securityContext:
+        windowsOptions:
+            runAsUserName: "ContainerAdministrator"
+  nodeSelector:
+    kubernetes.io/os: windows
+
+

runAsUserName 필드에 허용되는 값의 예: ContainerAdministrator, ContainerUser, NT AUTHORITY\NETWORK SERVICE, NT AUTHORITY\LOCAL SERVICE

+

일반적으로 윈도우 파드용 ContainerUser를 사용하여 컨테이너를 실행하는 것이 좋습니다. 사용자는 컨테이너와 호스트 간에 공유되지 않지만 ContainerAdministrator는 컨테이너 내에서 추가 권한을 갖습니다. 주의해야 할 username의 제한 사항이 있습니다.

+

ContainerAdministrator를 사용하는 좋은 예는 PATH를 설정하는 것 입니다. USER 지시어를 사용한다면 다음과 같이 할 수 있습니다:

+
USER ContainerAdministrator
+RUN setx /M PATH "%PATH%;C:/your/path"
+USER ContainerUser
+
+

또한 secrets 는 노드 볼륨에 일반 텍스트로 기록됩니다(Linux의 tmpfs/in-memory와 비교). 이는 두 가지 작업을 수행해야 함을 의미합니다.

+
    +
  • file ACL을 사용하여 secrets 파일 위치를 보호
  • +
  • BitLocker를 사용하여 볼륨 수준 암호화 사용
  • +
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ko/windows/docs/storage/index.html b/ko/windows/docs/storage/index.html new file mode 100644 index 000000000..c8aa11684 --- /dev/null +++ b/ko/windows/docs/storage/index.html @@ -0,0 +1,2332 @@ + + + + + + + + + + + + + + + + + + + + + 스토리지 옵션 - EKS 모범 사례 가이드 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

영구 스토리지 옵션

+

In-tree와 Out-of-tree 볼륨 플러그인

+

컨테이너 스토리지 인터페이스(CSI)가 도입되기 전에는 모든 볼륨 플러그인이 in-tree 였습니다. 즉, 코어 쿠버네티스 바이너리와 함께 빌드, 연결, 컴파일 및 제공되고 핵심 쿠버네티스 API를 확장했습니다. 이는 쿠버네티스에 새로운 스토리지 시스템(볼륨 플러그인)을 추가하려면 핵심 코어 쿠버네티스 코드 저장소에 대한 코드를 확인해야 하는 것을 의미했습니다.

+

Out-of-tree 볼륨 플러그인은 쿠버네티스 코드 베이스와 독립적으로 개발되며 쿠버네티스 클러스터에 확장으로 배포(및 설치) 됩니다. 이를 통해 벤더는 쿠버네티스 릴리스 주기와 별도로 드라이버를 업데이트 할 수 있습니다. 이는 쿠버네티스가 벤더에 k8s와 인터페이스하는 표준 방법을 제공하는 스토리지 인터페이스 혹은 CSI를 만들었기 때문에 가능합니다.

+

Amazon Elastic Kubernetes Services (EKS) 스토리지 클래스 및 CSI 드라이버에 대한 자세한 내용을 AWS 문서에서 확인할 수 있습니다.

+

윈도우용 In-tree 볼륨 플러그인

+

쿠버네티스 볼륨을 사용하면 데이터 지속성 요구 사항이 있는 애플리케이션을 쿠버네티스에 배포할 수 있습니다. 퍼시스턴트 볼륨 관리는 볼륨 프로비저닝/프로비저닝 해제/크기 조정, 쿠버네티스 노드에 볼륨 연결/분리, 파드의 개별 컨테이너에 볼륨 마운트/마운트 해제로 구성됩니다. 특정 스토리지 백엔드 또는 프로토콜에 대해 이런 볼륨 관리 작업을 구현하기 위한 코드는 쿠버네티스 볼륨 플러그인 (In-tree 볼륨 플러그인) 형식으로 제공됩니다. Amazon EKS에서는 윈도우에서 다음과 같은 클래스의 쿠버네티스 볼륨 플러그인이 지원됩니다:

+

In-tree 볼륨 플러그인: awsElasticBlockStore

+

윈도우 노드에서 In-tree 볼륨 플러그인을 사용하기 위해서는 NTFS를 fsType 으로 사용하기 위한 추가 StorageClass를 생성해야 합니다. EKS에서 기본 StorageClass는 ext4를 기본 fsType으로 사용합니다.

+

StorageClass는 관리자가 제공하는 스토리지의 "클래스"를 설명하는 방법을 제공합니다. 다양한 클래스는 QoS 수준, 백업 정책 또는 클러스터 관리자가 결정한 임의 정책에 매핑될 수 있습니다. 쿠버네티스는 클래스가 무엇을 나타내는지에 대해 의견이 없습니다. 이 개념은 다른 스토리지 시스템에서는 "프로파일" 이라고 부르기도 합니다.

+

다음 명령을 실행하여 확인할 수 있습니다:

+
kubectl describe storageclass gp2
+
+

출력:

+
Name:            gp2
+IsDefaultClass:  Yes
+Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClas
+","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"name":"gp2"},"parameters":{"fsType"
+"ext4","type":"gp2"},"provisioner":"kubernetes.io/aws-ebs","volumeBindingMode":"WaitForFirstConsumer"}
+,storageclass.kubernetes.io/is-default-class=true
+Provisioner:           kubernetes.io/aws-ebs
+Parameters:            fsType=ext4,type=gp2
+AllowVolumeExpansion:  <unset>
+MountOptions:          <none>
+ReclaimPolicy:         Delete
+VolumeBindingMode:     WaitForFirstConsumer
+Events:                <none>
+
+

NTFS를 지원하는 새 StorageClass를 생성하려면 다음 매니페스트를 사용하십시오:

+
kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+  name: gp2-windows
+provisioner: kubernetes.io/aws-ebs
+parameters:
+  type: gp2
+  fsType: ntfs
+volumeBindingMode: WaitForFirstConsumer
+
+

다음 명령을 실행하여 StorageClass를 생성합니다:

+
kubectl apply -f NTFSStorageClass.yaml
+
+

다음 단계는 퍼시스턴트 볼륨 클레임(PVC)을 생성하는 것입니다.

+

퍼시스턴트 볼륨(PV)은 관리자가 프로비저닝했거나 PVC를 사용하여 동적으로 프로비저닝된 클러스터의 스토리지입니다. 노드가 클러스터 리소스인 것처럼 클러스터의 리소스입니다. 이 API 객체는 NFS, iSCSI 또는 클라우드 공급자별 스토리지 시스템 등 스토리지 구현의 세부 정보를 캡처합니다.

+

PVC는 사용자의 스토리지 요청입니다. 클레임은 특정 크기 및 액세스 모드를 요청할 수 있습니다(예: ReadWriteOnce, ReadOnlyMany 또는 ReadWriteMan로 마운트될 수 있음).

+

사용자에게는 다양한 유즈케이스를 위해 성능과 같은 다양한 속성을 가진 PV가 필요합니다. 클러스터 관리자는 사용자에게 해당 볼륨이 구현되는 방법에 대한 세부 정보를 노출시키지 않고도 크기 및 액세스 모드보다 더 다양한 방식으로 PV를 제공할 수 있어야 합니다. 이런 요구 사항을 충족하기 위해 StorageClass 리소스가 있습니다.

+

아래 예제에서 윈도우 네임스페이스 내에 PVC가 생성되었습니다.

+
apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: ebs-windows-pv-claim
+  namespace: windows
+spec: 
+  accessModes:
+    - ReadWriteOnce
+  storageClassName: gp2-windows
+  resources: 
+    requests:
+      storage: 1Gi
+
+

다음 명령을 실행하여 PVC를 만듭니다:

+
kubectl apply -f persistent-volume-claim.yaml
+
+

다음 매니페스트에서는 윈도우 파드를 생성하고 VolumeMount를 C:\Data로 설정하고 PVC를 C:\Data에 연결된 스토리지로 사용합니다.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: windows-server-ltsc2019
+  namespace: windows
+spec:
+  selector:
+    matchLabels:
+      app: windows-server-ltsc2019
+      tier: backend
+      track: stable
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: windows-server-ltsc2019
+        tier: backend
+        track: stable
+    spec:
+      containers:
+      - name: windows-server-ltsc2019
+        image: mcr.microsoft.com/windows/servercore:ltsc2019
+        ports:
+        - name: http
+          containerPort: 80
+        imagePullPolicy: IfNotPresent
+        volumeMounts:
+        - mountPath: "C:\\data"
+          name: test-volume
+      volumes:
+        - name: test-volume
+          persistentVolumeClaim:
+            claimName: ebs-windows-pv-claim
+      nodeSelector:
+        kubernetes.io/os: windows
+        node.kubernetes.io/windows-build: '10.0.17763'
+
+

PowerShell을 통해 윈도우 파드에 액세스하여 결과를 테스트합니다:

+
kubectl exec -it podname powershell -n windows
+
+

윈도우 파드 내에서 ls를 수행합니다:

+

출력:

+
PS C:\> ls
+
+
+    Directory: C:\
+
+
+Mode                 LastWriteTime         Length Name
+----                 -------------         ------ ----
+d-----          3/8/2021   1:54 PM                data
+d-----          3/8/2021   3:37 PM                inetpub
+d-r---          1/9/2021   7:26 AM                Program Files
+d-----          1/9/2021   7:18 AM                Program Files (x86)
+d-r---          1/9/2021   7:28 AM                Users
+d-----          3/8/2021   3:36 PM                var
+d-----          3/8/2021   3:36 PM                Windows
+-a----         12/7/2019   4:20 AM           5510 License.txt
+
+

data directory 는 EBS 볼륨에 의해 제공됩니다.

+

윈도우 Out-of-tree

+

CSI 플러그인과 연결된 코드는 일반적으로 컨테이너 이미지로 배포되고 데몬셋(DaemonSet) 및 스테이트풀셋(StatefulSet)과 같은 표준 쿠버네티스 구성을 사용하여 배포되는 out-of-tree 스크립트 및 바이너리로 제공됩니다. CSI 플러그인은 쿠버네티스에서 광범위한 볼륨 관리 작업을 처리합니다. CSI 플러그인은 일반적으로 노드 플러그인(각 노드에서 데몬셋으로 실행됨)과 컨트롤러 플러그인으로 구성됩니다.

+

CSI 노드 플러그인 (특히 블록 장치 또는 공유 파일 시스템을 통해 노출되는 퍼시스턴트 볼륨과 관련된 플러그인)은 디스크 장치 스캔, 파일 시스템 탑재 등과 같은 다양한 권한 작업을 수행해야 합니다. 이런 작업은 호스트 운영 체제마다 다릅니다. 리눅스 워커 노드의 경우 컨테이너화된 CSI 노드 플러그인은 일반적으로 권한 있는 컨테이너로 배포됩니다. 윈도우 워커 노드의 경우 각 윈도우 노드에 사전 설치해야 하는 커뮤니티 관리 독립 실행형 바이너리인 csi-proxy 를 사용하여 컨테이너화된 CSI 노드 플러그인에 대한 권한 있는 작업이 지원됩니다.

+

Amazon EKS 최적화 윈도우 AMI에서는 2022년 4월부터 CSI-proxy가 포함됩니다. 고객은 윈도우 노드의 SMB CSI 드라이버를 사용하여 Amazon FSx for Windows File Server, Amazon FSx for NetApp ONTAP SMB Shares, 및/또는 AWS Storage Gateway – File Gateway에 액세스 할 수 있습니다.

+

다음 블로그에서는 Amazon FSx for Windows File Server를 위도우 파드용 영구 스토리지로 사용하도록 SMB CSI 드라이버를 설정하는 방법에 대한 세부 정보가 나와 있습니다.

+

Amazon FSx for Windows File Server

+

한 가지 옵션은 SMB Global Mapping이라는 SMB 기능을 통해 Windows 파일 서버용 Amazon FSx를 사용하는 것입니다. 이 기능을 사용하면 호스트에 SMB 공유를 마운트한 다음 해당 공유의 디렉터리를 컨테이너로 전달할 수 있습니다. 컨테이너를 특정 서버, 공유, 사용자 이름 또는 암호로 구성할 필요가 없습니다. 대신 호스트에서 모두 처리됩니다. 컨테이너는 로컬 스토리지가 있는 것처럼 작동합니다.

+
+

SMB Global Mapping은 오케스트레이터에게 투명하게 전달되며 HostPath를 통해 마운트되므로 보안 문제가 발생할 수 있습니다.

+
+

아래 예제에서, G:\Directory\app-state 경로는 윈도우 노드의 SMB 공유입니다.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: test-fsx
+spec:
+  containers:
+  - name: test-fsx
+    image: mcr.microsoft.com/windows/servercore:ltsc2019
+    command:
+      - powershell.exe
+      - -command
+      - "Add-WindowsFeature Web-Server; Invoke-WebRequest -UseBasicParsing -Uri 'https://dotnetbinaries.blob.core.windows.net/servicemonitor/2.0.1.6/ServiceMonitor.exe' -OutFile 'C:\\ServiceMonitor.exe'; echo '<html><body><br/><br/><marquee><H1>Hello EKS!!!<H1><marquee></body><html>' > C:\\inetpub\\wwwroot\\default.html; C:\\ServiceMonitor.exe 'w3svc'; "
+    volumeMounts:
+      - mountPath: C:\dotnetapp\app-state
+        name: test-mount
+  volumes:
+    - name: test-mount
+      hostPath: 
+        path: G:\Directory\app-state
+        type: Directory
+  nodeSelector:
+      beta.kubernetes.io/os: windows
+      beta.kubernetes.io/arch: amd64
+
+

다음 블로그 에서는 Amazon FSx for Windows File Server를 윈도우 파드용 영구 스토리지로 설정하는 방법에 대한 세부 정보가 나와 있습니다.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/custom-networking/image-2.png b/networking/custom-networking/image-2.png new file mode 100644 index 000000000..b1572606c Binary files /dev/null and b/networking/custom-networking/image-2.png differ diff --git a/networking/custom-networking/image-3.png b/networking/custom-networking/image-3.png new file mode 100644 index 000000000..e3165c073 Binary files /dev/null and b/networking/custom-networking/image-3.png differ diff --git a/networking/custom-networking/image.png b/networking/custom-networking/image.png new file mode 100644 index 000000000..90f8d479c Binary files /dev/null and b/networking/custom-networking/image.png differ diff --git a/networking/custom-networking/index.html b/networking/custom-networking/index.html new file mode 100644 index 000000000..56aa66375 --- /dev/null +++ b/networking/custom-networking/index.html @@ -0,0 +1,2412 @@ + + + + + + + + + + + + + + + + + + + + + + + Custom Networking - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Custom Networking

+

By default, Amazon VPC CNI will assign Pods an IP address selected from the primary subnet. The primary subnet is the subnet CIDR that the primary ENI is attached to, usually the subnet of the node/host.

+

If the subnet CIDR is too small, the CNI may not be able to acquire enough secondary IP addresses to assign to your Pods. This is a common challenge for EKS IPv4 clusters.

+

Custom networking is one solution to this problem.

+

Custom networking addresses the IP exhaustion issue by assigning the node and Pod IPs from secondary VPC address spaces (CIDR). Custom networking support supports ENIConfig custom resource. The ENIConfig includes an alternate subnet CIDR range (carved from a secondary VPC CIDR), along with the security group(s) that the Pods will belong to. When custom networking is enabled, the VPC CNI creates secondary ENIs in the subnet defined under ENIConfig. The CNI assigns Pods an IP addresses from a CIDR range defined in a ENIConfig CRD.

+

Since the primary ENI is not used by custom networking, the maximum number of Pods you can run on a node is lower. The host network Pods continue to use IP address assigned to the primary ENI. Additionally, the primary ENI is used to handle source network translation and route Pods traffic outside the node.

+

Example Configuration

+

While custom networking will accept valid VPC range for secondary CIDR range, we recommend that you use CIDRs (/16) from the CG-NAT space, i.e. 100.64.0.0/10 or 198.19.0.0/16 as those are less likely to be used in a corporate setting than other RFC1918 ranges. For additional information about the permitted and restricted CIDR block associations you can use with your VPC, see IPv4 CIDR block association restrictions in the VPC and subnet sizing section of the VPC documentation.

+

As shown in the diagram below, the primary Elastic Network Interface (ENI) of the worker node still uses the primary VPC CIDR range (in this case 10.0.0.0/16) but the secondary ENIs use the secondary VPC CIDR Range (in this case 100.64.0.0/16). Now, in order to have the Pods use the 100.64.0.0/16 CIDR range, you must configure the CNI plugin to use custom networking. You can follow through the steps as documented here.

+

illustration of pods on secondary subnet

+

If you want the CNI to use custom networking, set the AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG environment variable to true.

+
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true
+
+

When AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true, the CNI will assign Pod IP address from a subnet defined in ENIConfig. The ENIConfig custom resource is used to define the subnet in which Pods will be scheduled.

+
apiVersion : crd.k8s.amazonaws.com/v1alpha1
+kind : ENIConfig
+metadata:
+  name: us-west-2a
+spec: 
+  securityGroups:
+    - sg-0dff111a1d11c1c11
+  subnet: subnet-011b111c1f11fdf11
+
+

Upon creating the ENIconfig custom resources, you will need to create new worker nodes and drain the existing nodes. The existing worker nodes and Pods will remain unaffected.

+

Recommendations

+

Use Custom Networking When

+

We recommend you to consider custom networking if you are dealing with IPv4 exhaustion and can’t use IPv6 yet. Amazon EKS support for RFC6598 space enables you to scale Pods beyond RFC1918 address exhaustion challenges. Please consider using prefix delegation with custom networking to increase the Pods density on a node.

+

You might consider custom networking if you have a security requirement to run Pods on a different network with different security group requirements. When custom networking enabled, the pods use different subnet or security groups as defined in the ENIConfig than the node's primary network interface.

+

Custom networking is indeed an ideal option for deploying multiple EKS clusters and applications to connect on-premise datacenter services. You can increase the number of private addresses (RFC1918) accessible to EKS in your VPC for services such as Amazon Elastic Load Balancing and NAT-GW, while using non-routable CG-NAT space for your Pods across multiple clusters. Custom networking with the transit gateway and a Shared Services VPC (including NAT gateways across several Availability Zones for high availability) enables you to deliver scalable and predictable traffic flows. This blog post describes an architectural pattern that is one of the most recommended ways to connect EKS Pods to a datacenter network using custom networking.

+

Avoid Custom Networking When

+

Ready to Implement IPv6

+

Custom networking can mitigate IP exhaustion issues, but it requires additional operational overhead. If you are currently deploying a dual-stack (IPv4/IPv6) VPC or if your plan includes IPv6 support, we recommend implementing IPv6 clusters instead. You can set up IPv6 EKS clusters and migrate your apps. In an IPv6 EKS cluster, both Kubernetes and Pods get an IPv6 address and can communicate in and out to both IPv4 and IPv6 endpoints. Please review best practices for Running IPv6 EKS Clusters.

+

Exhausted CG-NAT Space

+

Furthermore, if you're currently utilizing CIDRs from the CG-NAT space or are unable to link a secondary CIDR with your cluster VPC, you may need to explore other options, such as using an alternative CNI. We strongly recommend that you either obtain commercial support or possess the in-house knowledge to debug and submit patches to the open source CNI plugin project. Refer Alternate CNI Plugins user guide for more details.

+

Use Private NAT Gateway

+

Amazon VPC now offers private NAT gateway capabilities. Amazon's private NAT Gateway enables instances in private subnets to connect to other VPCs and on-premises networks with overlapping CIDRs. Consider utilizing the method described on this blog post to employ a private NAT gateway to overcome communication issues for the EKS workloads caused by overlapping CIDRs, a significant complaint expressed by our clients. Custom networking cannot address the overlapping CIDR difficulties on its own, and it adds to the configuration challenges.

+

The network architecture used in this blog post implementation follows the recommendations under Enable communication between overlapping networks in Amazon VPC documentation. As demonstrated in this blog post, you may expand the usage of private NAT Gateway in conjunction with RFC6598 addresses to manage customers' private IP exhaustion issues. The EKS clusters, worker nodes are deployed in the non-routable 100.64.0.0/16 VPC secondary CIDR range, whereas the private NAT gateway, NAT gateway are deployed to the routable RFC1918 CIDR ranges. The blog explains how a transit gateway is used to connect VPCs in order to facilitate communication across VPCs with overlapping non-routable CIDR ranges. For use cases in which EKS resources in a VPC's non-routable address range need to communicate with other VPCs that do not have overlapping address ranges, customers have the option of using VPC Peering to interconnect such VPCs. This method could provide potential cost savings as all data transit within an Availability Zone via a VPC peering connection is now free.

+

illustration of network traffic using private NAT gateway

+

Unique network for nodes and Pods

+

If you need to isolate your nodes and Pods to a specific network for security reasons, we recommend that you deploy nodes and Pods to a subnet from a larger secondary CIDR block (e.g. 100.64.0.0/8). Following the installation of the new CIDR in your VPC, you can deploy another node group using the secondary CIDR and drain the original nodes to automatically redeploy the pods to the new worker nodes. For more information on how to implement this, see this blog post.

+

Custom networking is not used in the setup represented in the diagram below. Rather, Kubernetes worker nodes are deployed on subnets from your VPC's secondary VPC CIDR range, such as 100.64.0.0/10. You can keep the EKS cluster running (the control plane will remain on the original subnet/s), but the nodes and Pods will be moved to a secondary subnet/s. This is yet another, albeit unconventional, technique to mitigate the danger of IP exhaustion in a VPC. We propose draining the old nodes before redeploying the pods to the new worker nodes.

+

illustration of worker nodes on secondary subnet

+

Automate Configuration with Availability Zone Labels

+

You can enable Kubernetes to automatically apply the corresponding ENIConfig for the worker node Availability Zone (AZ).

+

Kubernetes automatically adds the tag topology.kubernetes.io/zone to your worker nodes. Amazon EKS recommends using the availability zone as your ENI config name when you only have one secondary subnet (alternate CIDR) per AZ. Note that tag failure-domain.beta.kubernetes.io/zone is deprecated and replaced with the tag topology.kubernetes.io/zone.

+
    +
  1. Set name field to the Availability Zone of your VPC.
  2. +
  3. Enable automatic configuration with this command:
  4. +
+
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true
+
+

if you have multiple secondary subnets per availability zone, you need create a specific ENI_CONFIG_LABEL_DEF. You might consider configuring ENI_CONFIG_LABEL_DEF as k8s.amazonaws.com/eniConfig and label nodes with custom eniConfig names, such as k8s.amazonaws.com/eniConfig=us-west-2a-subnet-1 and k8s.amazonaws.com/eniConfig=us-west-2a-subnet-2.

+

Replace Pods when Configuring Secondary Networking

+

Enabling custom networking does not modify existing nodes. Custom networking is a disruptive action. Rather than doing a rolling replacement of all the worker nodes in your cluster after enabling custom networking, we suggest updating the AWS CloudFormation template in the EKS Getting Started Guide with a custom resource that calls a Lambda function to update the aws-node Daemonset with the environment variable to enable custom networking before the worker nodes are provisioned.

+

If you had any nodes in your cluster with running Pods before you switched to the custom CNI networking feature, you should cordon and drain the nodes to gracefully shutdown the Pods and then terminate the nodes. Only new nodes matching the ENIConfig label or annotations use custom networking, and hence the Pods scheduled on these new nodes can be assigned an IP from secondary CIDR.

+

Calculate Max Pods per Node

+

Since the node’s primary ENI is no longer used to assign Pod IP addresses, there is a decrease in the number of Pods you can run on a given EC2 instance type. To work around this limitation you can use prefix assignment with custom networking. With prefix assignment, each secondary IP is replaced with a /28 prefix on secondary ENIs.

+

Consider the maximum number of Pods for an m5.large instance with custom networking.

+

The maximum number of Pods you can run without prefix assignment is 29

+
    +
  • ((3 ENIs - 1) * (10 secondary IPs per ENI - 1)) + 2 = 20
  • +
+

Enabling prefix attachments increases the number of Pods to 290.

+
    +
  • (((3 ENIs - 1) * ((10 secondary IPs per ENI - 1) * 16)) + 2 = 290
  • +
+

However, we suggest setting max-pods to 110 rather than 290 because the instance has a rather small number of virtual CPUs. On bigger instances, EKS recommends a max pods value of 250. When utilizing prefix attachments with smaller instance types (e.g. m5.large), it is possible that you will exhaust the instance's CPU and memory resources well before its IP addresses.

+
+

Info

+

When the CNI prefix allocates a /28 prefix to an ENI, it has to be a contiguous block of IP addresses. If the subnet that the prefix is generated from is highly fragmented, the prefix attachment may fail. You can mitigate this from happening by creating a new dedicated VPC for the cluster or by reserving subnet a set of CIDR exclusively for prefix attachments. Visit Subnet CIDR reservations for more information on this topic.

+
+

Identify Existing Usage of CG-NAT Space

+

Custom networking allows you to mitigate IP exhaustion issue, however it can’t solve all the challenges. If you already using CG-NAT space for your cluster, or simply don’t have the ability to associate a secondary CIDR with your cluster VPC, we suggest you to explore other options, like using an alternate CNI or moving to IPv6 clusters.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/index/image.png b/networking/index/image.png new file mode 100644 index 000000000..3e961a1aa Binary files /dev/null and b/networking/index/image.png differ diff --git a/networking/index/index.html b/networking/index/index.html new file mode 100644 index 000000000..ce57b1f11 --- /dev/null +++ b/networking/index/index.html @@ -0,0 +1,2210 @@ + + + + + + + + + + + + + + + + + + + + + + + Home - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Amazon EKS Best Practices Guide for Networking

+

It is critical to understand Kubernetes networking to operate your cluster and applications efficiently. Pod networking, also called the cluster networking, is the center of Kubernetes networking. Kubernetes supports Container Network Interface (CNI) plugins for cluster networking.

+

Amazon EKS officially supports Amazon Virtual Private Cloud (VPC) CNI plugin to implement Kubernetes Pod networking. The VPC CNI provides native integration with AWS VPC and works in underlay mode. In underlay mode, Pods and hosts are located at the same network layer and share the network namespace. The IP address of the Pod is consistent from the cluster and VPC perspective.

+

This guide introduces the Amazon VPC Container Network Interface(VPC CNI) in the context of Kubernetes cluster networking. The VPC CNI is the default networking plugin supported by EKS and hence is the focus of the guide. The VPC CNI is highly configurable to support different use cases. This guide further includes dedicated sections on different VPC CNI use cases, operating modes, sub-components, followed by the recommendations.

+

Amazon EKS runs upstream Kubernetes and is certified Kubernetes conformant. Although you can use alternate CNI plugins, this guide does not provide recommendations for managing alternate CNIs. Check the EKS Alternate CNI documentation for a list of partners and resources for managing alternate CNIs effectively.

+

Kubernetes Networking Model

+

Kubernetes sets the following requirements on cluster networking:

+
    +
  • Pods scheduled on the same node must be able to communicate with other Pods without using NAT (Network Address Translation).
  • +
  • All system daemons (background processes, for example, kubelet) running on a particular node can communicate with the Pods running on the same node.
  • +
  • Pods that use the host network must be able to contact all other Pods on all other nodes without using NAT.
  • +
+

See the Kubernetes network model for details on what Kubernetes expects from compatible networking implementations. The following figure illustrates the relationship between Pod network namespaces and the host network namespace.

+

illustration of host network and 2 pod network namespaces

+

Container Networking Interface (CNI)

+

Kubernetes supports CNI specifications and plugins to implement Kubernetes network model. A CNI consists of a specification (current version 1.0.0) and libraries for writing plugins to configure network interfaces in containers, along with a number of supported plugins. CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted.

+

The CNI plugin is enabled by passing kubelet the --network-plugin=cni command-line option. Kubelet reads a file from --cni-conf-dir (default /etc/cni/net.d) and uses the CNI configuration from that file to set up each Pod’s network. The CNI configuration file must match the CNI specification (minimum v0.4.0) and any required CNI plugins referenced by the configuration must be present in the --cni-bin-dir directory (default /opt/cni/bin). If there are multiple CNI configuration files in the directory, the kubelet uses the configuration file that comes first by name in lexicographic order.

+

Amazon Virtual Private Cloud (VPC) CNI

+

The AWS-provided VPC CNI is the default networking add-on for EKS clusters. VPC CNI add-on is installed by default when you provision EKS clusters. VPC CNI runs on Kubernetes worker nodes. The VPC CNI add-on consists of the CNI binary and the IP Address Management (ipamd) plugin. The CNI assigns an IP address from the VPC network to a Pod. The ipamd manages AWS Elastic Networking Interfaces (ENIs) to each Kubernetes node and maintains the warm pool of IPs. The VPC CNI provides configuration options for pre-allocation of ENIs and IP addresses for fast Pod startup times. Refer to Amazon VPC CNI for recommended plugin management best practices.

+

Amazon EKS recommends you specify subnets in at least two availability zones when you create a cluster. Amazon VPC CNI allocates IP addresses to Pods from the node subnets. We strongly recommend checking the subnets for available IP addresses. Please consider VPC and Subnet recommendations before deploying EKS clusters.

+

Amazon VPC CNI allocates a warm pool of ENIs and secondary IP addresses from the subnet attached to the node’s primary ENI. This mode of VPC CNI is called the "secondary IP mode." The number of IP addresses and hence the number of Pods (Pod density) is defined by the number of ENIs and the IP address per ENI (limits) as defined by the instance type. The secondary mode is the default and works well for small clusters with smaller instance types. Please consider using prefix mode if you are experiencing pod density challenges. You can also increase the available IP addresses on node for Pods by assigning prefixes to ENIs.

+

Amazon VPC CNI natively integrates with AWS VPC and allows users to apply existing AWS VPC networking and security best practices for building Kubernetes clusters. This includes the ability to use VPC flow logs, VPC routing policies, and security groups for network traffic isolation. By default, the Amazon VPC CNI applies security group associated with the primary ENI on the node to the Pods. Consider enabling security groups for Pods when you would like to assign different network rules for a Pod.

+

By default, VPC CNI assigns IP addresses to Pods from the subnet assigned to the primary ENI of a node. It is common to experience a shortage of IPv4 addresses when running large clusters with thousands of workloads. AWS VPC allows you to extend available IPs by assigning a secondary CIDRs to work around exhaustion of IPv4 CIDR blocks. AWS VPC CNI allows you to use a different subnet CIDR range for Pods. This feature of VPC CNI is called custom networking. You might consider using custom networking to use 100.64.0.0/10 and 198.19.0.0/16 CIDRs (CG-NAT) with EKS. This effectively allows you to create an environment where Pods no longer consume any RFC1918 IP addresses from your VPC.

+

Custom networking is one option to address the IPv4 address exhaustion problem, but it requires operational overhead. We recommend IPv6 clusters over custom networking to resolve this problem. Specifically, we recommend migrating to IPv6 clusters if you have completely exhausted all available IPv4 address space for your VPC. Evaluate your organization’s plans to support IPv6, and consider if investing in IPv6 may have more long-term value.

+

EKS’s support for IPv6 is focused on solving the IP exhaustion problem caused by a limited IPv4 address space. In response to customer issues with IPv4 exhaustion, EKS has prioritized IPv6-only Pods over dual-stack Pods. That is, Pods may be able to access IPv4 resources, but they are not assigned an IPv4 address from VPC CIDR range. The VPC CNI assigns IPv6 addresses to Pods from the AWS managed VPC IPv6 CIDR block.

+

Subnet Calculator

+

This project includes a Subnet Calculator Excel Document. This calculator document simulates the IP address consumption of a specified workload under different ENI configuration options, such as WARM_IP_TARGET and WARM_ENI_TARGET. The document includes two sheets, a first for Warm ENI mode, and a second for Warm IP mode. Review the VPC CNI guidance for more information on these modes.

+

Inputs: +- Subnet CIDR Size +- Warm ENI Target or Warm IP Target +- List of instances + - type, number, and number of workload pods scheduled per instance

+

Outputs: +- Total number of pods hosted +- Number of Subnet IPs consumed +- Number of Subnet IPs remaining +- Instance Level Details + - Number of Warm IPs/ENIs per instance + - Number of Active IPs/ENIs per instance

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/ip-optimization-strategies/custom-networking.gif b/networking/ip-optimization-strategies/custom-networking.gif new file mode 100644 index 000000000..27de9f443 Binary files /dev/null and b/networking/ip-optimization-strategies/custom-networking.gif differ diff --git a/networking/ip-optimization-strategies/index.html b/networking/ip-optimization-strategies/index.html new file mode 100644 index 000000000..b977e913d --- /dev/null +++ b/networking/ip-optimization-strategies/index.html @@ -0,0 +1,2359 @@ + + + + + + + + + + + + + + + + + + + + + + + Optimizing IP Address Utilization - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Optimizing IP Address Utilization

+

Containerized environments are growing in scale at a rapid pace, thanks to application modernization. This means that more and more worker nodes and pods are being deployed.

+

The Amazon VPC CNI plugin assigns each pod an IP address from the VPC's CIDR(s). This approach provides full visibility of the Pod addresses with tools such as VPC Flow Logs and other monitoring solutions. Depending on your workload type this can cause a substantial number of IP addresses to be consumed by the pods.

+

When designing your AWS networking architecture, it is important to optimize Amazon EKS IP consumption at the VPC and at the node level. This will help you mitigate IP exhaustion issues and increase the pod density per node.

+

In this section, we will discuss techniques that can help you achieve these goals.

+

Optimize node-level IP consumption

+

Prefix delegation is a feature of Amazon Virtual Private Cloud (Amazon VPC) that allows you to assign IPv4 or IPv6 prefixes to your Amazon Elastic Compute Cloud (Amazon EC2) instances. It increases the IP addresses per network interface (ENI), which increases the pod density per node and improves your compute efficiency. Prefix delegation is also supported with Custom Networking.

+

For detailed information please see Prefix Delegation with Linux nodes and Prefix Delegation with Windows nodes sections.

+

Mitigate IP exhaustion

+

To prevent your clusters from consuming all available IP addresses, we strongly recommend sizing your VPCs and subnets with growth in mind.

+

Adopting IPv6 is a great way to avoid these problems from the very beginning. However, for organizations whose scalability needs exceed the initial planning and cannot adopt IPv6, improving the VPC design is the recommended response to IP address exhaustion. The most commonly used technique among Amazon EKS customers is adding non-routable Secondary CIDRs to the VPC and configuring the VPC CNI to use this additional IP space when allocating IP addresses to Pods. This is commonly referred to as Custom Networking.

+

We will cover which variables of the Amazon VPC CNI you can use to optimize the warm pool of IPs assigned to your nodes. We will close this section with some other architectural patterns that are not intrinsic to Amazon EKS but can help mitigate IP exhaustion.

+ +

Adopting IPv6 is the easiest way to work around the RFC1918 limitations; we strongly recommend you consider adopting IPv6 as your first option when choosing a network architecture. IPv6 provides a significantly larger total IP address space, and cluster administrators can focus on migrating and scaling applications without devoting effort towards working around IPv4 limits.

+

Amazon EKS clusters support both IPv4 and IPv6. By default, EKS clusters use IPv4 address space. Specifying an IPv6 based address space at cluster creation time will enable the use of IPv6. In an IPv6 EKS cluster, pods and services receive IPv6 addresses while maintaining the ability for legacy IPv4 endpoints to connect to services running on IPv6 clusters and vice versa. All the pod-to-pod communication within a cluster always occurs over IPv6. Within a VPC (/56), the IPv6 CIDR block size for IPv6 subnets is fixed at /64. This provides 2^64 (approximately 18 quintillion) IPv6 addresses allowing to scale your deployments on EKS.

+

For detailed information please see the Running IPv6 EKS Clusters section and for hands-on experience please see the Understanding IPv6 on Amazon EKS section of the Get hands-on with IPv6 workshop.

+

EKS Cluster in IPv6 Mode, traffic flow

+

Optimize IP consumption in IPv4 clusters

+

This section is dedicated to customers that are running legacy applications, and/or are not ready to migrate to IPv6. While we encourage all organizations to migrate to IPv6 as soon as possible, we recognize that some may still need to look into alternative approaches to scale their container workloads with IPv4. For this reason, we will also walk you through the architectural patterns to optimize IPv4 (RFC1918) address space consumption with Amazon EKS clusters.

+

Plan for Growth

+

As a first line of defense against IP exhaustion, we strongly recommend to size your IPv4 VPCs and subnets with growth in mind, to prevent your clusters to consume all the available IP addresses. You will not be able to create new Pods or nodes if the subnets don’t have enough available IP addresses.

+

Before building VPC and subnets, it is advised to work backwards from the required workload scale. For example, when clusters are built using eksctl (a simple CLI tool for creating and managing clusters on EKS) /19 subnets are created by default. A netmask of /19 is suitable for the majority of workload types allowing more than 8000 addresses to be allocated.

+
+

Attention

+

When you size VPCs and subnets, there might be a number of elements (other than pods and nodes) which can consume IP addresses, for example Load Balancers, RDS Databases and other in-vpc services.

+
+

Additionally, Amazon EKS, can create up to 4 elastic network interfaces (X-ENI) that are required to allow communication towards the control plane (more info here). During cluster upgrades, Amazon EKS creates new X-ENIs and deletes the old ones when the upgrade is successful. For this reason we recommend a netmask of at least /28 (16 IP addresses) for subnets associated with an EKS cluster.

+

You can use the sample EKS Subnet Calculator spreadsheet to plan for your network. The spreadsheet calculates IP usage based on workloads and VPC ENI configuration. The IP usage is compared to an IPv4 subnet to determine if the configuration and subnet size is sufficient for your workload. Keep in mind that, if subnets in your VPC run out of available IP addresses, we suggest creating a new subnet using the VPC’s original CIDR blocks. Notice that now Amazon EKS now allows modification of cluster subnets and security groups.

+

Expand the IP space

+

If you are about to exhaust the RFC1918 IP space, you can use the Custom Networking pattern to conserve routable IPs by scheduling Pods inside dedicated additional subnets. +While custom networking will accept valid VPC range for secondary CIDR range, we recommend that you use CIDRs (/16) from the CG-NAT space, i.e. 100.64.0.0/10 or 198.19.0.0/16 as those are less likely to be used in a corporate setting than RFC1918 ranges.

+

For detailed information please see the dedicated section for Custom Networking.

+

Custom Networking, traffic flow

+

Optimize the IPs warm pool

+

With the default configuration, the VPC CNI keeps an entire ENI (and associated IPs) in the warm pool. This may consume a large number of IPs, especially on larger instance types.

+

If your cluster subnet has a limited number of IP addresses available, scrutinize these VPC CNI configuration environment variables:

+
    +
  • WARM_IP_TARGET
  • +
  • MINIMUM_IP_TARGET
  • +
  • WARM_ENI_TARGET
  • +
+

You can configure the value of MINIMUM_IP_TARGET to closely match the number of Pods you expect to run on your nodes. Doing so will ensure that as Pods get created, and the CNI can assign IP addresses from the warm pool without calling the EC2 API.

+

Please be mindful that setting the value of WARM_IP_TARGET too low, will cause additional calls to the EC2 API, and that might cause throttling of the requests. For large clusters use along with MINIMUM_IP_TARGET to avoid throttling of the requests.

+

To configure these options, you can download the aws-k8s-cni.yaml manifest and set the environment variables. At the time of writing, the latest release is located here. Check the version of the configuration value matches the installed VPC CNI version.

+
+

Warning

+

These settings will be reset to defaults when you update the CNI. Please take a backup of the CNI, before you update it. Review the configuration settings to determine if you need to reapply them after update is successful.

+
+

You can adjust the CNI parameters on the fly without downtime for your existing applications, but you should choose values that will support your scalability needs. For example, if you're working with batch workloads, we recommend updating the default WARM_ENI_TARGET to match the Pod scale needs. Setting WARM_ENI_TARGET to a high value always maintains the warm IP pool required to run large batch workloads and hence avoid data processing delays.

+
+

Warning

+

Improving your VPC design is the recommended response to IP address exhaustion. Consider solutions like IPv6 and Secondary CIDRs. Adjusting these values to minimize the number of Warm IPs should be a temporary solution after other options are excluded. Misconfiguring these values may interfere with cluster operation.

+

Before making any changes to a production system, be sure to review the considerations on this page.

+
+

Monitor IP Address Inventory

+

In addition to the solutions described above, it is also important to have visibility over IP utilization. You can monitor the IP addresses inventory of subnets using CNI Metrics Helper. Some of the metrics available are:

+
    +
  • maximum number of ENIs the cluster can support
  • +
  • number of ENIs already allocated
  • +
  • number of IP addresses currently assigned to Pods
  • +
  • total and maximum number of IP address available
  • +
+

You can also set CloudWatch alarms to get notified if a subnet is running out of IP addresses.

+
+

Warning

+

Make sure DISABLE_METRICS variable for VPC CNI is set to false.

+
+

Further considerations

+

There are other architectural patterns not intrinsic to Amazon EKS that can help with IP exhaustion. For example, you can optimize communication across VPCs or share a VPC across multiple accounts to limit the IPv4 address allocation.

+

Learn more about these patterns here:

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/ip-optimization-strategies/ipv6.gif b/networking/ip-optimization-strategies/ipv6.gif new file mode 100644 index 000000000..c43baa011 Binary files /dev/null and b/networking/ip-optimization-strategies/ipv6.gif differ diff --git a/networking/ipv6/Pod-to-service-ipv6.png b/networking/ipv6/Pod-to-service-ipv6.png new file mode 100644 index 000000000..501d5a7c5 Binary files /dev/null and b/networking/ipv6/Pod-to-service-ipv6.png differ diff --git a/networking/ipv6/eks-cluster-ipv6-foundation.png b/networking/ipv6/eks-cluster-ipv6-foundation.png new file mode 100644 index 000000000..b07a9d14f Binary files /dev/null and b/networking/ipv6/eks-cluster-ipv6-foundation.png differ diff --git a/networking/ipv6/eks-egress-ipv6.png b/networking/ipv6/eks-egress-ipv6.png new file mode 100644 index 000000000..4d6535c79 Binary files /dev/null and b/networking/ipv6/eks-egress-ipv6.png differ diff --git a/networking/ipv6/eks-ipv4-snat-cni-internet.png b/networking/ipv6/eks-ipv4-snat-cni-internet.png new file mode 100644 index 000000000..6e4a81003 Binary files /dev/null and b/networking/ipv6/eks-ipv4-snat-cni-internet.png differ diff --git a/networking/ipv6/eks-ipv4-snat-cni.png b/networking/ipv6/eks-ipv4-snat-cni.png new file mode 100644 index 000000000..6d330a273 Binary files /dev/null and b/networking/ipv6/eks-ipv4-snat-cni.png differ diff --git a/networking/ipv6/eks-ipv6-foundation.png b/networking/ipv6/eks-ipv6-foundation.png new file mode 100644 index 000000000..c11bedfde Binary files /dev/null and b/networking/ipv6/eks-ipv6-foundation.png differ diff --git a/networking/ipv6/image-2.png b/networking/ipv6/image-2.png new file mode 100644 index 000000000..bb75b5fe4 Binary files /dev/null and b/networking/ipv6/image-2.png differ diff --git a/networking/ipv6/image-3.png b/networking/ipv6/image-3.png new file mode 100644 index 000000000..7b35aceb9 Binary files /dev/null and b/networking/ipv6/image-3.png differ diff --git a/networking/ipv6/image-4.png b/networking/ipv6/image-4.png new file mode 100644 index 000000000..73eebf185 Binary files /dev/null and b/networking/ipv6/image-4.png differ diff --git a/networking/ipv6/image-5.png b/networking/ipv6/image-5.png new file mode 100644 index 000000000..51203b3ae Binary files /dev/null and b/networking/ipv6/image-5.png differ diff --git a/networking/ipv6/index.html b/networking/ipv6/index.html new file mode 100644 index 000000000..56acb94e6 --- /dev/null +++ b/networking/ipv6/index.html @@ -0,0 +1,2336 @@ + + + + + + + + + + + + + + + + + + + + + + + Running IPv6 Clusters - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Running IPv6 EKS Clusters

+ + +

EKS in IPv6 mode solves the IPv4 exhaustion challenge often manifested in large scale EKS clusters. EKS’s support for IPv6 is focused on resolving the IPv4 exhaustion problem, which stems from the limited size of the IPv4 address space. This is a significant concern raised by a number of our customers and is distinct from Kubernetes’ “IPv4/IPv6 dual-stack” feature. +EKS/IPv6 will also provide the flexability to inter-connect network boundaries using IPv6 CIDRs hence minimizing the chances to suffer from CIDR overlap, therefor solving a 2-Fold problem (In-Cluster, Cross-Cluster). +When deploying EKS clusters in IPv6 mode (--ip-family ipv6), the action is not a reversible. In simple words EKS IPv6 support is enabled for the entire lifetime of your cluster.

+

In an IPv6 EKS cluster, Pods and Services will receive IPv6 addresses while maintaining compatibility with legacy IPv4 Endpoints. This includes the ability for external IPv4 endpoints to access in-cluster services, and Pods to access external IPv4 endpoints.

+

Amazon EKS IPv6 support leverages the native VPC IPv6 capabilities. Each VPC is allocated with an IPv4 address prefix (CIDR block size can be from /16 to /28) and a unique /56 IPv6 address prefix (fixed) from within Amazon’s GUA (Global Unicast Address); you can assign a /64 address prefix to each subnet in your VPC. IPv4 features, like Route Tables, Network Access Control Lists, Peering, and DNS resolution, work the same way in an IPv6 enabled VPC. The VPC is then referred as dual-stack VPC, following dual-stack subnets, the following diagram depict the IPV4&IPv6 VPC foundation pattern that support EKS/IPv6 based clusters:

+

Dual Stack VPC, mandatory foundation for EKS cluster in IPv6 mode

+

In the IPv6 world, every address is internet routable. By default, VPC allocates IPv6 CIDR from the public GUA range. VPCs do not support assigning private IPv6 addresses from the Unique Local Address (ULA) range as defined by RFC 4193 (fd00::/8 or fc00::/8). This is true even when you would like to assign an IPv6 CIDR owned by you. Egressing to the internet from Private Subnets is supported by implementing an egress-only internet gateway (EIGW) in a VPC, allowing outbound traffic while blocking all incoming traffic. +The following diagram depict a Pod IPv6 Internet egress flow inside an EKS/IPv6 cluster:

+

Dual Stack VPC, EKS Cluster in IPv6 Mode, Pods in private subnets egressing to Internet IPv6 endpoints

+

Best practices for implementing IPv6 subnets can be found in the VPC user guide.

+

In an IPv6 EKS cluster, nodes and Pods receive public IPv6 addresses. EKS assigns IPv6 addresses to services based on Unique Local IPv6 Unicast Addresses (ULA). The ULA Service CIDR for an IPv6 cluster is automatically assigned during the cluster creation stage and cannot be specified, unlike IPv4. The following diagram depict an EKS/IPv6 based cluster control-plane & data-plan foundation pattern:

+

Dual Stack VPC, EKS Cluster in IPv6 Mode, control plane ULA, data plane IPv6 GUA for EC2 & Pods

+

Overview

+

EKS/IPv6 is only supported in prefix mode (VPC-CNI Plug-in ENI IP assign mode). Learn more on Prefix +Mode.

+
+

Prefix assignment only works on Nitro-based EC2 instances, hence EKS/IPv6 is only supported when the cluster data-plane uses EC2 Nitro-based instances.

+
+

In simple words an IPv6 prefix of /80 (Per worker-node) will yield ~10^14 IPv6 addresses, the limiting factor will no longer be IPs but Pod density (Resources wise).

+

IPv6 prefix assignment only occurs at the EKS worker-node bootstrap time. +This behaviour is known to mitigate scenarios where high Pod churn EKS/IPv4 clusters are often delayed in Pod scheduling due to throttled API calls generated by the VPC CNI plug-in (ipamd) aimed to allocate Private IPv4 addresses in a timely fashion. It is also known to make the VPC-CNI plug-in advanced knobs tuning WARM_IP/ENI, MINIMUM_IP unnecessarily.

+

The following diagram zooms into an IPv6 worker-node Elastic Network Interface (ENI):

+

illustration of worker subnet, including primary ENI with multiple IPv6 Addresses

+

Every EKS worker-node is assigned with IPv4 and IPv6 addresses, along with corresponding DNS entries. For a given worker-node, only a single IPv4 address from the dual-stack subnet is consumed. EKS support for IPv6 enables you to communicate with IPv4 endpoints (AWS, on-premise, internet) through a highly opinionated egress-only IPv4 model. EKS implements a host-local CNI plugin, secondary to the VPC CNI plugin, which allocates and configures an IPv4 address for a Pod. The CNI plugin configures a host-specific non-routable IPv4 address for a Pod from the 169.254.172.0/22 range. The IPv4 address assigned to the Pod is unique to the worker-node and is not advertised beyond the worker-node. 169.254.172.0/22 provides up to 1024 unique IPv4 addresses which can support large instance types.

+

The following diagram depict the flow of an IPv6 Pod connecting to an IPv4 endpoint outside the cluster boundary (non-internet):

+

EKS/IPv6, IPv4 egress-only flow

+

In the above diagram Pods will perform a DNS lookup for the endpoint and, upon receiving an IPv4 “A” response, Pod’s node-only unique IPv4 address is translated through source network address translation (SNAT) to the Private IPv4 (VPC) address of the primary network interface attached to the EC2 Worker-node.

+

EKS/IPv6 Pods will also need to connect to IPv4 endpoints over the internet using public IPv4 Addresses, to achieve that a similar flow exists. +The following diagram depict the flow of an IPv6 Pod connecting to an IPv4 endpoint outside the cluster boundary (internet routable):

+

EKS/IPv6, IPv4 Internet egress-only flow

+

In the above diagram Pods will perform a DNS lookup for the endpoint and, upon receiving an IPv4 “A” response, Pod’s node-only unique IPv4 address is translated through source network address translation (SNAT) to the Private IPv4 (VPC) address of the primary network interface attached to the EC2 Worker-node. The Pod IPv4 Address (Source IPv4: EC2 Primary IP) is then routed to the IPv4 NAT Gateway where the EC2 Primary IP is translated (SNAT) into a valid internet routable IPv4 Public IP Address (NAT Gateway Assigned Public IP).

+

Any Pod-to-Pod communication across the nodes always uses an IPv6 address. VPC CNI configures iptables to handle IPv6 while blocking any IPv4 connections.

+

Kubernetes services will receive only IPv6 addresses (ClusterIP) from Unique Local IPv6 Unicast Addresses (ULA). The ULA Service CIDR for an IPv6 cluster is automatically assigned during EKS cluster creation stage and cannot be modified. The following diagram depict the Pod to Kubernetes Service flow:

+

EKS/IPv6, IPv6 Pod to IPv6 k8s service (ClusterIP ULA) flow

+

Services are exposed to the internet using an AWS load balancer. The load balancer receives public IPv4 and IPv6 addresses, a.k.a dual-stack load balancer. For IPv4 clients accessing IPv6 cluster kubernetes services, the load balancer does IPv4 to IPv6 translation.

+

Amazon EKS recommends running worker nodes and Pods in private subnets. You can create public load balancers in the public subnets that load balance traffic to Pods running on nodes that are in private subnets. +The following diagram depict an internet IPv4 user accessing an EKS/IPv6 Ingress based service:

+

Internet IPv4 user to EKS/IPv6 Ingress service

+
+

Note: The above pattern requires to deploy the most recent version of the AWS load balancer controller

+
+

EKS Control Plane <-> Data Plane communication

+

EKS will provision Cross-Account ENIs (X-ENIs) in dual stack mode (IPv4/IPv6). Kubernetes node components such as kubelet and kube-proxy are configured to support dual stack. Kubelet and kube-proxy run in a hostNetwork mode and bind to both IPv4 and IPv6 addresses attached to the primary network interface of a node. The Kubernetes api-server communicates to Pods and node components via the X-ENIs is IPv6 based. Pods communicate with the api-servers via the X-ENIs, and Pod to api-server communication always uses IPv6 mode.

+

illustration of cluster including X-ENIs

+

Recommendations

+

Maintain Access to IPv4 EKS APIs

+

EKS APIs are accessible by IPv4 only. This also includes the Cluster API Endpoint. You will not be able to access cluster endpoints and APIs from an IPv6 only network. It is required that your network supports (1) an IPv6 transition mechanism such as NAT64/DNS64 that facilitates communication between IPv6 and IPv4 hosts and (2) a DNS service that supports translations of IPv4 endpoints.

+

Schedule Based on Compute Resources

+

A single IPv6 prefix is sufficient to run many Pods on a single node. This also effectively removes ENI and IP limitations on the maximum number of Pods on a node. Although IPv6 removes direct dependency on max-Pods, when using prefix attachments with smaller instance types like the m5.large, you’re likely to exhaust the instance’s CPU and memory resources long before you exhaust its IP addresses. You must set the EKS recommended maximum Pod value by hand if you are using self-managed node groups or a managed node group with a custom AMI ID.

+

You can use the following formula to determine the maximum number of Pods you can deploy on a node for a IPv6 EKS cluster.

+
    +
  • +

    ((Number of network interfaces for instance type (number of prefixes per network interface-1)* 16) + 2

    +
  • +
  • +

    ((3 ENIs)((10 secondary IPs per ENI-1) 16)) + 2 = 460 (real)

    +
  • +
+

Managed node groups automatically calculate the maximum number of Pods for you. Avoid changing EKS’s recommended value for the maximum number of Pods to avoid Pod scheduling failures due to resource limitations.

+

Evaluate Purpose of Existing Custom Networking

+

If custom networking is currently enabled, Amazon EKS recommends re-evaluating your need for it with IPv6. If you chose to use custom networking to address the IPv4 exhaustion issue, it is no longer necessary with IPv6. If you are utilizing custom networking to satisfy a security requirement, such as a separate network for nodes and Pods, you are encouraged to submit an EKS roadmap request.

+

Fargate Pods in EKS/IPv6 Cluster

+

EKS supports IPv6 for Pods running on Fargate. Pods running on Fargate will consume IPv6 and VPC Routable Private IPv4 addresses carved from the VPC CIDR ranges (IPv4&IPv6). In simple words your EKS/Fargate Pods cluster wide density will be limited to the available IPv4 and IPv6 addresses. It is recommended to size your dual-stack subnets/VPC CIDRs for future growth. You will not be able to schedule new Fargate Pods if the underlying subnet does not contain an available IPv4 address, irrespective of IPv6 available addresses.

+

Deploy the AWS Load Balancer Controller (LBC)

+

The upstream in-tree Kubernetes service controller does not support IPv6. We recommend using the most recent version of the AWS Load Balancer Controller add-on. The LBC will only deploy a dual-stack NLB or a dual-stack ALB upon consuming corresponding kubernetes service/ingress definition annotated with: "alb.ingress.kubernetes.io/ip-address-type: dualstack" and "alb.ingress.kubernetes.io/target-type: ip"

+

AWS Network Load Balancer does not support dual-stack UDP protocol address types. If you have strong requirements for low-latency, real-time streaming, online gaming, and IoT, we recommend running IPv4 clusters. To learn more about managing health checks for UDP services, please refer to “How to route UDP traffic into Kubernetes”.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/ipv6/ipv4-internet-to-eks-ipv6.png b/networking/ipv6/ipv4-internet-to-eks-ipv6.png new file mode 100644 index 000000000..c106e1bc8 Binary files /dev/null and b/networking/ipv6/ipv4-internet-to-eks-ipv6.png differ diff --git a/networking/ipvs/index.html b/networking/ipvs/index.html new file mode 100644 index 000000000..768864694 --- /dev/null +++ b/networking/ipvs/index.html @@ -0,0 +1,2281 @@ + + + + + + + + + + + + + + + + + + + + + + + Running kube-proxy in IPVS Mode - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Running kube-proxy in IPVS Mode

+

EKS in IP Virtual Server (IPVS) mode solves the network latency issue often seen when running large clusters with over 1,000 services with kube-proxy running in legacy iptables mode. This performance issue is the result of sequential processing of iptables packet filtering rules for each packet. This latency issue has been addressed in nftables, the successor to iptables. However, as of the time of this writing, kube-proxy is still under development to make use of nftables. To get around this issue, you can configure your cluster to run kube-proxy in IPVS mode.

+

Overview

+

IPVS, which has been GA since Kubernetes version 1.11, uses hash tables rather than linear searching to process packets, providing efficiency for clusters with thousands of nodes and services. IPVS was designed for load balancing, making it a suitable solution for Kubernetes networking performance issues.

+

IPVS offers several options for distributing traffic to backend pods. Detailed information for each option can be found in the official Kubernetes documentation, but a simple list is shown below. Round Robin and Least Connections are among the most popular choices for IPVS load balancing options in Kubernetes. +

- rr (Round Robin)
+- wrr (Weighted Round Robin)
+- lc (Least Connections)
+- wlc (Weighted Least Connections)
+- lblc (Locality Based Least Connections)
+- lblcr (Locality Based Least Connections with Replication)
+- sh (Source Hashing)
+- dh (Destination Hashing)
+- sed (Shortest Expected Delay)
+- nq (Never Queue)
+

+

Implementation

+

Only a few steps are required to enable IPVS in your EKS cluster. The first thing you need to do is ensure your EKS worker node images have the Linux Virtual Server administration ipvsadm package installed. To install this package on a Fedora based image, such as Amazon Linux 2023, you can run the following command on the worker node instance. +

sudo dnf install -y ipvsadm
+
+On a Debian based image, such as Ubuntu, the installation command would look like this. +
sudo apt-get install ipvsadm
+

+

Next, you need to load the kernel modules for the IPVS configuration options listed above. We recommend writing these modules to a file inside of the /etc/modules-load.d/ directory so that they survive a reboot. +

sudo sh -c 'cat << EOF > /etc/modules-load.d/ipvs.conf
+ip_vs
+ip_vs_rr
+ip_vs_wrr
+ip_vs_lc
+ip_vs_wlc
+ip_vs_lblc
+ip_vs_lblcr
+ip_vs_sh
+ip_vs_dh
+ip_vs_sed
+ip_vs_nq
+nf_conntrack
+EOF'
+
+You can run the following command to load these modules on a machine that is already running. +
sudo modprobe ip_vs 
+sudo modprobe ip_vs_rr
+sudo modprobe ip_vs_wrr
+sudo modprobe ip_vs_lc
+sudo modprobe ip_vs_wlc
+sudo modprobe ip_vs_lblc
+sudo modprobe ip_vs_lblcr
+sudo modprobe ip_vs_sh
+sudo modprobe ip_vs_dh
+sudo modprobe ip_vs_sed
+sudo modprobe ip_vs_nq
+sudo modprobe nf_conntrack
+

+
+

Note

+

It is highly recommended to execute these worker node steps as part of you worker node's bootstrapping process via user data script or in any build scripts executed to build a custom worker node AMI.

+
+

Next, you will configure your cluster's kube-proxy DaemonSet to run in IPVS mode. This is done by setting the kube-proxy mode to ipvs and the ipvs scheduler to one of the load balancing options listed above, for example: rr for Round Robin.

+
+

Warning

+

This is a disruptive change and should be performed in off-hours. We recommend making these changes during initial EKS cluster creation to minimize impacts.

+
+

You can issue an AWS CLI command to enable IPVS by updating the kube-proxy EKS Add-on. +

aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name kube-proxy \
+  --configuration-values '{"ipvs": {"scheduler": "rr"}, "mode": "ipvs"}' \
+  --resolve-conflicts OVERWRITE
+
+Or you can do this by modifying the kube-proxy-config ConfigMap in your cluster. +
kubectl -n kube-system edit cm kube-proxy-config
+
+Find the scheduler setting under ipvs and set the value to one of the IPVS load balancing options listed above, for example: rr for Round Robin. +Find the mode setting, which defaults to iptables, and change the value to ipvs. +The result of either option should look similar to the configuration below. +
  iptables:
+    masqueradeAll: false
+    masqueradeBit: 14
+    minSyncPeriod: 0s
+    syncPeriod: 30s
+  ipvs:
+    excludeCIDRs: null
+    minSyncPeriod: 0s
+    scheduler: "rr"
+    syncPeriod: 30s
+  kind: KubeProxyConfiguration
+  metricsBindAddress: 0.0.0.0:10249
+  mode: "ipvs"
+  nodePortAddresses: null
+  oomScoreAdj: -998
+  portRange: ""
+  udpIdleTimeout: 250ms
+

+

If your worker nodes were joined to your cluster prior to making these changes, you will need to restart the kube-proxy DaemonSet. +

kubectl -n kube-system rollout restart ds kube-proxy
+

+

Validation

+

You can validate that your cluster and worker nodes are running in IPVS mode by issuing the following command on one of your worker nodes. +

sudo ipvsadm -L
+

+

At a minimum, you should see a result similar to the one below, showing entries for the Kubernetes API Server service at 10.100.0.1 and the CoreDNS service at 10.100.0.10. +

IP Virtual Server version 1.2.1 (size=4096)
+Prot LocalAddress:Port Scheduler Flags
+  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
+TCP  ip-10-100-0-1.us-east-1. rr
+  -> ip-192-168-113-81.us-eas Masq        1      0          0
+  -> ip-192-168-162-166.us-ea Masq        1      1          0
+TCP  ip-10-100-0-10.us-east-1 rr
+  -> ip-192-168-104-215.us-ea Masq        1      0          0
+  -> ip-192-168-123-227.us-ea Masq        1      0          0
+UDP  ip-10-100-0-10.us-east-1 rr
+  -> ip-192-168-104-215.us-ea Masq        1      0          0
+  -> ip-192-168-123-227.us-ea Masq        1      0          0
+

+
+

Note

+

This example output comes from an EKS cluster with a service IP address range of 10.100.0.0/16.

+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/loadbalancing/loadbalancing/index.html b/networking/loadbalancing/loadbalancing/index.html new file mode 100644 index 000000000..b5a4fc4d1 --- /dev/null +++ b/networking/loadbalancing/loadbalancing/index.html @@ -0,0 +1,2620 @@ + + + + + + + + + + + + + + + + + + + + + + + Load Balancing - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Load Balancing

+

Load Balancers receive incoming traffic and distribute it across targets of the +intended application hosted in an EKS Cluster. This improves the resilience of the +application. +When deployed in an EKS Cluster the AWS Load Balancer controller will +create and manage AWS Elastic Load Balancers for that cluster. +When a Kubernetes Service of type LoadBalancer is created, the AWS Load Balancer controller creates a Network Load Balancer (NLB) which load balances received traffic at Layer 4 of the OSI model. While when a Kubernetes Ingress object is created, the AWS Load Balancer Controller creates an Application Load Balancer (ALB) which load balances traffic at Layer 7 of the OSI model.

+

Choosing Load Balancer Type

+

The AWS Elastic Load Balancing portfolio supports the following load balancers: Application Load Balancers (ALB), Network Load Balancers (NLB), Gateway Load Balancers (GWLB), and Classic Load Balancers (CLB). This best practices section will focus on the ALB and NLB which are the two which are most relevant for EKS Clusters.

+

The main consideration in choosing the type of load balancer is the workload requirements.

+

For more detailed information and as a reference for all AWS Load balancers, see Product Comparisons

+

Choose the Application Load Balancer (ALB) if your workload is HTTP/HTTPS

+

If a workloads requires load balancing at Layer 7 of the OSI Model, the AWS Load +Balancer Controller can be used to provision an ALB; we cover the provisioning in +the following section. The ALB is controlled and configured by the Ingress resource +mentioned earlier and routes HTTP or HTTPS traffic to different Pods within the cluster. +The ALB provides customers with the flexibility to change the application traffic +routing algorithm; the default routing algorithm is round robin with the least outstanding requests routing algorithm also an alternative.

+

Choose the Network Load Balancer (NLB) if your workload is TCP, or if your workload requires Source IP Preservation of Clients

+

A Network Load Balancer functions at the fourth layer (Transport) of the Open Systems Interconnection (OSI) model. It is suited for TCP & UDP based workloads. Network Load Balancer also by default preserves the Source IP of address of the clients when presenting the traffic to the pod.

+

Choose the Network Load Balancer (NLB) if your workload cannot utilize DNS

+

Another key reason to use the NLB is if your clients cannot utilize DNS. In this case, the NLB may be a better fit for your workload as the IPs on a Network Load Balancer are static. While clients are recommended to use DNS when resolving Domain Names to IP Addresses when connecting to Load Balancers, if a client's application doesn't support DNS resolution and only accepts hard-coded IPs then an NLB is a better fit as the IPs are static and remain same for the life of the NLB.

+

Provisioning Load Balancers

+

After determining the Load Balancer best suited for your workloads, customers have a +number of options for provisioning a load balancer.

+

Provision Load Balancers by deploying the AWS Load Balancer Controller

+

There are two key methods of provisioning load balancers within an EKS Cluster.

+
    +
  • Leveraging the AWS Cloud Provider Load balancer Controller (legacy)
  • +
  • Leveraging the AWS Load Balancer Controller (recommended)
  • +
+

By default, Kubernetes Service resources of type LoadBalancer get reconciled by the Kubernetes Service Controller that is built into the CloudProvider component of the kube-controller-manager or the cloud-controller-manager (also known as the in-tree controller).

+

The configuration of the provisioned load balancer is controlled by annotations that are added to the manifest for the Service or Ingress object and are different when using the AWS Load Balancer Controller than they are when using the AWS cloud provider load balancer controller.

+

The AWS Cloud Provider Load balancer Controller is legacy and is currently only receiving critical bug fixes. When you create a Kubernetes Service of type LoadBalancer, the AWS cloud provider load balancer controller creates AWS Classic Load Balancers by default, but can also create AWS Network Load Balancers with the correct annotation.

+

The AWS Load Balancer Controller (LBC) has to be installed in the EKS clusters and provisions AWS load balancers that point to cluster Service or Ingress resources.

+

In order for the LBC to manage the reconciliation of Kubernetes Service resources of type LoadBalancer, you need to offload the reconciliation from the in-tree controller to the LBC, explicitly. +With LoadBalancerClassWith service.beta.kubernetes.io/aws-load-balancer-type annotation

+

Choosing Load Balancer Target-Type

+

Register Pods as targets using IP Target-Type

+

An AWS Elastic Load Balancer: Network & Application, sends received traffic to registered targets in a target group. For an EKS Cluster there are 2 types of targets you can register in the target group: Instance & IP, which target type is used has implications on what gets registered and how traffic is routed from the Load Balancer to the pod. +By default the AWS Load Balancer controller will register targets using ‘Instance’ type and this target will be the Worker Node’s IP and NodePort, implication of this include:

+
    +
  • Traffic from the Load Balancer will be forwarded to the Worker Node on the NodePort, this gets processed by iptables rules (configured by kube-proxy running on the node), and gets forwarded to the Service on its ClusterIP (still on the node), finally the Service randomly selects a pod registered to it and forwards the traffic to it. This flow involves multiple hops and extra latency can be incurred especially because the Service will sometimes select a pod running on another worker node which might also be in another AZ.
  • +
  • Because the Load Balancer registers the Worker Node as its target this means its health check which gets sent to the target will not be directly received by the pod but by the Worker Node on its NodePort and health check traffic will follow the same path described above.
  • +
  • Monitoring and Troubleshooting is more complex since traffic forwarded by the Load Balancer isn’t directly sent to the pods and you’d have to carefully correlate the packet received on the Worker Node to to the Service ClusterIP and eventually the pod to have full end-to-end visibility into the packet’s path for proper troubleshooting.
  • +
+

targettype_instance.png

+

By contrast if you configure the target type as ‘IP’ as we recommend the implication will be the following:

+
    +
  • Traffic from the Load Balancer will be forwarded directly to the pod, this simplifies the network path as it bypasses the previous extra hops of Worker Nodes and Service Cluster IP, it reduces latency that would otherwise have been incurred if the Service forwarded traffic to a pod in another AZ and lastly it removes the iptables rules overhead processing on the Worker Nodes.
  • +
  • The Load Balancer’s health check is directly received and responded to by the pod, this means the target status ‘healthy’ or ‘unhealthy’ are a direct representation of the pod’s health status.
  • +
  • Monitoring and Troubleshooting is easier and any tool used that captures packet IP address will directly reveal the bi-directional traffic between the Load Balancer and the pod in its source and destination fields.
  • +
+

targettype_ip.png

+

To create an AWS Elastic Load Balancing that uses IP Targets you add:

+
    +
  • alb.ingress.kubernetes.io/target-type: ip annotation to your Ingress’ manifest when configuring your Kubernetes Ingress (Application Load Balancer)
  • +
  • service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip annotation to your Service’s Manifest when configuring your Kubernetes Service of type LoadBalancer (Network Load Balancer).
  • +
+

Availability and Pod Lifecycle

+

During an application upgrade you must make sure that your application is always available to process requests so users do not experience any downtime. One common challenge in this scenario is syncing the availability status of your workloads between the Kubernetes layer, and the infrastructure, for instance external Load Balancers. The next few sections highlight the best practices to address such scenarios.

+

Note : The explanations below are based on the EndpointSlices as it is the recommended replacement for the Endpoints in Kubernetes. The differences between the two are negligible in the context of the scenarios covered below. AWS Load Balancer Controller by default consumes Endpoints, you can enable EndpointSlices by enabling the enable-endpoint-sliceflag on the controller.

+

Use health checks

+

Kubernetes by default runs the process health check where the kubelet process on the node verifies whether or not the main process of the container is running. If not then by default it restarts that container. However you can also configure Kubernetes probes to identify when a container process is running but in a deadlock state, or whether an application has started successfully or not. Probes can be based on exec, grpc, httpGet and tcpSocket mechanisms. Based on the type and result of the probe the container can be restarted.

+

Please see the [Pod Creation] in the Appendix section below to revisit the sequence of events in Pod creation process.

+

Use readiness probes

+

By default when all the containers within a Pod are running the Pod condition is considered to be “Ready”. However the application may still not be able to process client requests. For example the application may need to pull some data or configuration from an external resource to be able to process requests. In such a state you would neither want to kill the application nor forward any requests to it. Readiness probe enables you to make sure that the Pod is not considered to be “Ready”, meaning that it will not be added to the EndpointSlice object, until the probe result is success. On the other hand if the probe fails further down the line then the Pod is removed from the EndpointSlice object. You can configure a readiness probe in the Pod manifest for each container. kubelet process on each node runs the readiness probe against the containers on that node.

+

Utilize Pod readiness gates

+

One aspect of the readiness probe is the fact that there is no external feedback/influence mechanism in it, kubelet process on the node executes the probe and defines the state of the probe. This does not have any impact on the requests between microservices themselves in the Kubernetes layer (east west traffic) since the EndpointSlice Controller keeps the list of endpoints (Pods) always up to date. Why and when would you need an external mechanism then ?

+

When you expose your applications using Kubernetes Service type of Load Balancer or Kubernetes Ingress (for north - south traffic) then the list of Pod IPs for the respective Kubernetes Service must be propagated to the external infrastructure load balancer so that the load balancer also has an up to date list targets. AWS Load Balancer Controller bridges the gap here. When you use AWS Load Balancer Controller and leverage target group: IP , just like kube-proxy the AWS Load Balancer Controller also receives an update (via watch) and then it communicates with the ELB API to configure and start registering the Pod IP as a target on the ELB.

+

When you perform a rolling update of a Deployment, new Pods get created, and as soon as a new Pod’s condition is “Ready” an old/existing Pod gets terminated. During this process, the Kubernetes EndpointSlice object is updated faster than the time it takes the ELB to register the new Pods as targets, see target registration. For a brief time you could have a state mismatch between the Kubernetes layer and the infrastructure layer where client requests could be dropped. During this period within the Kubernetes layer new Pods would be ready to process requests but from ELB point of view they are not.

+

Pod Readiness Gates enables you to define additional requirements that must be met before the Pod condition is considered to be “Ready”. In the case of AWS ELB, the AWS Load Balancer Controller monitors the status of the target (the Pod) on the AWS ELB and once the target registration completes and its status turns “Healthy” then the controller updates the Pod’ s condition to “Ready”. With this approach you influence the Pod condition based on the state of the external network, which is the target status on the AWS ELB. Pod Readiness Gates is crucial in rolling update scenarios as it enables you to prevent the rolling update of a deployment from terminating old pods until the newly created Pods target status turn “Healthy” on the AWS ELB.

+

Gracefully shutdown applications

+

Your application should respond to a SIGTERM signal by starting its graceful shutdown so that clients do not experience any downtime. What this means is your application should run cleanup procedures such as saving data, closing file descriptors, closing database connections, completing in-flight requests gracefully and exit in a timely manner to fulfill the Pod termination request. You should set the grace period to long enough so that cleanup can finish. To learn how to respond to the SIGTERM signal you can refer to the resources of the respective programming language that you use for your application.

+

If your application is unable to shutdown gracefully upon receipt of a SIGTERM signal or if it ignores/does not receive the signal, then you can instead leverage PreStop hook to initiate a graceful shutdown of the application. Prestop hook is executed immediately before the SIGTERM signal is sent and it can perform arbitrary operations without having to implement those operations in the application code itself.

+

The overall sequence of events is shown in the diagram below. Note: regardless of the result of graceful shutdown procedure of the application, or the result of the PreStop hook, the application containers are eventually terminated at the end of the grace period via SIGKILL.

+

podterminationlifecycle.png

+

Please see the [Pod Deletion] in the Appendix section below to revisit the sequence of events in Pod deletion process.

+

Gracefully handle the client requests

+

The sequence of events in Pod deletion is different than Pod creation. When a Pod is created kubelet updates the Pod IP in Kubernetes API and only then the EndpointSlice object is updated. On the other hand when a Pod is being terminated Kubernetes API notifies both the kubelet and EndpointSlice controller at the same time. Carefully inspect the following diagram which shows the sequence of events.

+

statepropagation.png

+

The way the state propagates all the way from API server down to the iptables rules on the nodes explained above creates an interesting race condition. Because there is a high chance that the container receives the SIGKILL signal much earlier than the kube-proxy on each node updates the local iptables rules. In such an event two scenarios worth mentioning are :

+
    +
  • If your application immediately and bluntly drops the in-flight requests and connections upon receipt of SIGTERM which means the clients would see 50x errors all over the place.
  • +
  • Even if your application ensures that all in-flight requests and connections are processed completely upon receipt of SIGTERM, during the grace period, new client requests would still be sent to the application container because iptables rules may still not be updated yet. Until the cleanup procedure closes the server socket on the container those new requests will result in new connections. When the grace period ends those connections, which are established after the SIGTERM, at that time are dropped unconditionally since SIGKILL is sent.
  • +
+

Setting the grace period in Pod spec long enough may address this challenge but depending on the propagation delay and the number of actual client requests it is hard to anticipate the time it takes for the application to close out the connections gracefully. Hence the not so perfect but most feasible approach here is to use a PreStop hook to delay the SIGTERM signal until the iptables rules are updated to make sure that no new client requests are sent to the application rather, only existing connections carry on. PreStop hook can be a simple Exec handler such as sleep 10.

+

The behavior and the recommendation mentioned above would be equally applicable when you expose your applications using Kubernetes Service type of Load Balancer or Kubernetes Ingress (for north - south traffic) using AWS Load Balancer Controller and leverage target group: IP . Because just like kube-proxy the AWS Load Balancer Controller also receives an update (via watch) on the EndpointSlice object and then it communicates with the ELB API to start deregistering the Pod IP from the ELB. However depending on the load on Kubernetes API or the ELB API this can also take time and the SIGTERM may have already been sent to the application long ago. Once the ELB starts deregistering the target it stops sending requests to that target so the application will not receive any new requests and the ELB also starts a Deregistration delay which is 300 seconds by default. During the deregistration process the target is draining where basically the ELB waits for the in-flight requests/existing connections to that target to drain. Once the deregistration delay expires then the target is unused and any in-flight requests to that target is forcibly dropped.

+

Use Pod disruption budget

+

Configure a Pod Disruption Budget (PDB) for your applications. PDBlimits the number of Pods of a replicated application that are down simultaneously from voluntary disruptions. It ensures that a minimum number or percentage of pods remain available in a StatefulSet or Deployment. For example, a quorum-based application needs to ensure that the number of replicas running is never brought below the number needed for a quorum. Or a web front end might ensure that the number of replicas serving load never falls below a certain percentage of the total. PDB will protect the application against actions such as nodes being drained, or new versions of Deployments being rolled out. Keep in mind that PDB’s will not protect the application against involuntary disruptions such as a failure of the node operating system or loss of network connectivity. For more information please refer to the Specifying a Disruption Budget for your Application in Kubernetes documentation.

+

References

+ +

Appendix

+

Pod Creation

+

It is imperative to understand what is the sequence of events in a scenario where a Pod is deployed and then it becomes healthy/ready to receive and process client requests. Let’s talk about the sequence of events.

+
    +
  1. A Pod is created on the Kubernetes control plane (i.e. by a kubectl command, or Deployment update, or scaling action).
  2. +
  3. kube-scheduler assigns the Pod to a node in the cluster.
  4. +
  5. The kubelet process running on the assigned node receives the update (via watch) and communicates with the container runtime to start the containers defined in the Pod spec.
  6. +
  7. When the containers starts running, the kubelet updates the Pod condition as Ready in the Pod object in the Kubernetes API.
  8. +
  9. The EndpointSlice Controller receives the Pod condition update (via watch) and adds the Pod IP/Port as a new endpoint to the EndpointSlice object (list of Pod IPs) of the respective Kubernetes Service.
  10. +
  11. kube-proxy process on each node receives the update (via watch) on the EndpointSlice object and then updates the iptables rules on each node, with the new Pod IP/port.
  12. +
+

Pod Deletion

+

Just like Pod creation, it is imperative to understand what is the sequence of events during Pod deletion. Let’ s talk about the sequence of events.

+
    +
  1. A Pod deletion request is sent to the Kubernetes API server (i.e. by a kubectl command, or Deployment update, or scaling action).
  2. +
  3. Kubernetes API server starts a grace period, which is 30 seconds by default, by setting the deletionTimestamp field in the Pod object. (Grace period can be configured in Pod spec through terminationGracePeriodSeconds)
  4. +
  5. The kubelet process running on the node receives the update (via watch) on the Pod object and sends a SIGTERM signal to process identifier 1 (PID 1) inside each container in that Pod. It then watches the terminationGracePeriodSeconds.
  6. +
  7. The EndpointSlice Controller also receives the update (via watch) from Step 2 and sets the endpoint condition to “terminating” in the EndpointSlice object (list of Pod IPs) of the respective Kubernetes Service.
  8. +
  9. kube-proxy process on each node receives the update (via watch) on the EndpointSlice object then iptables rules on each node get updated by the kube-proxy to stop forwarding clients requests to the Pod.
  10. +
  11. When the terminationGracePeriodSeconds expires then the kubelet sends SIGKILL signal to the parent process of each container in the Pod and forcibly terminates them.
  12. +
  13. TheEndpointSlice Controller removes the endpoint from the EndpointSlice object.
  14. +
  15. API server deletes the Pod object.
  16. +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/loadbalancing/podterminationlifecycle.png b/networking/loadbalancing/podterminationlifecycle.png new file mode 100644 index 000000000..d536bdaa2 Binary files /dev/null and b/networking/loadbalancing/podterminationlifecycle.png differ diff --git a/networking/loadbalancing/statepropagation.png b/networking/loadbalancing/statepropagation.png new file mode 100644 index 000000000..e8bd8c7de Binary files /dev/null and b/networking/loadbalancing/statepropagation.png differ diff --git a/networking/loadbalancing/target_type_instance.png b/networking/loadbalancing/target_type_instance.png new file mode 100644 index 000000000..04bc4aad2 Binary files /dev/null and b/networking/loadbalancing/target_type_instance.png differ diff --git a/networking/loadbalancing/target_type_ip.png b/networking/loadbalancing/target_type_ip.png new file mode 100644 index 000000000..bb99e1e7f Binary files /dev/null and b/networking/loadbalancing/target_type_ip.png differ diff --git a/networking/monitoring/conntrack.png b/networking/monitoring/conntrack.png new file mode 100644 index 000000000..6f6d22feb Binary files /dev/null and b/networking/monitoring/conntrack.png differ diff --git a/networking/monitoring/cw_metrics.png b/networking/monitoring/cw_metrics.png new file mode 100644 index 000000000..1d6557461 Binary files /dev/null and b/networking/monitoring/cw_metrics.png differ diff --git a/networking/monitoring/explore_metrics.png b/networking/monitoring/explore_metrics.png new file mode 100644 index 000000000..0517a351c Binary files /dev/null and b/networking/monitoring/explore_metrics.png differ diff --git a/networking/monitoring/index.html b/networking/monitoring/index.html new file mode 100644 index 000000000..9cad8bba2 --- /dev/null +++ b/networking/monitoring/index.html @@ -0,0 +1,2503 @@ + + + + + + + + + + + + + + + + + + + + + + + Monitoring for Network performance issues - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Monitoring EKS workloads for Network performance issues

+

Monitoring CoreDNS traffic for DNS throttling issues

+

Running DNS intensive workloads can sometimes experience intermittent CoreDNS failures due to DNS throttling, and this can impact applications where you may encounter occasional UnknownHostException errors.

+

The Deployment for CoreDNS has an anti-affinity policy that instructs the Kubernetes scheduler to run instances of CoreDNS on separate worker nodes in the cluster, i.e. it should avoid co-locating replicas on the same worker node. This effectively reduces the number of DNS queries per network interface because traffic from each replica is routed through a different ENI. If you notice that DNS queries are being throttled because of the 1024 packets per second limit, you can 1) try increasing the number of CoreDNS replicas or 2) implement NodeLocal DNSCache. See Monitor CoreDNS Metrics for further information.

+

Challenge

+
    +
  • Packet drop happens in seconds and it can be tricky for us to properly monitor these patterns to determine if DNS throttling is actually happening.
  • +
  • DNS queries are throttled at the elastic network interface level. So, throttled queries don't appear in the query logging.
  • +
  • Flow logs do not capture all IP traffic. E.g. Traffic generated by instances when they contact the Amazon DNS server. If you use your own DNS server, then all traffic to that DNS server is logged
  • +
+

Solution

+

An easy way to identify the DNS throttling issues in worker nodes is by capturing linklocal_allowance_exceeded metric. The linklocal_allowance_exceeded is number of packets dropped because the PPS of the traffic to local proxy services exceeded the maximum for the network interface. This impacts traffic to the DNS service, the Instance Metadata Service, and the Amazon Time Sync Service. Instead of tracking this event real-time, we can stream this metric to Amazon Managed Service for Prometheus as well and can have them visualized in Amazon Managed Grafana

+

Monitoring DNS query delays using Conntrack metrics

+

Another metric that can help in monitoring the CoreDNS throttling / query delay are conntrack_allowance_available and conntrack_allowance_exceeded. +Connectivity failures caused by exceeding Connections Tracked allowances can have a larger impact than those resulting from exceeding other allowances. When relying on TCP to transfer data, packets that are queued or dropped due to exceeding EC2 instance network allowances, such as Bandwidth, PPS, etc., are typically handled gracefully thanks to TCP’s congestion control capabilities. Impacted flows will be slowed down, and lost packets will be retransmitted. However, when an instance exceeds its Connections Tracked allowance, no new connections can be established until some of the existing ones are closed to make room for new connections.

+

conntrack_allowance_available and conntrack_allowance_exceeded helps customers in monitoring the connections tracked allowance which varies for every instance. These network performance metrics give customers visibility into the number of packets queued or dropped when an instance’s networking allowances, such as Network Bandwidth, Packets-Per-Second (PPS), Connections Tracked, and Link-local service access (Amazon DNS, Instance Meta Data Service, Amazon Time Sync) are exceeded

+

conntrack_allowance_available is the number of tracked connections that can be established by the instance before hitting the Connections Tracked allowance of that instance type (supported for nitro-based instance only). +conntrack_allowance_exceeded is the number of packets dropped because connection tracking exceeded the maximum for the instance and new connections could not be established.

+

Other important Network performance metrics

+

Other important network performance metrics include:

+

bw_in_allowance_exceeded (ideal value of the metric should be zero) is the number of packets queued and/or dropped because the inbound aggregate bandwidth exceeded the maximum for the instance

+

bw_out_allowance_exceeded (ideal value of the metric should be zero) is the number of packets queued and/or dropped because the outbound aggregate bandwidth exceeded the maximum for the instance

+

pps_allowance_exceeded (ideal value of the metric should be zero) is the number of packets queued and/or dropped because the bidirectional PPS exceeded the maximum for the instance

+

Capturing the metrics to monitor workloads for network performance issues

+

The Elastic Network Adapter (ENA ) driver publishes network performance metrics discussed above from the instances where they are enabled. All the network performance metrics can be published to CloudWatch using the CloudWatch agent. Please refer to the blog for more information.

+

Let's now capture the metrics discussed above, store them in Amazon Managed Service for Prometheus and visualize using Amazon Managed Grafana

+

Prerequisites

+
    +
  • ethtool - Ensure the worker nodes have ethtool installed
  • +
  • An AMP workspace configured in your AWS account. For instructions, see Create a workspace in the AMP User Guide.
  • +
  • Amazon Managed Grafana Workspace
  • +
+

Deploying Prometheus ethtool exporter

+

The deployment contains a python script that pulls information from ethtool and publishes it in prometheus format.

+
kubectl apply -f https://raw.githubusercontent.com/Showmax/prometheus-ethtool-exporter/master/deploy/k8s-daemonset.yaml
+
+

Deploy the ADOT collector to scrape the ethtool metrics and store in Amazon Managed Service for Prometheus workspace

+

Each cluster where you install AWS Distro for OpenTelemetry (ADOT) must have this role to grant your AWS service account permissions to store metrics into Amazon Managed Service for Prometheus. Follow these steps to create and associate your IAM role to your Amazon EKS service account using IRSA:

+
eksctl create iamserviceaccount --name adot-collector --namespace default --cluster <CLUSTER_NAME> --attach-policy-arn arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess --attach-policy-arn arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess --attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy --region <REGION> --approve  --override-existing-serviceaccounts
+
+

Let's deploy the ADOT collector to scrape the metrcis from the prometheus ethtool exporter and store it in Amazon Managed Service for Prometheus

+

The following procedure uses an example YAML file with deployment as the mode value. This is the default mode and deploys the ADOT Collector similarly to a standalone application. This configuration receives OTLP metrics from the sample application and Amazon Managed Service for Prometheus metrics scraped from pods on the cluster

+
curl -o collector-config-amp.yaml https://raw.githubusercontent.com/aws-observability/aws-otel-community/master/sample-configs/operator/collector-config-amp.yaml
+
+

In collector-config-amp.yaml, replace the following with your own values: +* mode: deployment +* serviceAccount: adot-collector +* endpoint: "" +* region: "" +* name: adot-collector

+
kubectl apply -f collector-config-amp.yaml 
+
+

Once the adot collector is deployed, the metrics will be stored successfully in Amazon Prometheus

+

Configure alert manager in Amazon Managed Service for Prometheus to send notifications

+

You can use alert manager in Amazon Managed Service for Prometheus to set up alerting rules for critical alerts then you can send notifications to an Amazon SNS topic. Let's configure recording rules and alerting rules to check for the metrics discussed so far.

+

We will use the ACK Controller for Amazon Managed Service for Prometheus to provision the alerting and recording rules.

+

Let's deploy the ACL controller for the Amazon Managed Service for Prometheus service:

+
export SERVICE=prometheusservice
+export RELEASE_VERSION=`curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4`
+export ACK_SYSTEM_NAMESPACE=ack-system
+export AWS_REGION=us-east-1
+aws ecr-public get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin public.ecr.aws
+helm install --create-namespace -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller \
+oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION --set=aws.region=$AWS_REGION
+
+

Run the command and after a few moments you should see the following message:

+
You are now able to create Amazon Managed Service for Prometheus (AMP) resources!
+
+The controller is running in "cluster" mode.
+
+The controller is configured to manage AWS resources in region: "us-east-1"
+
+The ACK controller has been successfully installed and ACK can now be used to provision an Amazon Managed Service for Prometheus workspace.
+
+

Let's now create a yaml file for provisioning the alert manager defnition and rule groups. +Save the below file as rulegroup.yaml

+
apiVersion: prometheusservice.services.k8s.aws/v1alpha1
+kind: RuleGroupsNamespace
+metadata:
+   name: default-rule
+spec:
+   workspaceID: <Your WORKSPACE-ID>
+   name: default-rule
+   configuration: |
+     groups:
+     - name: ppsallowance
+       rules:
+       - record: metric:pps_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="pps_allowance_exceeded"}[30s])
+       - alert: PPSAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="pps_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})
+           description: "PPSAllowanceExceeded is greater than 0"
+     - name: bw_in
+       rules:
+       - record: metric:bw_in_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="bw_in_allowance_exceeded"}[30s])
+       - alert: BWINAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="bw_in_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})
+           description: "BWInAllowanceExceeded is greater than 0"
+     - name: bw_out
+       rules:
+       - record: metric:bw_out_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="bw_out_allowance_exceeded"}[30s])
+       - alert: BWOutAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="bw_out_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})
+           description: "BWoutAllowanceExceeded is greater than 0"            
+     - name: conntrack
+       rules:
+       - record: metric:conntrack_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="conntrack_allowance_exceeded"}[30s])
+       - alert: ConntrackAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="conntrack_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})
+           description: "ConnTrackAllowanceExceeded is greater than 0"
+     - name: linklocal
+       rules:
+       - record: metric:linklocal_allowance_exceeded
+         expr: rate(node_net_ethtool{device="eth0",type="linklocal_allowance_exceeded"}[30s])
+       - alert: LinkLocalAllowanceExceeded
+         expr: rate(node_net_ethtool{device="eth0",type="linklocal_allowance_exceeded"} [30s]) > 0
+         labels:
+           severity: critical
+
+         annotations:
+           summary: Packets dropped due to PPS rate allowance exceeded for local services  (instance {{ $labels.instance }})
+           description: "LinkLocalAllowanceExceeded is greater than 0"
+
+

Replace Your WORKSPACE-ID with the Workspace ID of the workspace you are using.

+

Let's now configure the alert manager definition. Save the below fie as alertmanager.yaml

+
apiVersion: prometheusservice.services.k8s.aws/v1alpha1  
+kind: AlertManagerDefinition
+metadata:
+  name: alert-manager
+spec:
+  workspaceID: <Your WORKSPACE-ID >
+  configuration: |
+    alertmanager_config: |
+      route:
+         receiver: default_receiver
+       receivers:
+       - name: default_receiver
+          sns_configs:
+          - topic_arn: TOPIC-ARN
+            sigv4:
+              region: REGION
+            message: |
+              alert_type: {{ .CommonLabels.alertname }}
+              event_type: {{ .CommonLabels.event_type }}     
+
+

Replace You WORKSPACE-ID with the Workspace ID of the new workspace, TOPIC-ARN with the ARN of an Amazon Simple Notification Service topic where you want to send the alerts, and REGION with the current region of the workload. Make sure that your workspace has permissions to send messages to Amazon SNS.

+

Visualize ethtool metrics in Amazon Managed Grafana

+

Let's visualize the metrics within the Amazon Managed Grafana and build a dashboard. Configure the Amazon Managed Service for Prometheus as a datasource inside the Amazon Managed Grafana console. For instructions, see Add Amazon Prometheus as a datasource

+

Let's explore the metrics in Amazon Managed Grafana now: +Click the explore button, and search for ethtool:

+

Node_ethtool metrics

+

Let's build a dashboard for the linklocal_allowance_exceeded metric by using the query rate(node_net_ethtool{device="eth0",type="linklocal_allowance_exceeded"}[30s]). It will result in the below dashboard.

+

linklocal_allowance_exceeded dashboard

+

We can clearly see that there were no packets dropped as the value is zero.

+

Let's build a dashboard for the conntrack_allowance_exceeded metric by using the query rate(node_net_ethtool{device="eth0",type="conntrack_allowance_exceeded"}[30s]). It will result in the below dashboard.

+

conntrack_allowance_exceeded dashboard

+

The metric conntrack_allowance_exceeded can be visualized in CloudWatch, provided you run a cloudwatch agent as described here. The resulting dashboard in CloudWatch will look like below:

+

CW_NW_Performance

+

We can clearly see that there were no packets dropped as the value is zero. If you are using Nitro-based instances, you can create a similar dashboard for conntrack_allowance_available and pro-actively monitor the connections in your EC2 instance. You can further extend this by configuring alerts in Amazon Managed Grafana to send notifications to Slack, SNS, Pagerduty etc.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/monitoring/linklocal.png b/networking/monitoring/linklocal.png new file mode 100644 index 000000000..a640a833b Binary files /dev/null and b/networking/monitoring/linklocal.png differ diff --git a/networking/prefix-mode/image-2.jpeg b/networking/prefix-mode/image-2.jpeg new file mode 100644 index 000000000..fd78b6571 Binary files /dev/null and b/networking/prefix-mode/image-2.jpeg differ diff --git a/networking/prefix-mode/image.png b/networking/prefix-mode/image.png new file mode 100644 index 000000000..e39140ced Binary files /dev/null and b/networking/prefix-mode/image.png differ diff --git a/networking/prefix-mode/index_linux/index.html b/networking/prefix-mode/index_linux/index.html new file mode 100644 index 000000000..5297f8cfc --- /dev/null +++ b/networking/prefix-mode/index_linux/index.html @@ -0,0 +1,2319 @@ + + + + + + + + + + + + + + + + + + + + + + + Prefix Mode for Linux - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Prefix Mode for Linux

+

Amazon VPC CNI assigns network prefixes to Amazon EC2 network interfaces to increase the number of IP addresses available to nodes and increase pod density per node. You can configure version 1.9.0 or later of the Amazon VPC CNI add-on to assign IPv4 and IPv6 CIDRs instead of assigning individual secondary IP addresses to network interfaces.

+

Prefix mode is enabled by default on IPv6 clusters and is the only option supported. The VPC CNI assigns a /80 IPv6 prefix to a slot on an ENI. Please refer to the IPv6 section of this guide for further information.

+

With prefix assignment mode, the maximum number of elastic network interfaces per instance type remains the same, but you can now configure Amazon VPC CNI to assign /28 (16 IP addresses) IPv4 address prefixes, instead of assigning individual IPv4 addresses to the slots on network interfaces. When ENABLE_PREFIX_DELEGATION is set to true VPC CNI allocates an IP address to a Pod from the prefix assigned to an ENI. Please follow the instructions mentioned in the EKS user guide to enable Prefix IP mode.

+

illustration of two worker subnets, comparing ENI secondary IPvs to ENIs with delegated prefixes

+

The maximum number of IP addresses that you can assign to a network interface depends on the instance type. Each prefix that you assign to a network interface counts as one IP address. For example, a c5.large instance has a limit of 10 IPv4 addresses per network interface. Each network interface for this instance has a primary IPv4 address. If a network interface has no secondary IPv4 addresses, you can assign up to 9 prefixes to the network interface. For each additional IPv4 address that you assign to a network interface, you can assign one less prefix to the network interface. Review the AWS EC2 documentation on IP addresses per network interface per instance type and assigning prefixes to network interfaces.

+

During worker node initialization, the VPC CNI assigns one or more prefixes to the primary ENI. The CNI pre-allocates a prefix for faster pod startup by maintaining a warm pool. The number of prefixes to be held in warm pool can be controlled by setting environment variables.

+
    +
  • WARM_PREFIX_TARGET, the number of prefixes to be allocated in excess of current need.
  • +
  • WARM_IP_TARGET, the number of IP addresses to be allocated in excess of current need.
  • +
  • MINIMUM_IP_TARGET, the minimum number of IP addresses to be available at any time.
  • +
  • WARM_IP_TARGET and MINIMUM_IP_TARGET if set will override WARM_PREFIX_TARGET.
  • +
+

As more Pods scheduled additional prefixes will be requested for the existing ENI. First, the VPC CNI attempts to allocate a new prefix to an existing ENI. If the ENI is at capacity, the VPC CNI attempts to allocate a new ENI to the node. New ENIs will be attached until the maximum ENI limit (defined by the instance type) is reached. When a new ENI is attached, ipamd will allocate one or more prefixes needed to maintain the WARM_PREFIX_TARGET, WARM_IP_TARGET, and MINIMUM_IP_TARGET setting.

+

flow chart of procedure for assigning IP to pod

+

Recommendations

+

Use Prefix Mode when

+

Use prefix mode if you are experiencing Pod density issue on the worker nodes. To avoid VPC CNI errors, we recommend examining the subnets for contiguous block of addresses for /28 prefix before migrate to prefix mode. Please refer “Use Subnet Reservations to Avoid Subnet Fragmentation (IPv4)” section for Subnet reservation details.

+

For backward compatibility, the max-pods limit is set to support secondary IP mode. To increase the pod density, please specify the max-pods value to Kubelet and --use-max-pods=false as the user data for the nodes. You may consider using the max-pod-calculator.sh script to calculate EKS’s recommended maximum number of pods for a given instance type. Refer to the EKS user guide for example user data.

+
./max-pods-calculator.sh --instance-type m5.large --cni-version ``1.9``.0 --cni-prefix-delegation-enabled
+
+

Prefix assignment mode is especially relevant for users of CNI custom networking where the primary ENI is not used for pods. With prefix assignment, you can still attach more IPs on nearly every Nitro instance type, even without the primary ENI used for pods.

+

Avoid Prefix Mode when

+

If your subnet is very fragmented and has insufficient available IP addresses to create /28 prefixes, avoid using prefix mode. The prefix attachment may fail if the subnet from which the prefix is produced is fragmented (a heavily used subnet with scattered secondary IP addresses). This problem may be avoided by creating a new subnet and reserving a prefix.

+

In prefix mode, the security group assigned to the worker nodes is shared by the Pods. Consider using Security groups for Podsif you have a security requirement to achieve compliance by running applications with varying network security requirements on shared compute resources.

+

Use Similar Instance Types in the same Node Group

+

Your node group may contain instances of many types. If an instance has a low maximum pod count, that value is applied to all nodes in the node group. Consider using similar instance types in a node group to maximize node use. We recommend configuring node.kubernetes.io/instance-type in the requirements part of the provisioner API if you are using Karpenter for automated node scaling.

+
+

Warning

+

The maximum pod count for all nodes in a particular node group is defined by the lowest maximum pod count of any single instance type in the node group.

+
+

Configure WARM_PREFIX_TARGET to conserve IPv4 addresses

+

The installation manifest’s default value for WARM_PREFIX_TARGET is 1. In most cases, the recommended value of 1 for WARM_PREFIX_TARGET will provide a good mix of fast pod launch times while minimizing unused IP addresses assigned to the instance.

+

If you have a need to further conserve IPv4 addresses per node use WARM_IP_TARGET and MINIMUM_IP_TARGET settings, which override WARM_PREFIX_TARGET when configured. By setting WARM_IP_TARGET to a value less than 16, you can prevent the CNI from keeping an entire excess prefix attached.

+

Prefer allocating new prefixes over attaching a new ENI

+

Allocating an additional prefix to an existing ENI is a faster EC2 API operation than creating and attaching a new ENI to the instance. Using prefixes improves performance while being frugal with IPv4 address allocation. Attaching a prefix typically completes in under a second, whereas attaching a new ENI can take up to 10 seconds. For most use cases, the CNI will only need a single ENI per worker node when running in prefix mode. If you can afford (in the worst case) up to 15 unused IPs per node, we strongly recommend using the newer prefix assignment networking mode, and realizing the performance and efficiency gains that come with it.

+

Use Subnet Reservations to Avoid Subnet Fragmentation (IPv4)

+

When EC2 allocates a /28 IPv4 prefix to an ENI, it has to be a contiguous block of IP addresses from your subnet. If the subnet that the prefix is generated from is fragmented (a highly used subnet with scattered secondary IP addresses), the prefix attachment may fail, and you will see the following error message in the VPC CNI logs:

+
failed to allocate a private IP/Prefix address: InsufficientCidrBlocks: There are not enough free cidr blocks in the specified subnet to satisfy the request.
+
+

To avoid fragmentation and have sufficient contiguous space for creating prefixes, you may use VPC Subnet CIDR reservations to reserve IP space within a subnet for exclusive use by prefixes. Once you create a reservation, the VPC CNI plugin will call EC2 APIs to assign prefixes that are automatically allocated from the reserved space.

+

It is recommended to create a new subnet, reserve space for prefixes, and enable prefix assignment with VPC CNI for worker nodes running in that subnet. If the new subnet is dedicated only to Pods running in your EKS cluster with VPC CNI prefix assignment enabled, then you can skip the prefix reservation step.

+

Avoid downgrading VPC CNI

+

Prefix mode works with VPC CNI version 1.9.0 and later. Downgrading of the Amazon VPC CNI add-on to a version lower than 1.9.0 must be avoided once the prefix mode is enabled and prefixes are assigned to ENIs. You must delete and recreate nodes if you decide to downgrade the VPC CNI.

+

Replace all nodes during the transition to Prefix Delegation

+

It is highly recommended that you create new node groups to increase the number of available IP addresses rather than doing rolling replacement of existing worker nodes. Cordon and drain all the existing nodes to safely evict all of your existing Pods. To prevent service disruptions, we suggest implementing Pod Disruption Budgets on your production clusters for critical workloads. Pods on new nodes will be assigned an IP from a prefix assigned to an ENI. After you confirm the Pods are running, you can delete the old nodes and node groups. If you are using managed node groups, please follow steps mentioned here to safely delete a node group.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/prefix-mode/index_windows/index.html b/networking/prefix-mode/index_windows/index.html new file mode 100644 index 000000000..81b90cbc8 --- /dev/null +++ b/networking/prefix-mode/index_windows/index.html @@ -0,0 +1,2302 @@ + + + + + + + + + + + + + + + + + + + + + + + Prefix Mode for Windows - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Prefix Mode for Windows

+

In Amazon EKS, each Pod that runs on a Windows host is assigned a secondary IP address by the VPC resource controller by default. This IP address is a VPC-routable address that is allocated from the host's subnet. On Linux, each ENI attached to the instance has multiple slots that can be populated by a secondary IP address or a /28 CIDR (a prefix). Windows hosts, however, only support a single ENI and its available slots. Using only secondary IP addresses can artifically limit the number of pods you can run on a Windows host, even when there is an abundance of IP addresses available for assignment.

+

In order to increase the pod density on Windows hosts, especially when using smaller instance types, you can enable Prefix Delegation for Windows nodes. When prefix delegation is enabled, /28 IPv4 prefixes are assigned to ENI slots rather than secondary IP addresses. Prefix delegation can be enabled by adding the enable-windows-prefix-delegation: "true" entry to the amazon-vpc-cni config map. This is the same config map where you need to set enable-windows-ipam: "true" entry for enabling Windows support.

+

Please follow the instructions mentioned in the EKS user guide to enable Prefix Delegation mode for Windows nodes.

+

illustration of two worker subnets, comparing ENI secondary IPvs to ENIs with delegated prefixes

+

Figure: Comparison of Secondary IP mode with Prefix Delegation mode

+

The maximum number of IP addresses you can assign to a network interface depends on the instance type and its size. Each prefix assigned to a network interface consumes an available slot. For example, a c5.large instance has a limit of 10 slots per network interface. The first slot on a network interface is always consumed by the interface's primary IP address, leaving you with 9 slots for prefixes and/or secondary IP addresses. If these slots are assigned prefixes, the node can support (9 * 16) 144 IP address whereas if they're assigned secondary IP addresses it can only support 9 IP addresses. See the documentation on IP addresses per network interface per instance type and assigning prefixes to network interfaces for further information.

+

During worker node initialization, the VPC Resource Controller assigns one or more prefixes to the primary ENI for faster pod startup by maintaining a warm pool of the IP addresses. The number of prefixes to be held in warm pool can be controlled by setting the following configuration parameters in amazon-vpc-cni config map.

+
    +
  • warm-prefix-target, the number of prefixes to be allocated in excess of current need.
  • +
  • warm-ip-target, the number of IP addresses to be allocated in excess of current need.
  • +
  • minimum-ip-target, the minimum number of IP addresses to be available at any time.
  • +
  • warm-ip-target and/or minimum-ip-target if set will override warm-prefix-target.
  • +
+

As more Pods are scheduled on the node, additional prefixes will be requested for the existing ENI. When a Pod is scheduled on the node, VPC Resource Controller would first try to assign an IPv4 address from the existing prefixes on the node. If that is not possible, then a new IPv4 prefix will be requested as long as the subnet has the required capacity.

+

flow chart of procedure for assigning IP to pod

+

Figure: Workflow during assignment of IPv4 address to the Pod

+

Recommendations

+

Use Prefix Delegation when

+

Use prefix delegation if you are experiencing Pod density issues on the worker nodes. To avoid errors, we recommend examining the subnets for contiguous block of addresses for /28 prefix before migrating to prefix mode. Please refer “Use Subnet Reservations to Avoid Subnet Fragmentation (IPv4)” section for Subnet reservation details.

+

By default, the max-pods on Windows nodes is set to 110. For the vast majority of instance types, this should be sufficient. If you want to increase or decrease this limit, then add the following to the bootstrap command in your user data: +

-KubeletExtraArgs '--max-pods=example-value'
+
+For more details about the bootstrap configuration parameters for Windows nodes, please visit the documentation here.

+

Avoid Prefix Delegation when

+

If your subnet is very fragmented and has insufficient available IP addresses to create /28 prefixes, avoid using prefix mode. The prefix attachment may fail if the subnet from which the prefix is produced is fragmented (a heavily used subnet with scattered secondary IP addresses). This problem may be avoided by creating a new subnet and reserving a prefix.

+

Configure parameters for prefix delegation to conserve IPv4 addresses

+

warm-prefix-target, warm-ip-target, and minimum-ip-target can be used to fine tune the behaviour of pre-scaling and dynamic scaling with prefixes. By default, the following values are used: +

warm-ip-target: "1"
+minimum-ip-target: "3"
+
+By fine tuning these configuration parameters, you can achieve an optimal balance of conserving the IP addresses and ensuring decreased Pod latency due to assignment of IP address. For more information about these configuration parameters, visit the documentation here.

+

Use Subnet Reservations to Avoid Subnet Fragmentation (IPv4)

+

When EC2 allocates a /28 IPv4 prefix to an ENI, it has to be a contiguous block of IP addresses from your subnet. If the subnet that the prefix is generated from is fragmented (a highly used subnet with scattered secondary IP addresses), the prefix attachment may fail, and you will see the following node event: +

InsufficientCidrBlocks: The specified subnet does not have enough free cidr blocks to satisfy the request
+
+To avoid fragmentation and have sufficient contiguous space for creating prefixes, use VPC Subnet CIDR reservations to reserve IP space within a subnet for exclusive use by prefixes. Once you create a reservation, the IP addresses from the reserved blocks will not be assigned to other resources. That way, VPC Resource Controller will be able to get available prefixes during the assignment call to the node ENI.

+

It is recommended to create a new subnet, reserve space for prefixes, and enable prefix assignment for worker nodes running in that subnet. If the new subnet is dedicated only to Pods running in your EKS cluster with prefix delegation enabled, then you can skip the prefix reservation step.

+

Replace all nodes when migrating from Secondary IP mode to Prefix Delegation mode or vice versa

+

It is highly recommended that you create new node groups to increase the number of available IP addresses rather than doing rolling replacement of existing worker nodes.

+

When using self-managed node groups, the steps for transition would be:

+
    +
  • Increase the capacity in your cluster such that the new nodes would be able to accomodate your workloads
  • +
  • Enable/Disable the Prefix Delegation feature for Windows
  • +
  • Cordon and drain all the existing nodes to safely evict all of your existing Pods. To prevent service disruptions, we suggest implementing Pod Disruption Budgets on your production clusters for critical workloads.
  • +
  • After you confirm the Pods are running, you can delete the old nodes and node groups. Pods on new nodes will be assigned an IPv4 address from a prefix assigned to the node ENI.
  • +
+

When using managed node groups, the steps for transition would be:

+
    +
  • Enable/Disable the Prefix Delegation feature for Windows
  • +
  • Update the node group using the steps mentioned here. This performs similar steps as above but are managed by EKS.
  • +
+
+

Warning

+

Run all Pods on a node in the same mode

+
+

For Windows, we recommend that you avoid running Pods in both secondary IP mode and prefix delegation mode at the same time. Such a situation can arise when you migrate from secondary IP mode to prefix delegation mode or vice versa with running Windows workloads.

+

While this will not impact your running Pods, there can be inconsistency with respect to the node's IP address capacity. For example, consider that a t3.xlarge node which has 14 slots for secondary IPv4 addresses. If you are running 10 Pods, then 10 slots on the ENI will be consumed by secondary IP addresses. After you enable prefix delegation the capacity advertised to the kube-api server would be (14 slots * 16 ip addresses per prefix) 244 but the actual capacity at that moment would be (4 remaining slots * 16 addresses per prefix) 64. This inconsistency between the amount of capacity advertised and the actual amount of capacity (remaining slots) can cause issues if you run more Pods than there are IP addresses available for assignment.

+

That being said, you can use the migration strategy as described above to safely transition your Pods from secondary IP address to addresses obtained from prefixes. When toggling between the modes, the Pods will continue running normally and:

+
    +
  • When toggling from secondary IP mode to prefix delegation mode, the secondary IP addresses assigned to the running pods will not be released. Prefixes will be assigned to the free slots. Once a pod is terminated, the secondary IP and slot it was using will be released.
  • +
  • When toggling from prefix delegation mode to secondary IP mode, a prefix will be released when all the IPs within its range are no longer allocated to pods. If any IP from the prefix is assigned to a pod then that prefix will be kept until the pods are terminated.
  • +
+

Debugging Issues with Prefix Delegation

+

You can use our debugging guide here to deep dive into the issue you are facing with prefix delegation on Windows.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/prefix-mode/windows-1.jpg b/networking/prefix-mode/windows-1.jpg new file mode 100644 index 000000000..c4de90aa2 Binary files /dev/null and b/networking/prefix-mode/windows-1.jpg differ diff --git a/networking/prefix-mode/windows-2.jpg b/networking/prefix-mode/windows-2.jpg new file mode 100644 index 000000000..87c6c4574 Binary files /dev/null and b/networking/prefix-mode/windows-2.jpg differ diff --git a/networking/sgpp/image-2.png b/networking/sgpp/image-2.png new file mode 100644 index 000000000..687f18232 Binary files /dev/null and b/networking/sgpp/image-2.png differ diff --git a/networking/sgpp/image-3.png b/networking/sgpp/image-3.png new file mode 100644 index 000000000..b47b97a6a Binary files /dev/null and b/networking/sgpp/image-3.png differ diff --git a/networking/sgpp/image.png b/networking/sgpp/image.png new file mode 100644 index 000000000..d67214222 Binary files /dev/null and b/networking/sgpp/image.png differ diff --git a/networking/sgpp/index.html b/networking/sgpp/index.html new file mode 100644 index 000000000..5a5985808 --- /dev/null +++ b/networking/sgpp/index.html @@ -0,0 +1,2435 @@ + + + + + + + + + + + + + + + + + + + + + + + Security Groups per Pod - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Security Groups Per Pod

+

An AWS security group acts as a virtual firewall for EC2 instances to control inbound and outbound traffic. By default, the Amazon VPC CNI will use security groups associated with the primary ENI on the node. More specifically, every ENI associated with the instance will have the same EC2 Security Groups. Thus, every Pod on a node shares the same security groups as the node it runs on.

+

As seen in the image below, all application Pods operating on worker nodes will have access to the RDS database service (considering RDS inbound allows node security group). Security groups are too coarse grained because they apply to all Pods running on a node. Security groups for Pods provides network segmentation for workloads which is an essential part a good defense in depth strategy.

+

illustration of node with security group connecting to RDS +With security groups for Pods, you can improve compute efficiency by running applications with varying network security requirements on shared compute resources. Multiple types of security rules, such as Pod-to-Pod and Pod-to-External AWS services, can be defined in a single place with EC2 security groups and applied to workloads with Kubernetes native APIs. The image below shows security groups applied at the Pod level and how they simplify your application deployment and node architecture. The Pod can now access Amazon RDS database.

+

illustration of pod and node with different security groups connecting to RDS

+

You can enable security groups for Pods by setting ENABLE_POD_ENI=true for VPC CNI. Once enabled, the “VPC Resource Controller“ running on the control plane (managed by EKS) creates and attaches a trunk interface called “aws-k8s-trunk-eni“ to the node. The trunk interface acts as a standard network interface attached to the instance. To manage trunk interfaces, you must add the AmazonEKSVPCResourceController managed policy to the cluster role that goes with your Amazon EKS cluster.

+

The controller also creates branch interfaces named "aws-k8s-branch-eni" and associates them with the trunk interface. Pods are assigned a security group using the SecurityGroupPolicy custom resource and are associated with a branch interface. Since security groups are specified with network interfaces, we are now able to schedule Pods requiring specific security groups on these additional network interfaces. Review the EKS User Guide Section on Security Groups for Pods, including deployment prerequisites.

+

illustration of worker subnet with security groups associated with ENIs

+

Branch interface capacity is additive to existing instance type limits for secondary IP addresses. Pods that use security groups are not accounted for in the max-pods formula and when you use security group for pods you need to consider raising the max-pods value or be ok with running fewer pods than the node can actually support.

+

A m5.large can have up to 9 branch network interfaces and up to 27 secondary IP addresses assigned to its standard network interfaces. As shown in the example below, the default max-pods for a m5.large is 29, and EKS counts the Pods that use security groups towards the maximum Pods. Please see the EKS user guide for instructions on how to change the max-pods for nodes.

+

When security groups for Pods are used in combination with custom networking, the security group defined in security groups for Pods is used rather than the security group specified in the ENIConfig. As a result, when custom networking is enabled, carefully assess security group ordering while using security groups per Pod.

+

Recommendations

+

Disable TCP Early Demux for Liveness Probe

+

If are you using liveness or readiness probes, you also need to disable TCP early demux, so that the kubelet can connect to Pods on branch network interfaces via TCP. This is only required in strict mode. To do this run the following command:

+
kubectl edit daemonset aws-node -n kube-system
+
+

Under the initContainer section, change the value for DISABLE_TCP_EARLY_DEMUX to true.

+

Use Security Group For Pods to leverage existing AWS configuration investment.

+

Security groups makes it easier to restrict network access to VPC resources, such as RDS databases or EC2 instances. One clear advantage of security groups per Pod is the opportunity to reuse existing AWS security group resources. +If you are using security groups as a network firewall to limit access to your AWS services, we propose applying security groups to Pods using branch ENIs. Consider using security groups for Pods if you are transferring apps from EC2 instances to EKS and limit access to other AWS services with security groups.

+

Configure Pod Security Group Enforcing Mode

+

Amazon VPC CNI plugin version 1.11 added a new setting named POD_SECURITY_GROUP_ENFORCING_MODE (“enforcing mode”). The enforcing mode controls both which security groups apply to the pod, and if source NAT is enabled. You may specify the enforcing mode as either strict or standard. Strict is the default, reflecting the previous behavior of the VPC CNI with ENABLE_POD_ENI set to true.

+

In Strict Mode, only the branch ENI security groups are enforced. The source NAT is also disabled.

+

In Standard Mode, the security groups associated with both the primary ENI and branch ENI (associated with the pod) are applied. Network traffic must comply with both security groups.

+
+

Warning

+

Any mode change will only impact newly launched Pods. Existing Pods will use the mode that was configured when the Pod was created. Customers will need to recycle existing Pods with security groups if they want to change the traffic behavior.

+
+

Enforcing Mode: Use Strict mode for isolating pod and node traffic:

+

By default, security groups for Pods is set to "strict mode." Use this setting if you must completely separate Pod traffic from the rest of the node's traffic. In strict mode, the source NAT is turned off so the branch ENI outbound security groups can be used.

+
+

Warning

+

When strict mode is enabled, all outbound traffic from a pod will leave the node and enter the VPC network. Traffic between pods on the same node will go over the VPC. This increases VPC traffic and limits node-based features. The NodeLocal DNSCache is not supported with strict mode.

+
+

Enforcing Mode: Use Standard mode in the following situations

+

Client source IP visible to the containers in the Pod

+

If you need to keep the client source IP visible to the containers in the Pod, consider setting POD_SECURITY_GROUP_ENFORCING_MODE to standard. Kubernetes services support externalTrafficPolicy=local to support preservation of the client source IP (default type cluster). You can now run Kubernetes services of type NodePort and LoadBalancer using instance targets with an externalTrafficPolicy set to Local in the standard mode. Local preserves the client source IP and avoids a second hop for LoadBalancer and NodePort type Services.

+

Deploying NodeLocal DNSCache

+

When using security groups for pods, configure standard mode to support Pods that use NodeLocal DNSCache. NodeLocal DNSCache improves Cluster DNS performance by running a DNS caching agent on cluster nodes as a DaemonSet. This will help the pods that have the highest DNS QPS requirements to query local kube-dns/CoreDNS having a local cache, which will improve the latency.

+

NodeLocal DNSCache is not supported in strict mode as all network traffic, even to the node, enters the VPC.

+

Supporting Kubernetes Network Policy

+

We recommend using standard enforcing mode when using network policy with Pods that have associated security groups.

+

We strongly recommend to utilize security groups for Pods to limit network-level access to AWS services that are not part of a cluster. Consider network policies to restrict network traffic between Pods inside a cluster, often known as East/West traffic.

+

Identify Incompatibilities with Security Groups per Pod

+

Windows-based and non-nitro instances do not support security groups for Pods. To utilize security groups with Pods, the instances must be tagged with isTrunkingEnabled. Use network policies to manage access between Pods rather than security groups if your Pods do not depend on any AWS services within or outside of your VPC.

+

Use Security Groups per Pod to efficiently control traffic to AWS Services

+

If an application running within the EKS cluster has to communicate with another resource within the VPC, e.g. an RDS database, then consider using SGs for pods. While there are policy engines that allow you to specify an CIDR or a DNS name, they are a less optimal choice when communicating with AWS services that have endpoints that reside within a VPC.

+

In contrast, Kubernetes network policies provide a mechanism for controlling ingress and egress traffic both within and outside the cluster. Kubernetes network policies should be considered if your application has limited dependencies on other AWS services. You may configure network policies that specify egress rules based on CIDR ranges to limit access to AWS services as opposed to AWS native semantics like SGs. You may use Kubernetes network policies to control network traffic between Pods (often referred to as East/West traffic) and between Pods and external services. Kubernetes network policies are implemented at OSI levels 3 and 4.

+

Amazon EKS allows you to use network policy engines such as Calico and Cilium. By default, the network policy engines are not installed. Please check the respective install guides for instructions on how to set up. For more information on how to use network policy, see EKS Security best practices. The DNS hostnames feature is available in the enterprise versions of network policy engines, which could be useful for controlling traffic between Kubernetes Services/Pods and resources that run outside of AWS. Also, you can consider DNS hostname support for AWS services that don't support security groups by default.

+

Tag a single Security Group to use AWS Loadbalancer Controller

+

When many security groups are allocated to a Pod, Amazon EKS recommends tagging a single security group with kubernetes.io/cluster/$name shared or owned. The tag allows the AWS Loadbalancer Controller to update the rules of security groups to route traffic to the Pods. If just one security group is given to a Pod, the assignment of a tag is optional. Permissions set in a security group are additive, therefore tagging a single security group is sufficient for the loadbalancer controller to locate and reconcile the rules. It also helps to adhere to the default quotas defined by security groups.

+

Configure NAT for Outbound Traffic

+

Source NAT is disabled for outbound traffic from Pods that are assigned security groups. For Pods using security groups that require access the internet launch worker nodes on private subnets configured with a NAT gateway or instance and enable external SNAT in the CNI.

+
kubectl set env daemonset -n kube-system aws-node AWS_VPC_K8S_CNI_EXTERNALSNAT=true
+
+

Deploy Pods with Security Groups to Private Subnets

+

Pods that are assigned security groups must be run on nodes that are deployed on to private subnets. Note that Pods with assigned security groups deployed to public subnets will not able to access the internet.

+

Verify terminationGracePeriodSeconds in Pod Specification File

+

Ensure that terminationGracePeriodSeconds is non-zero in your Pod specification file (default 30 seconds). This is essential in order for Amazon VPC CNI to delete the Pod network from the worker node. When set to zero, the CNI plugin does not remove the Pod network from the host, and the branch ENI is not effectively cleaned up.

+

Using Security Groups for Pods with Fargate

+

Security groups for Pods that run on Fargate work very similarly to Pods that run on EC2 worker nodes. For example, you have to create the security group before referencing it in the SecurityGroupPolicy you associate with your Fargate Pod. By default, the cluster security group is assiged to all Fargate Pods when you don't explicitly assign a SecurityGroupPolicy to a Fargate Pod. For simplicity's sake, you may want to add the cluster security group to a Fagate Pod's SecurityGroupPolicy otherwise you will have to add the minimum security group rules to your security group. You can find the cluster security group using the describe-cluster API.

+
 aws eks describe-cluster --name CLUSTER_NAME --query 'cluster.resourcesVpcConfig.clusterSecurityGroupId'
+
+
cat >my-fargate-sg-policy.yaml <<EOF
+apiVersion: vpcresources.k8s.aws/v1beta1
+kind: SecurityGroupPolicy
+metadata:
+  name: my-fargate-sg-policy
+  namespace: my-fargate-namespace
+spec:
+  podSelector: 
+    matchLabels:
+      role: my-fargate-role
+  securityGroups:
+    groupIds:
+      - cluster_security_group_id
+      - my_fargate_pod_security_group_id
+EOF
+
+

The minimum security group rules are listed here. These rules allow Fargate Pods to communicate with in-cluster services like kube-apiserver, kubelet, and CoreDNS. You also need add rules to allow inbound and outbound connections to and from your Fargate Pod. This will allow your Pod to communicate with other Pods or resources in your VPC. Additionally, you have to include rules for Fargate to pull container images from Amazon ECR or other container registries such as DockerHub. For more information, see AWS IP address ranges in the AWS General Reference.

+

You can use the below commands to find the security groups applied to a Fargate Pod.

+
kubectl get pod FARGATE_POD -o jsonpath='{.metadata.annotations.vpc\.amazonaws\.com/pod-eni}{"\n"}'
+
+

Note down the eniId from above command.

+
aws ec2 describe-network-interfaces --network-interface-ids ENI_ID --query 'NetworkInterfaces[*].Groups[*]'
+
+

Existing Fargate pods must be deleted and recreated in order for new security groups to be applied. For instance, the following command initiates the deployment of the example-app. To update specific pods, you can change the namespace and deployment name in the below command.

+
kubectl rollout restart -n example-ns deployment example-pod
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/subnet-calc/subnet-calc.xlsx b/networking/subnet-calc/subnet-calc.xlsx new file mode 100644 index 000000000..0000a5a6b Binary files /dev/null and b/networking/subnet-calc/subnet-calc.xlsx differ diff --git a/networking/subnets/eks-shared-subnets.png b/networking/subnets/eks-shared-subnets.png new file mode 100644 index 000000000..fefe02e0a Binary files /dev/null and b/networking/subnets/eks-shared-subnets.png differ diff --git a/networking/subnets/image-2.jpg b/networking/subnets/image-2.jpg new file mode 100644 index 000000000..5e1166d94 Binary files /dev/null and b/networking/subnets/image-2.jpg differ diff --git a/networking/subnets/image.png b/networking/subnets/image.png new file mode 100644 index 000000000..eb2e63f3a Binary files /dev/null and b/networking/subnets/image.png differ diff --git a/networking/subnets/index.html b/networking/subnets/index.html new file mode 100644 index 000000000..7f2b1bb58 --- /dev/null +++ b/networking/subnets/index.html @@ -0,0 +1,2662 @@ + + + + + + + + + + + + + + + + + + + + + + + VPC and Subnet Considerations - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

VPC and Subnet Considerations

+

Operating an EKS cluster requires knowledge of AWS VPC networking, in addition to Kubernetes networking.

+

We recommend you understand the EKS control plane communication mechanisms before you start designing your VPC or deploying clusters into existing VPCs.

+

Refer to Cluster VPC considerations and Amazon EKS security group considerations when architecting a VPC and subnets to be used with EKS.

+

Overview

+

EKS Cluster Architecture

+

An EKS cluster consists of two VPCs:

+
    +
  • An AWS-managed VPC that hosts the Kubernetes control plane. This VPC does not appear in the customer account.
  • +
  • A customer-managed VPC that hosts the Kubernetes nodes. This is where containers run, as well as other customer-managed AWS infrastructure such as load balancers used by the cluster. This VPC appears in the customer account. You need to create customer-managed VPC prior creating a cluster. The eksctl creates a VPC if you do not provide one.
  • +
+

The nodes in the customer VPC need the ability to connect to the managed API server endpoint in the AWS VPC. This allows the nodes to register with the Kubernetes control plane and receive requests to run application Pods.

+

The nodes connect to the EKS control plane through (a) an EKS public endpoint or (b) a Cross-Account elastic network interfaces (X-ENI) managed by EKS. When a cluster is created, you need to specify at least two VPC subnets. EKS places a X-ENI in each subnet specified during cluster create (also called cluster subnets). The Kubernetes API server uses these Cross-Account ENIs to communicate with nodes deployed on the customer-managed cluster VPC subnets.

+

general illustration of cluster networking, including load balancer, nodes, and pods.

+

As the node starts, the EKS bootstrap script is executed and Kubernetes node configuration files are installed. As part of the boot process on each instance, the container runtime agents, kubelet, and Kubernetes node agents are launched.

+

To register a node, Kubelet contacts the Kubernetes cluster endpoint. It establishes a connection with either the public endpoint outside of the VPC or the private endpoint within the VPC. Kubelet receives API instructions and provides status updates and heartbeats to the endpoint on a regular basis.

+

EKS Control Plane Communication

+

EKS has two ways to control access to the cluster endpoint. Endpoint access control lets you choose whether the endpoint can be reached from the public internet or only through your VPC. You can turn on the public endpoint (which is the default), the private endpoint, or both at once.

+

The configuration of the cluster API endpoint determines the path that nodes take to communicate to the control plane. Note that these endpoint settings can be changed at any time through the EKS console or API.

+

Public Endpoint

+

This is the default behavior for new Amazon EKS clusters. When only the public endpoint for the cluster is enabled, Kubernetes API requests that originate from within your cluster’s VPC (such as worker node to control plane communication) leave the VPC, but not Amazon’s network. In order for nodes to connect to the control plane, they must have a public IP address and a route to an internet gateway or a route to a NAT gateway where they can use the public IP address of the NAT gateway.

+

Public and Private Endpoint

+

When both the public and private endpoints are enabled, Kubernetes API requests from within the VPC communicate to the control plane via the X-ENIs within your VPC. Your cluster API server is accessible from the internet.

+

Private Endpoint

+

There is no public access to your API server from the internet when only private endpoint is enabled. All traffic to your cluster API server must come from within your cluster’s VPC or a connected network. The nodes communicate to API server via X-ENIs within your VPC. Note that cluster management tools must have access to the private endpoint. Learn more about how to connect to a private Amazon EKS cluster endpoint from outside the Amazon VPC.

+

Note that the cluster's API server endpoint is resolved by public DNS servers to a private IP address from the VPC. In the past, the endpoint could only be resolved from within the VPC.

+

VPC configurations

+

Amazon VPC supports IPv4 and IPv6 addressing. Amazon EKS supports IPv4 by default. A VPC must have an IPv4 CIDR block associated with it. You can optionally associate multiple IPv4 Classless Inter-Domain Routing (CIDR) blocks and multiple IPv6 CIDR blocks to your VPC. When you create a VPC, you must specify an IPv4 CIDR block for the VPC from the private IPv4 address ranges as specified in RFC 1918. The allowed block size is between a /16 prefix (65,536 IP addresses) and /28 prefix (16 IP addresses).

+

When creating a new VPC, you can attach a single IPv6 CIDR block, and up to five when changing an existing VPC. The prefix length of the IPv6 CIDR block size can be between /44 and /60 and for the IPv6 subnets it can be betwen /44/ and /64. You can request an IPv6 CIDR block from the pool of IPv6 addresses maintained by Amazon. Please refer to VPC CIDR blocks section of the VPC User Guide for more information.

+

Amazon EKS clusters support both IPv4 and IPv6. By default, EKS clusters use IPv4 IP. Specifying IPv6 at cluster creation time will enable the use IPv6 clusters. IPv6 clusters require dual-stack VPCs and subnets.

+

Amazon EKS recommends you use at least two subnets that are in different Availability Zones during cluster creation. The subnets you pass in during cluster creation are known as cluster subnets. When you create a cluster, Amazon EKS creates up to 4 cross account (x-account or x-ENIs) ENIs in the subnets that you specify. The x-ENIs are always deployed and are used for cluster administration traffic such as log delivery, exec, and proxy. Please refer to the EKS user guide for complete VPC and subnet requirement details.

+

Kubernetes worker nodes can run in the cluster subnets, but it is not recommended. During cluster upgrades Amazon EKS provisions additional ENIs in the cluster subnets. When your cluster scales out, worker nodes and pods may consume the available IPs in the cluster subnet. Hence in order to make sure there are enough available IPs you might want to consider using dedicated cluster subnets with /28 netmask.

+

Kubernetes worker nodes can run in either a public or a private subnet. Whether a subnet is public or private refers to whether traffic within the subnet is routed through an internet gateway. Public subnets have a route table entry to the internet through the internet gateway, but private subnets don't.

+

The traffic that originates somewhere else and reaches your nodes is called ingress. Traffic that originates from the nodes and leaves the network is called egress. Nodes with public or elastic IP addresses (EIPs) within a subnet configured with an internet gateway allow ingress from outside of the VPC. Private subnets usually have a routing to a NAT gateway, which do not allow ingress traffic to the nodes in the subnets from outside of VPC while still allowing traffic from the nodes to leave the VPC (egress).

+

In the IPv6 world, every address is internet routable. The IPv6 addresses associated with the nodes and pods are public. Private subnets are supported by implementing an egress-only internet gateways (EIGW) in a VPC, allowing outbound traffic while blocking all incoming traffic. Best practices for implementing IPv6 subnets can be found in the VPC user guide.

+

You can configure VPC and Subnets in three different ways:

+

Using only public subnets

+

In the same public subnets, both nodes and ingress resources (such as load balancers) are created. Tag the public subnet with kubernetes.io/role/elb to construct load balancers that face the internet. In this configuration, the cluster endpoint can be configured to be public, private, or both (public and private).

+

Using private and public subnets

+

Nodes are created on private subnets, whereas Ingress resources are instantiated in public subnets. You can enable public, private, or both (public and private) access to the cluster endpoint. Depending on the configuration of the cluster endpoint, node traffic will enter via the NAT gateway or the ENI.

+

Using only private subnets

+

Both nodes and ingress are created in private subnets. Using the kubernetes.io/role/internal-elb subnet tag to construct internal load balancers. Accessing your cluster's endpoint will require a VPN connection. You must activate AWS PrivateLink for EC2 and all Amazon ECR and S3 repositories. Only the private endpoint of the cluster should be enabled. We suggest going through the EKS private cluster requirements before provisioning private clusters.

+

Communication across VPCs

+

There are many scenarios when you require multiple VPCs and separate EKS clusters deployed to these VPCs.

+

You can use Amazon VPC Lattice to consistently and securely connect services across multiple VPCs and accounts (without requiring additional connectivity to be provided by services like VPC peering, AWS PrivateLink or AWS Transit Gateway). Learn more here.

+

Amazon VPC Lattice, traffic flow

+

Amazon VPC Lattice operates in the link-local address space in IPv4 and IPv6, providing connectivity between services that may have overlapping IPv4 addresses. For operational efficiency, we strongly recommend deploying EKS clusters and nodes to IP ranges that do not overlap. In case your infrastructure includes VPCs with overlapping IP ranges, you need to architect your network accordingly. We suggest Private NAT Gateway, or VPC CNI in custom networking mode in conjunction with transit gateway to integrate workloads on EKS to solve overlapping CIDR challenges while preserving routable RFC1918 IP addresses.

+

Private Nat Gateway with Custom Networking, traffic flow

+

Consider utilizing AWS PrivateLink, also known as an endpoint service, if you are the service provider and would want to share your Kubernetes service and ingress (either ALB or NLB) with your customer VPC in separate accounts.

+

Sharing VPC across multiple accounts

+

Many enterprises adopted shared Amazon VPCs as a means to streamline network administration, reduce costs and improve security across multiple AWS Accounts in an AWS Organization. They utilize AWS Resource Access Manager (RAM) to securely share supported AWS resources with individual AWS Accounts, organizational units (OUs) or entire AWS Organization.

+

You can deploy Amazon EKS clusters, managed node groups and other supporting AWS resources (like LoadBalancers, security groups, end points, etc.,) in shared VPC Subnets from an another AWS Account using AWS RAM. Below figure depicts an example highlevel architecture. This allows central networking teams control over the networking constructs like VPCs, Subnets, etc., while allowing application or platform teams to deploy Amazon EKS clusters in their respective AWS Accounts. A complete walkthrough of this scenario is available at this github repository.

+

Deploying Amazon EKS in VPC Shared Subnets across AWS Accounts.

+

Considerations when using Shared Subnets

+
    +
  • +

    Amazon EKS clusters and worker nodes can be created within shared subnets that are all part of the same VPC. Amazon EKS does not support the creation of clusters across multiple VPCs.

    +
  • +
  • +

    Amazon EKS uses AWS VPC Security Groups (SGs) to control the traffic between the Kubernetes control plane and the cluster's worker nodes. Security groups are also used to control the traffic between worker nodes, and other VPC resources, and external IP addresses. You must create these security groups in the application/participant account. Ensure that the security groups you intend to use for your pods are also located in the participant account. You can configure the inbound and outbound rules within your security groups to permit the necessary traffic to and from security groups located in the Central VPC account.

    +
  • +
  • +

    Create IAM roles and associated policies within the participant account where your Amazon EKS cluster resides. These IAM roles and policies are essential for granting the necessary permissions to Kubernetes clusters managed by Amazon EKS, as well as to the nodes and pods running on Fargate. The permissions enable Amazon EKS to make calls to other AWS services on your behalf.

    +
  • +
  • +

    You can follow following approaches to allow cross Account access to AWS resources like Amazon S3 buckets, Dynamodb tables, etc., from k8s pods:

    +
      +
    • +

      Resource based policy approach: If the AWS service supports resource policies, you can add appropriate resource based policy to allow cross account access to IAM Roles assigned to the kubernetes pods. In this scenario, OIDC provider, IAM Roles, and permission policies exist in the application account. To find AWS Services that support Resource based policies, refer AWS services that work with IAM and look for the services that have Yes in the Resource Based column.

      +
    • +
    • +

      OIDC Provider approach: IAM resources like OIDC Provider, IAM Roles, Permission, and Trust policies will be created in other participant AWS Account where the resources exists. These roles will be assigned to Kubernetes pods in application account, so that they can access cross account resources. Refer Cross account IAM roles for Kubernetes service accounts blog for a complete walkthrough of this approach.

      +
    • +
    +
  • +
  • +

    You can deploy the Amazon Elastic Loadbalancer (ELB) resources (ALB or NLB) to route traffic to k8s pods either in application or central networking accounts. Refer to Expose Amazon EKS Pods Through Cross-Account Load Balancer walkthrough for detailed instructions on deploying the ELB resources in central networking account. This option offers enhanced flexibility, as it grants the Central Networking account full control over the security configuration of the Load Balancer resources.

    +
  • +
  • +

    When using custom networking feature of Amazon VPC CNI, you need to use the Availability Zone (AZ) ID mappings listed in the central networking account to create each ENIConfig. This is due to random mapping of physical AZs to the AZ names in each AWS account.

    +
  • +
+

Security Groups

+

A security group controls the traffic that is allowed to reach and leave the resources that it is associated with. Amazon EKS uses security groups to manage the communication between the control plane and nodes. When you create a cluster, Amazon EKS creates a security group that's named eks-cluster-sg-my-cluster-uniqueID. EKS associates these security groups to the managed ENIs and the nodes. The default rules allow all traffic to flow freely between your cluster and nodes, and allows all outbound traffic to any destination.

+

When you create a cluster, you can specify your own security groups. Please see recommendation for security groups when you specify own security groups.

+

Recommendations

+

Consider Multi-AZ Deployment

+

AWS Regions provide multiple physically separated and isolated Availability Zones (AZ), which are connected with low-latency, high-throughput, and highly redundant networking. With Availability Zones, you can design and operate applications that automatically fail over between Availability Zones without interruption. Amazon EKS strongly recommends deploying EKS clusters to multiple availability zones. Please consider specifying subnets in at least two availability zones when you create the cluster.

+

Kubelet running on nodes automatically adds labels to the node object such as topology.kubernetes.io/region=us-west-2, and topology.kubernetes.io/zone=us-west-2d. We recommend to use node labels in conjunction with Pod topology spread constraints to control how Pods are spread across zones. These hints enable Kubernetes scheduler to place Pods for better expected availability, reducing the risk that a correlated failure affects your whole workload. Please refer Assigning nodes to Pods to see examples for node selector and AZ spread constraints.

+

You can define the subnets or availability zones when you create nodes. The nodes are placed in cluster subnets if no subnets are configured. EKS support for managed node groups automatically spreads the nodes across multiple availability zones on available capacity. Karpenter will honor the AZ spread placement by scaling nodes to specified AZs if workloads define topology spread limits.

+

AWS Elastic Load Balancers are managed by the AWS Load Balancer Controller for a Kubernetes cluster. It provisions an Application Load Balancer (ALB) for Kubernetes ingress resources and a Network Load Balancer (NLB) for Kubernetes services of type Loadbalancer. The Elastic Load Balancer controller uses tags to discover the subnets. ELB controller requires a minimum of two availability zones (AZs) to provision ingress resource successfully. Consider setting subnets in at least two AZs to take advantage of geographic redundancy's safety and reliability.

+

Deploy Nodes to Private Subnets

+

A VPC including both private and public subnets is the ideal method for deploying Kubernetes workloads on EKS. Consider setting a minimum of two public subnets and two private subnets in two distinct availability zones. The related route table of a public subnet contains a route to an internet gateway . Pods are able to interact with the Internet via a NAT gateway. Private subnets are supported by egress-only internet gateways in the IPv6 environment (EIGW).

+

Instantiating nodes in private subnets offers maximal control over traffic to the nodes and is effective for the vast majority of Kubernetes applications. Ingress resources (like as load balancers) are instantiated in public subnets and route traffic to Pods operating on private subnets.

+

Consider private only mode if you demand strict security and network isolation. In this configuration, three private subnets are deployed in distinct Availability Zones within the AWS Region's VPC. The resources deployed to the subnets cannot access the internet, nor can the internet access the resources in the subnets. In order for your Kubernetes application to access other AWS services, you must configure PrivateLink interfaces and/or gateway endpoints. You may setup internal load balancers to redirect traffic to Pods using AWS Load Balancer Controller. The private subnets must be tagged (kubernetes.io/role/internal-elb: 1) for the controller to provision load balancers. For nodes to register with the cluster, the cluster endpoint must be set to private mode. Please visit private cluster guide for complete requirements and considerations.

+

Consider Public and Private Mode for Cluster Endpoint

+

Amazon EKS offers public-only, public-and-private, and private-only cluster endpoint modes. The default mode is public-only, however we recommend configuring cluster endpoint in public and private mode. This option allows Kubernetes API calls within your cluster's VPC (such as node-to-control-plane communication) to utilize the private VPC endpoint and traffic to remain within your cluster's VPC. Your cluster API server, on the other hand, can be reached from the internet. However, we strongly recommend limiting the CIDR blocks that can use the public endpoint. Learn how to configure public and private endpoint access, including limiting CIDR blocks.

+

We suggest a private-only endpoint when you need security and network isolation. We recommend using either of the options listed in the EKS user guide to connect to an API server privately.

+

Configure Security Groups Carefully

+

Amazon EKS supports using custom security groups. Any custom security groups must allow communication between nodes and the Kubernetes control plane. Please check port requirements and configure rules manually when your organization doesn't allow for open communication.

+

EKS applies the custom security groups that you provide during cluster creation to the managed interfaces (X-ENIs). However, it does not immediately associate them with nodes. While creating node groups, it is strongly recommended to associate custom security groups manually. Please consider enabling securityGroupSelectorTerms to enable Karpenter node template discovery of custom security groups during autoscaling of nodes.

+

We strongly recommend creating a security group to allow all inter-node communication traffic. During the bootstrap process, nodes require outbound Internet connectivity to access the cluster endpoint. Evaluate outward access requirements, such as on-premise connection and container registry access, and set rules appropriately. Before putting changes into production, we strongly suggest that you check connections carefully in your development environment.

+

Deploy NAT Gateways in each Availability Zone

+

If you deploy nodes in private subnets (IPv4 and IPv6), consider creating a NAT Gateway in each Availability Zone (AZ) to ensure zone-independent architecture and reduce cross AZ expenditures. Each NAT gateway in an AZ is implemented with redundancy.

+

Use Cloud9 to access Private Clusters

+

AWS Cloud9 is a web-based IDE than can run securely in Private Subnets without ingress access, using AWS Systems Manager. Egress can also be disabled on the Cloud9 instance. Learn more about using Cloud9 to access private clusters and subnets.

+

illustration of AWS Cloud9 console connecting to no-ingress EC2 instance.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/networking/subnets/private-nat-gw.gif b/networking/subnets/private-nat-gw.gif new file mode 100644 index 000000000..49526c46f Binary files /dev/null and b/networking/subnets/private-nat-gw.gif differ diff --git a/networking/subnets/vpc-lattice.gif b/networking/subnets/vpc-lattice.gif new file mode 100644 index 000000000..0f96f5df0 Binary files /dev/null and b/networking/subnets/vpc-lattice.gif differ diff --git a/networking/vpc-cni/image-2.png b/networking/vpc-cni/image-2.png new file mode 100644 index 000000000..e176e5193 Binary files /dev/null and b/networking/vpc-cni/image-2.png differ diff --git a/networking/vpc-cni/image-3.png b/networking/vpc-cni/image-3.png new file mode 100644 index 000000000..a9fbf3360 Binary files /dev/null and b/networking/vpc-cni/image-3.png differ diff --git a/networking/vpc-cni/image-4.png b/networking/vpc-cni/image-4.png new file mode 100644 index 000000000..ba68f37ab Binary files /dev/null and b/networking/vpc-cni/image-4.png differ diff --git a/networking/vpc-cni/image-5.png b/networking/vpc-cni/image-5.png new file mode 100644 index 000000000..469d22197 Binary files /dev/null and b/networking/vpc-cni/image-5.png differ diff --git a/networking/vpc-cni/image.png b/networking/vpc-cni/image.png new file mode 100644 index 000000000..18dd6a007 Binary files /dev/null and b/networking/vpc-cni/image.png differ diff --git a/networking/vpc-cni/index.html b/networking/vpc-cni/index.html new file mode 100644 index 000000000..7797c2f45 --- /dev/null +++ b/networking/vpc-cni/index.html @@ -0,0 +1,2406 @@ + + + + + + + + + + + + + + + + + + + + + + + Amazon VPC CNI - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Amazon VPC CNI

+ + +

Amazon EKS implements cluster networking through the Amazon VPC Container Network Interface(VPC CNI) plugin. The CNI plugin allows Kubernetes Pods to have the same IP address as they do on the VPC network. More specifically, all containers inside the Pod share a network namespace, and they can communicate with each-other using local ports.

+

Amazon VPC CNI has two components:

+
    +
  • CNI Binary, which will setup Pod network to enable Pod-to-Pod communication. The CNI binary runs on a node root file system and is invoked by the kubelet when a new Pod gets added to, or an existing Pod removed from the node.
  • +
  • ipamd, a long-running node-local IP Address Management (IPAM) daemon and is responsible for:
  • +
  • managing ENIs on a node, and
  • +
  • maintaining a warm-pool of available IP addresses or prefix
  • +
+

When an instance is created, EC2 creates and attaches a primary ENI associated with a primary subnet. The primary subnet may be public or private. The Pods that run in hostNetwork mode use the primary IP address assigned to the node primary ENI and share the same network namespace as the host.

+

The CNI plugin manages Elastic Network Interfaces (ENI) on the node. When a node is provisioned, the CNI plugin automatically allocates a pool of slots (IPs or Prefix’s) from the node’s subnet to the primary ENI. This pool is known as the warm pool, and its size is determined by the node’s instance type. Depending on CNI settings, a slot may be an IP address or a prefix. When a slot on an ENI has been assigned, the CNI may attach additional ENIs with warm pool of slots to the nodes. These additional ENIs are called Secondary ENIs. Each ENI can only support a certain number of slots, based on instance type. The CNI attaches more ENIs to instances based on the number of slots needed, which usually corresponds to the number of Pods. This process continues until the node can no longer support additional ENI. The CNI also pre-allocates “warm” ENIs and slots for faster Pod startup. Note each instance type has a maximum number of ENIs that may be attached. This is one constraint on Pod density (number of Pods per node), in addition to compute resources.

+

flow chart illustrating procedure when new ENI delegated prefix is needed

+

The maximum number of network interfaces, and the maximum number of slots that you can use varies by the type of EC2 Instance. Since each Pod consumes an IP address on a slot, the number of Pods you can run on a particular EC2 Instance depends on how many ENIs can be attached to it and how many slots each ENI supports. We suggest setting the maximum Pods per EKS user guide to avoid exhaustion of the instance’s CPU and memory resources. Pods using hostNetwork are excluded from this calculation. You may consider using a script called max-pods-calculator.sh to calculate EKS’s recommended maximum Pods for a given instance type.

+

Overview

+

Secondary IP mode is the default mode for VPC CNI. This guide provides a generic overview of VPC CNI behavior when Secondary IP mode is enabled. The functionality of ipamd (allocation of IP addresses) may vary depending on the configuration settings for VPC CNI, such as Prefix Mode, Security Groups Per Pod, and Custom Networking.

+

The Amazon VPC CNI is deployed as a Kubernetes Daemonset named aws-node on worker nodes. When a worker node is provisioned, it has a default ENI, called the primary ENI, attached to it. The CNI allocates a warm pool of ENIs and secondary IP addresses from the subnet attached to the node’s primary ENI. By default, ipamd attempts to allocate an additional ENI to the node. The IPAMD allocates additional ENI when a single Pod is scheduled and assigned a secondary IP address from the primary ENI. This "warm" ENI enables faster Pod networking. As the pool of secondary IP addresses runs out, the CNI adds another ENI to assign more.

+

The number of ENIs and IP addresses in a pool are configured through environment variables called WARM_ENI_TARGET, WARM_IP_TARGET, MINIMUM_IP_TARGET. The aws-node Daemonset will periodically check that a sufficient number of ENIs are attached. A sufficient number of ENIs are attached when all of the WARM_ENI_TARGET, or WARM_IP_TARGET and MINIMUM_IP_TARGET conditions are met. If there are insufficient ENIs attached, the CNI will make an API call to EC2 to attach more until MAX_ENI limit is reached.

+
    +
  • WARM_ENI_TARGET - Integer, Values >0 indicate requirement Enabled
  • +
  • The number of Warm ENIs to be maintained. An ENI is “warm” when it is attached as a secondary ENI to a node, but it is not in use by any Pod. More specifically, no IP addresses of the ENI have been associated with a Pod.
  • +
  • Example: Consider an instance with 2 ENIs, each ENI supporting 5 IP addresses. WARM_ENI_TARGET is set to 1. If exactly 5 IP addresses are associated with the instance, the CNI maintains 2 ENIs attached to the instance. The first ENI is in use, and all 5 possible IP addresses of this ENI are used. The second ENI is “warm” with all 5 IP addresses in pool. If another Pod is launched on the instance, a 6th IP address will be needed. The CNI will assign this 6th Pod an IP address from the second ENI and from 5 IPs from the pool. The second ENI is now in use, and no longer in a “warm” status. The CNI will allocate a 3rd ENI to maintain at least 1 warm ENI.
  • +
+
+

Note

+

The warm ENIs still consume IP addresses from the CIDR of your VPC. IP addresses are “unused” or “warm” until they are associated with a workload, such as a Pod.

+
+
    +
  • WARM_IP_TARGET, Integer, Values >0 indicate requirement Enabled
  • +
  • The number of Warm IP addresses to be maintained. A Warm IP is available on an actively attached ENI, but has not been assigned to a Pod. In other words, the number of Warm IPs available is the number of IPs that may be assigned to a Pod without requiring an additional ENI.
  • +
  • Example: Consider an instance with 1 ENI, each ENI supporting 20 IP addresses. WARM_IP_TARGET is set to 5. WARM_ENI_TARGET is set to 0. Only 1 ENI will be attached until a 16th IP address is needed. Then, the CNI will attach a second ENI, consuming 20 possible addresses from the subnet CIDR.
  • +
  • MINIMUM_IP_TARGET, Integer, Values >0 indicate requirement Enabled
  • +
  • The minimum number of IP addresses to be allocated at any time. This is commonly used to front-load the assignment of multiple ENIs at instance launch.
  • +
  • Example: Consider a newly launched instance. It has 1 ENI and each ENI supports 10 IP addresses. MINIMUM_IP_TARGET is set to 100. The ENI immediately attaches 9 more ENIs for a total of 100 addresses. This happens regardless of any WARM_IP_TARGET or WARM_ENI_TARGET values.
  • +
+

This project includes a Subnet Calculator Excel Document. This calculator document simulates the IP address consumption of a specified workload under different ENI configuration options, such as WARM_IP_TARGET and WARM_ENI_TARGET.

+

illustration of components involved in assigning an IP address to a pod

+

When Kubelet receives an add Pod request, the CNI binary queries ipamd for an available IP address, which ipamd then provides to the Pod. The CNI binary wires up the host and Pod network.

+

Pods deployed on a node are, by default, assigned to the same security groups as the primary ENI. Alternatively, Pods may be configured with different security groups.

+

second illustration of components involved in assigning an IP address to a pod

+

As the pool of IP addresses is depleted, the plugin automatically attaches another elastic network interface to the instance and allocates another set of secondary IP addresses to that interface. This process continues until the node can no longer support additional elastic network interfaces.

+

third illustration of components involved in assigning an IP address to a pod

+

When a Pod is deleted, VPC CNI places the Pod’s IP address in a 30-second cool down cache. The IPs in a cool down cache are not assigned to new Pods. When the cooling-off period is over, VPC CNI moves Pod IP back to the warm pool. The cooling-off period prevents Pod IP addresses from being recycled prematurely and allows kube-proxy on all cluster nodes to finish updating the iptables rules. When the number of IPs or ENIs exceeds the number of warm pool settings, the ipamd plugin returns IPs and ENIs to the VPC.

+

As described above in Secondary IP mode, each Pod receives one secondary private IP address from one of the ENIs attached to an instance. Since each Pod uses an IP address, the number of Pods you can run on a particular EC2 Instance depends on how many ENIs can be attached to it and how many IP addresses it supports. The VPC CNI checks the limits file to find out how many ENIs and IP addresses are allowed for each type of instance.

+

You can use the following formula to determine maximum number of Pods you can deploy on a node.

+

(Number of network interfaces for the instance type × (the number of IP addresses per network interface - 1)) + 2

+

The +2 indicates Kubernetes Pods that use host networking, such as kube-proxy and VPC CNI. Amazon EKS requires kube-proxy and VPC CNI to be running on every node and are calculated towards max-pods. Consider updating max-pods if you plan to run more host networking Pods. You can specify --kubelet-extra-args "—max-pods=110" as user data in the launch template.

+

As an example, on a cluster with 3 c5.large nodes (3 ENIs and max 10 IPs per ENI), when the cluster starts up and has 2 CoreDNS pods, the CNI will consume 49 IP addresses and keeps them in warm pool. The warm pool enables faster Pod launches when the application is deployed.

+

Node 1 (with CoreDNS pod): 2 ENIs, 20 IPs assigned

+

Node 2 (with CoreDNS pod): 2 ENIs, 20 IPs assigned

+

Node 3 (no Pod): 1 ENI. 10 IPs assigned.

+

Keep in mind that infrastructure pods, often running as daemon sets, each contribute to the max-pod count. These can include:

+
    +
  • CoreDNS
  • +
  • Amazon Elastic LoadBalancer
  • +
  • Operational pods for metrics-server
  • +
+

We suggest that you plan your infrastructure by combining these Pods' capacities. For a list of the maximum number of Pods supported by each instance type, see eni-max-Pods.txt on GitHub.

+

illustration of multiple ENIs attached to a node

+

Recommendations

+

Deploy VPC CNI Managed Add-On

+

When you provision a cluster, Amazon EKS installs VPC CNI automatically. Amazon EKS nevertheless supports managed add-ons that enable the cluster to interact with underlying AWS resources such as computing, storage, and networking. We highly recommend that you deploy clusters with managed add-ons including VPC CNI.

+

Amazon EKS managed add-on offer VPC CNI installation and management for Amazon EKS clusters. Amazon EKS add-ons include the latest security patches, bug fixes, and are validated by AWS to work with Amazon EKS. The VPC CNI add-on enables you to continuously ensure the security and stability of your Amazon EKS clusters and decrease the amount of effort required to install, configure, and update add-ons. Additionally, a managed add-on can be added, updated, or deleted via the Amazon EKS API, AWS Management Console, AWS CLI, and eksctl.

+

You can find the managed fields of VPC CNI using --show-managed-fields flag with the kubectl get command.

+
kubectl get daemonset aws-node --show-managed-fields -n kube-system -o yaml
+
+

Managed add-ons prevents configuration drift by automatically overwriting configurations every 15 minutes. This means that any changes to managed add-ons, made via the Kubernetes API after add-on creation, will overwrite by the automated drift-prevention process and also set to defaults during add-on update process.

+

The fields managed by EKS are listed under managedFields with manager as EKS. Fields managed by EKS include service account, image, image url, liveness probe, readiness probe, labels, volumes, and volume mounts.

+
+

Info

+
+

The most frequently used fields such as WARM_ENI_TARGET, WARM_IP_TARGET, and MINIMUM_IP_TARGET are not managed and will not be reconciled. The changes to these fields will be preserved upon updating of the add-on.

+

We suggest testing the add-on behavior in your non-production clusters for a specific configuration before updating production clusters. Additionally, follow the steps in the EKS user guide for add-on configurations.

+

Migrate to Managed Add-On

+

You will manage the version compatibility and update the security patches of self-managed VPC CNI. To update a self-managed add-on, you must use the Kubernetes APIs and instructions outlined in the EKS user guide. We recommend migrating to a managed add-on for existing EKS clusters and highly suggest creating a backup of your current CNI settings prior to migration. To configure managed add-ons, you can utilize the Amazon EKS API, AWS Management Console, or AWS Command Line Interface.

+
kubectl apply view-last-applied daemonset aws-node -n kube-system > aws-k8s-cni-old.yaml
+
+

Amazon EKS will replace the CNI configuration settings if the field is listed as managed with default settings. We caution against modifying the managed fields. The add-on does not reconcile configuration fields such as the warm environment variables and CNI modes. The Pods and applications will continue to run while you migrate to a managed CNI.

+

Backup CNI Settings Before Update

+

VPC CNI runs on customer data plane (nodes), and hence Amazon EKS does not automatically update the add-on (managed and self-managed) when new versions are released or after you update your cluster to a new Kubernetes minor version. To update the add-on for an existing cluster, you must trigger an update via update-addon API or clicking update now link in the EKS console for add-ons. If you have deployed self-managed add-on, follow steps mentioned under updating self-managed VPC CNI add-on.

+

We strongly recommend that you update one minor version at a time. For example, if your current minor version is 1.9 and you want to update to 1.11, you should update to the latest patch version of 1.10 first, then update to the latest patch version of 1.11.

+

Perform an inspection of the aws-node Daemonset before updating Amazon VPC CNI. Take a backup of existing settings. If using a managed add-on, confirm that you have not updated any settings that Amazon EKS might override. We recommend a post update hook in your automation workflow or a manual apply step after an add-on update.

+
kubectl apply view-last-applied daemonset aws-node -n kube-system > aws-k8s-cni-old.yaml
+
+

For a self-managed add-on, compare the backup with releases on GitHub to see the available versions and familiarize yourself with the changes in the version that you want to update to. We recommend using Helm to manage self-managed add-ons and leverage values files to apply settings. Any update operations involving Daemonset delete will result in application downtime and must be avoided.

+

Understand Security Context

+

We strongly suggest you to understand the security contexts configured for managing VPC CNI efficiently. Amazon VPC CNI has two components CNI binary and ipamd (aws-node) Daemonset. The CNI runs as a binary on a node and has access to node root file system, also has privileged access as it deals with iptables at the node level. The CNI binary is invoked by the kubelet when Pods gets added or removed.

+

The aws-node Daemonset is a long-running process responsible for IP address management at the node level. The aws-node runs in hostNetwork mode and allows access to the loopback device, and network activity of other pods on the same node. The aws-node init-container runs in privileged mode and mounts the CRI socket allowing the Daemonset to monitor IP usage by the Pods running on the node. Amazon EKS is working to remove the privileged requirement of aws-node init container. Additionally, the aws-node needs to update NAT entries and to load the iptables modules and hence runs with NET_ADMIN privileges.

+

Amazon EKS recommends deploying the security policies as defined by the aws-node manifest for IP management for the Pods and networking settings. Please consider updating to the latest version of VPC CNI. Furthermore, please consider opening a GitHub issue if you have a specific security requirement.

+

Use separate IAM role for CNI

+

The AWS VPC CNI requires AWS Identity and Access Management (IAM) permissions. The CNI policy needs to be set up before the IAM role can be used. You can use AmazonEKS_CNI_Policy, which is an AWS managed policy for IPv4 clusters. AmazonEKS CNI managed policy only has permissions for IPv4 clusters. You must create a separate IAM policy for IPv6 clusters with the permissions listed here.

+

By default, VPC CNI inherits the Amazon EKS node IAM role (both managed and self-managed node groups).

+

Configuring a separate IAM role with the relevant policies for Amazon VPC CNI is strongly recommended. If not, the pods of Amazon VPC CNI gets the permission assigned to the node IAM role and have access to the instance profile assigned to the node.

+

The VPC CNI plugin creates and configures a service account called aws-node. By default, the service account binds to the Amazon EKS node IAM role with Amazon EKS CNI policy attached. To use the separate IAM role, we recommend that you create a new service account with Amazon EKS CNI policy attached. To use a new service account you must redeploy the CNI pods. Consider specifying a --service-account-role-arn for VPC CNI managed add-on when creating new clusters. Make sure you remove Amazon EKS CNI policy for both IPv4 and IPv6 from Amazon EKS node role.

+

It is advised that you block access instance metadata to minimize the blast radius of security breach.

+

Handle Liveness/Readiness Probe failures

+

We advise increasing the liveness and readiness probe timeout values (default timeoutSeconds: 10) for EKS 1.20 an later clusters to prevent probe failures from causing your application's Pod to become stuck in a containerCreating state. This problem has been seen in data-intensive and batch-processing clusters. High CPU use causes aws-node probe health failures, leading to unfulfilled Pod CPU requests. In addition to modifying the probe timeout, ensure that the CPU resource requests (default CPU: 25m) for aws-node are correctly configured. We do not suggest updating the settings unless your node is having issues.

+

We highly encourage you to run sudo bash /opt/cni/bin/aws-cni-support.sh on a node while you engage Amazon EKS support. The script will assist in evaluating kubelet logs and memory utilization on the node. Please consider installing SSM Agent on Amazon EKS worker nodes to run the script.

+

Configure IPTables Forward Policy on non-EKS Optimized AMI Instances

+

If you are using custom AMI, make sure to set iptables forward policy to ACCEPT under kubelet.service. Many systems set the iptables forward policy to DROP. You can build custom AMI using HashiCorp Packer and a build specification with resources and configuration scripts from the Amazon EKS AMI repository on AWS GitHub. You can update the kubelet.service and follow the instructions specified here to create a custom AMI.

+

Routinely Upgrade CNI Version

+

The VPC CNI is backward compatible. The latest version works with all Amazon EKS supported Kubernetes versions. Additionally, the VPC CNI is offered as an EKS add-on (see “Deploy VPC CNI Managed Add-On” above). While EKS add-ons orchestrates upgrades of add-ons, it will not automatically upgrade add-ons like the CNI because they run on the data plane. You are responsible for upgrading the VPC CNI add-on following managed and self-managed worker node upgrades.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/performance/performance_WIP/index.html b/performance/performance_WIP/index.html new file mode 100644 index 000000000..967a64a42 --- /dev/null +++ b/performance/performance_WIP/index.html @@ -0,0 +1,2317 @@ + + + + + + + + + + + + + + + + + + + Performance Efficiency Pillar - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Performance Efficiency Pillar

+

The performance efficiency pillar focuses on the efficient use of computing resources to meet requirements and how to maintain that efficiency as demand changes and technologies evolve. This section provides in-depth, best practices guidance for architecting for performance efficiency on AWS.

+

Definition

+

To ensure the efficient use of EKS container services, you should gather data on all aspects of the architecture, from the high-level design to the selection of EKS resource types. By reviewing your choices on a regular basis, you ensure that you are taking advantage of the continually evolving Amazon EKS and Container services. Monitoring will ensure that you are aware of any deviance from expected performance so you can take action on it.

+

Performance efficiency for EKS containers is composed of three areas:

+
    +
  • +

    Optimize your container

    +
  • +
  • +

    Resource Management

    +
  • +
  • +

    Scalability Management

    +
  • +
+

Best Practices

+

Optimize your container

+

You can run most applications in a Docker container without too much hassle. There are a number of things that you need to do to ensure it's running effectively in a production environment, including streamlining the build process. The following best practices will help you to achieve that.

+

Recommendations

+
    +
  • Make your container images stateless: A container created with a Docker image should be ephemeral and immutable. In other words, the container should be disposable and independent, i.e. a new one can be built and put in place with absolutely no configuration changes. Design your containers to be stateless. If you would like to use persistent data, use volumes instead. If you would like to store secrets or sensitive application data used by services, you can use solutions like AWS Systems ManagerParameter Store or third-party offerings or open source solutions, such as HashiCorp Valut and Consul, for runtime configurations.
  • +
  • Minimal base image : Start with a small base image. Every other instruction in the Dockerfile builds on top of this image. The smaller the base image, the smaller the resulting image is, and the more quickly it can be downloaded. For example, the alpine:3.7 image is 71 MB smaller than the centos:7 image. You can even use the scratch base image, which is an empty image on which you can build your own runtime environment.
  • +
  • Avoid unnecessary packages: When building a container image, include only the dependencies what your application needs and avoid installing unnecessary packages. For example if your application does not need an SSH server, don't include one. This will reduce complexity, dependencies, file sizes, and build times. To exclude files not relevant to the build use a .dockerignore file.
  • +
  • Use multi-stage build:Multi-stage builds allow you to build your application in a first "build" container and use the result in another container, while using the same Dockerfile. To expand a bit on that, in multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don't want in the final image. This method drastically reduces the size of your final image, without struggling to reduce the number of intermediate layers and files.
  • +
  • Minimize number of layers: Each instruction in the Dockerfile adds an extra layer to the Docker image. The number of instructions and layers should be kept to a minimum as this affects build performance and time. For example, the first instruction below will create multiple layers, whereas the second instruction by using &&(chaining) we reduced the number of layers, which will help provide better performance. The is the best way to reduce the number of layers that will be created in your Dockerfile.
  • +
  • +
            RUN apt-get -y update
    +        RUN apt-get install -y python
    +        RUN apt-get -y update && apt-get install -y python
    +
    +
  • +
  • +

    Properly tag your images: When building images, always tag them with useful and meaningful tags. This is a good way to organize and document metadata describing an image, for example, by including a unique counter like build id from a CI server (e.g. CodeBuild or Jenkins) to help with identifying the correct image. The tag latest is used by default if you do not provide one in your Docker commands. We recommend not to use the automatically created latest tag, because by using this tag you'll automatically be running future major releases, which could include breaking changes for your application. The best practice is to avoid the latest tag and instead use the unique digest created by your CI server.

    +
  • +
  • Use Build Cache to improve build speed : The cache allows you to take advantage of existing cached images, rather than building each image from scratch. For example, you should add the source code of your application as late as possible in your Dockerfile so that the base image and your application's dependencies get cached and aren't rebuilt on every build. To reuse already cached images, By default in Amazon EKS, the kubelet will try to pull each image from the specified registry. However, if the imagePullPolicy property of the container is set to IfNotPresent or Never, then a local image is used (preferentially or exclusively, respectively).
  • +
  • +

    Image Security : Using public images may be a great way to start working on containers and deploying it to Kubernetes. However, using them in production can come with a set of challenges. Especially when it comes to security. Ensure to follow the best practices for packaging and distributing the containers/applications. For example, don't build your containers with passwords baked in also you might need to control what's inside them. Recommend to use private repository such as Amazon ECR and leverage the in-built image scanning feature to identify software vulnerabilities in your container images.

    +
  • +
  • +

    Right size your containers: As you develop and run applications in containers, there are a few key areas to consider. How you size containers and manage your application deployments can negatively impact the end-user experience of services that you provide. To help you succeed, the following best practices will help you right size your containers. After you determine the number of resources required for your application, you should set requests and limits Kubernetes to ensure that your applications are running correctly.

    +
  • +
+

                (a) Perform testing of the application: to gather vital statistics and other performance                 Based upon this data you can work out the optimal configuration, in terms of memory and                 CPU, for your container. Vital statistics such as : CPU, Latency, I/O, Memory usage,                Network . Determine expected, mean, and peak container memory and CPU usage by                 doing a separate load test if necessary. Also consider all the processes that might                 potentially run in parallel in the container.

+

                Recommend to use CloudWatch Container insights or partner products, which will give                 you the right information to size containers and the Worker nodes.

+

                (b)Test services independently: As many applications depend on each other in a true                 microservice architecture, you need to test them with a high degree of independence                 meaning that the services are both able to properly function by themselves, as well as                 function as part of a cohesive system.

+

Resource Management

+

One of the most common questions that asked in the adoption of Kubernetes is "What should I put in a Pod?". For example, a three tier LAMP application container. Should we keep this application in the same pod? Well, this works effectively as a single pod but this is an example of an anti-pattern for Pod creation. There are two reasons for that

+

(a) If you have both the containers in the same Pod, you are forced to use the same scaling strategy which is not ideal for production environment also you can't effectively manage or constraint resources based on the usage. E.g: you might need to scale just the frontend not frontend and backend (MySQL) as a unit also if you would like to increase the resources dedicated just to the backend, you cant just do that.

+

(b) If you have two separate pods, one for frontend and other for backend. Scaling would be very easy and you get a better reliability.

+

The above might not work in all the use-cases. In the above example frontend and backend may land in different machines and they will communicate with each other via network, So you need to ask the question "Will my application work correctly, If they are placed and run on different machines?" If the answer is a "no" may be because of the application design or for some other technical reasons, then grouping of containers in a single pod makes sense. If the answer is "Yes" then multiple Pods is the correct approach.

+

Recommendations

+
    +
  • +

    Package a single application per container: +A container works best when a single application runs inside it. This application should have a single parent process. For example, do not run PHP and MySQL in the same container: it's harder to debug, and you can't horizontally scale the PHP container alone. This separation allows you to better tie the lifecycle of the application to that of the container. Your containers should be both stateless and immutable. Stateless means that any state (persistent data of any kind) is stored outside of the container, for example, you can use different kinds of external storage like Persistent disk, Amazon EBS, and Amazon EFS if needed, or managed database like Amazon RDS. Immutable means that a container will not be modified during its life: no updates, no patches, and no configuration changes. To update the application code or apply a patch, you build a new image and deploy it.

    +
  • +
  • +

    Use Labels to Kubernetes Objects: +Labels allow Kubernetes objects to be queried and operated upon in bulk. They can also be used to identify and organize Kubernetes objects into groups. As such defining labels should figure right at the top of any Kubernetes best practices list.

    +
  • +
  • +

    Setting resource request limits: +Setting request limits is the mechanism used to control the amount of system resources that a container can consume such as CPU and memory. These settings are what the container is guaranteed to get when the container initially starts. If a container requests a resource, container orchestrators such as Kubernetes will only schedule it on a node that can provide that resource. Limits, on the other hand, make sure that a container never goes above a certain value. The container is only allowed to go up to the limit, and then it is restricted.

    +
  • +
+

                In the below example Pod manifest, we add a limit of 1.0 CPU and 256 MB of memory

+
        apiVersion: v1
+        kind: Pod
+        metadata:
+          name: nginx-pod-webserver
+          labels:
+            name: nginx-pod
+        spec:
+          containers:
+          - name: nginx
+            image: nginx:latest
+            resources:
+              limits:
+                memory: "256Mi"
+                cpu: "1000m"
+              requests:
+                memory: "128Mi"
+                cpu: "500m"
+            ports:
+            - containerPort: 80
+
+

               It's a best practice to define these requests and limits in your pod definitions. If you don't                include these values, the scheduler doesn't understand what resources are needed.                Without this information, the scheduler might schedule the pod on a node without                sufficient resources to provide acceptable application performance.

+
    +
  • Limit the number of concurrent disruptions: +Use PodDisruptionBudget, This settings allows you to set a policy on the minimum available and maximum unavailable pods during voluntary eviction events. An example of an eviction would be when perform maintenance on the node or draining a node.
  • +
+

                 Example: A web frontend might want to ensure that 8 Pods to be available at any                  given time. In this scenario, an eviction can evict as many pods as it wants, as long as                  eight are available. +

apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+  name: frontend-demo
+spec:
+  minAvailable: 8
+  selector:
+    matchLabels:
+      app: frontend
+

+

                 N.B: You can also specify pod disruption budget as a percentage by using maxAvailable                  or maxUnavailable parameter.

+
    +
  • Use Namespaces: +Namespaces allows a physical cluster to be shared by multiple teams. A namespace allows to partition created resources into a logically named group. This allows you to set resource quotas per namespace, Role-Based Access Control (RBAC) per namespace, and also network policies per namespace. It gives you soft multitenancy features.
  • +
+

                 For example, If you have three applications running on a single Amazon EKS cluster                  accessed by three different teams which requires multiple resource constraints and                  different levels of QoS each group you could create a namespace per team and give each                  team a quota on the number of resources that it can utilize, such as CPU and memory.

+

                 You can also specify default limits in Kubernetes namespaces level by enabling                  LimitRange admission controller. These default limits will constrain the amount of CPU                  or memory a given Pod can use unless the defaults are explicitly overridden by the Pod's                  configuration.

+
    +
  • +

    Manage Resource Quota: +Each namespace can be assigned resource quota. Specifying quota allows to restrict how much of cluster resources can be consumed across all resources in a namespace. Resource quota can be defined by a ResourceQuota object. A presence of ResourceQuota object in a namespace ensures that resource quotas are enforced.

    +
  • +
  • +

    Configure Health Checks for Pods: +Health checks are a simple way to let the system know if an instance of your app is working or not. If an instance of your app is not working, then other services should not access it or send requests to it. Instead, requests should be sent to another instance of the app that is working. The system also should bring your app back to a healthy state. By default, all the running pods have the restart policy set to always which means the kubelet running within a node will automatically restart a pod when the container encounters an error. Health checks extend this capability of kubelet through the concept of container probes.

    +
  • +
+

Kubernetes provides two types of health checks: readiness and liveness probes. For example, consider if one of your applications, which typically runs for long periods of time, transitions to a non-running state and can only recover by being restarted. You can use liveness probes to detect and remedy such situations. Using health checks gives your applications better reliability, and higher uptime.

+
    +
  • Advanced Scheduling Techniques: +Generally, schedulers ensure that pods are placed only on nodes that have sufficient free resources, and across nodes, they try to balance out the resource utilization across nodes, deployments, replicas, and so on. But sometimes you want to control how your pods are scheduled. For example, perhaps you want to ensure that certain pods are only scheduled on nodes with specialized hardware, such as requiring a GPU machine for an ML workload. Or you want to collocate services that communicate frequently.
  • +
+

Kubernetes offers manyadvanced scheduling featuresand multiple filters/constraints to schedule the pods on the right node. For example, when using Amazon EKS, you can usetaints and tolerationsto restrict what workloads can run on specific nodes. You can also control pod scheduling using node selectorsandaffinity and anti-affinityconstructs and even have your own custom scheduler built for this purpose.

+

Scalability Management

+

Containers are stateless. They are born and when they die, they are not resurrected. There are many techniques that you can leverage on Amazon EKS, not only to scale out your containerized applications but also the Kubernetes worker node.

+

Recommendations

+
    +
  • +

    On Amazon EKS, you can configure Horizontal pod autoscaler,which automatically scales the number of pods in a replication controller, deployment, or replica set based on observed CPU utilization (or usecustom metricsbased on application-provided metrics).

    +
  • +
  • +

    You can use Vertical Pod Autoscaler which automatically adjusts the CPU and memory reservations for your pods to help "right size" your applications. This adjustment can improve cluster resource utilization and free up CPU and memory for other pods. This is useful in scenarios like your production database "MongoDB" does not scale the same way as a stateless application frontend, In this scenario you could use VPA to scale up the MongoDB Pod.

    +
  • +
  • +

    To enable VPA you need to use Kubernetes metrics server, which is an aggregator of resource usage data in your cluster. It is not deployed by default in Amazon EKS clusters. You need to configure it before configure VPA alternatively you can also use Prometheus to provide metrics for the Vertical Pod Autoscaler.

    +
  • +
  • +

    While HPA and VPA scale the deployments and pods, Cluster Autoscaler will scale-out and scale-in the size of the pool of worker nodes. It adjusts the size of a Kubernetes cluster based on the current utilization. Cluster Autoscaler increases the size of the cluster when there are pods that failed to schedule on any of the current nodes due to insufficient resources or when adding a new node would increase the overall availability of cluster resources. Please follow this step by step guide to setup Cluster Autoscaler. If you are using Amazon EKS on AWS Fargate, AWS Manages the control plane for you.

    +

    Please have a look at the reliability pillar for detailed information.

    +
  • +
+

Monitoring

+

Deployment Best Practices

+

Trade-Offs

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/reliability/docs/application/index.html b/reliability/docs/application/index.html new file mode 100644 index 000000000..192fdf815 --- /dev/null +++ b/reliability/docs/application/index.html @@ -0,0 +1,2983 @@ + + + + + + + + + + + + + + + + + + + + + + + Applications - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Running highly-available applications

+

Your customers expect your application to be always available, including when you're making changes and especially during spikes in traffic. A scalable and resilient architecture keeps your applications and services running without disruptions, which keeps your users happy. A scalable infrastructure grows and shrinks based on the needs of the business. Eliminating single points of failure is a critical step towards improving an application’s availability and making it resilient.

+

With Kubernetes, you can operate your applications and run them in a highly-available and resilient fashion. Its declarative management ensures that once you’ve set up the application, Kubernetes will continuously try to match the current state with the desired state.

+

Recommendations

+

Avoid running singleton Pods

+

If your entire application runs in a single Pod, then your application will be unavailable if that Pod gets terminated. Instead of deploying applications using individual pods, create Deployments. If a Pod that is created by a Deployment fails or gets terminated, the Deployment controller will start a new pod to ensure the specified number of replica Pods are always running.

+

Run multiple replicas

+

Running multiple replicas Pods of an app using a Deployment helps it run in a highly-available manner. If one replica fails, the remaining replicas will still function, albeit at reduced capacity until Kubernetes creates another Pod to make up for the loss. Furthermore, you can use the Horizontal Pod Autoscaler to scale replicas automatically based on workload demand.

+

Schedule replicas across nodes

+

Running multiple replicas won’t be very useful if all the replicas are running on the same node, and the node becomes unavailable. Consider using pod anti-affinity or pod topology spread constraints to spread replicas of a Deployment across multiple worker nodes.

+

You can further improve a typical application’s reliability by running it across multiple AZs.

+

Using Pod anti-affinity rules

+

The manifest below tells Kubernetes scheduler to prefer to place pods on separate nodes and AZs. It doesn’t require distinct nodes or AZ because if it did, then Kubernetes will not be able to schedule any pods once there is a pod running in each AZ. If your application requires just three replicas, you can use requiredDuringSchedulingIgnoredDuringExecution for topologyKey: topology.kubernetes.io/zone, and Kubernetes scheduler will not schedule two pods in the same AZ.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: spread-host-az
+  labels:
+    app: web-server
+spec:
+  replicas: 4
+  selector:
+    matchLabels:
+      app: web-server
+  template:
+    metadata:
+      labels:
+        app: web-server
+    spec:
+      affinity:
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app
+                  operator: In
+                  values:
+                  - web-server
+              topologyKey: topology.kubernetes.io/zone
+            weight: 100
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app
+                  operator: In
+                  values:
+                  - web-server
+              topologyKey: kubernetes.io/hostname 
+            weight: 99
+      containers:
+      - name: web-app
+        image: nginx:1.16-alpine
+
+

Using Pod topology spread constraints

+

Similar to pod anti-affinity rules, pod topology spread constraints allow you to make your application available across different failure (or topology) domains like hosts or AZs. This approach works very well when you're trying to ensure fault tolerance as well as availability by having multiple replicas in each of the different topology domains. Pod anti-affinity rules, on the other hand, can easily produce a result where you have a single replica in a topology domain because the pods with an anti-affinity toward each other have a repelling effect. In such cases, a single replica on a dedicated node isn't ideal for fault tolerance nor is it a good use of resources. With topology spread constraints, you have more control over the spread or distribution that the scheduler should try to apply across the topology domains. Here are some important properties to use in this approach: +1. The maxSkew is used to control or determine the maximum point to which things can be uneven across the topology domains. For example, if an application has 10 replicas and is deployed across 3 AZs, you can't get an even spread, but you can influence how uneven the distribution will be. In this case, the maxSkew can be anything between 1 and 10. A value of 1 means you can potentially end up with a spread like 4,3,3, 3,4,3 or 3,3,4 across the 3 AZs. In contrast, a value of 10 means you can potentially end up with a spread like 10,0,0, 0,10,0 or 0,0,10 across 3 AZs. +2. The topologyKey is a key for one of the node labels and defines the type of topology domain that should be used for the pod distribution. For example, a zonal spread would have the following key-value pair: +

topologyKey: "topology.kubernetes.io/zone"
+
+3. The whenUnsatisfiable property is used to determine how you want the scheduler to respond if the desired constraints can't be satisfied. +4. The labelSelector is used to find matching pods so that the scheduler can be aware of them when deciding where to place pods in accordance with the constraints that you specify.

+

In addition to these above, there are other fields that you can read about further in the Kubernetes documentation.

+

Pod topology spread constraints across 3 AZs

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: spread-host-az
+  labels:
+    app: web-server
+spec:
+  replicas: 10
+  selector:
+    matchLabels:
+      app: web-server
+  template:
+    metadata:
+      labels:
+        app: web-server
+    spec:
+      topologySpreadConstraints:
+      - maxSkew: 1
+        topologyKey: "topology.kubernetes.io/zone"
+        whenUnsatisfiable: ScheduleAnyway
+        labelSelector:
+          matchLabels:
+            app: express-test
+      containers:
+      - name: web-app
+        image: nginx:1.16-alpine
+
+

Run Kubernetes Metrics Server

+

Install the Kubernetes metrics server to help scale your applications. Kubernetes autoscaler add-ons like HPA and VPA need to track metrics of applications to scale them. The metrics-server collects resource metrics that can be used to make scaling decisions. The metrics are collected from kubelets and served in Metrics API format.

+

The metrics server doesn’t retain any data, and it’s not a monitoring solution. Its purpose is to expose CPU and memory usage metrics to other systems. If you want to track your application's state over time, you need a monitoring tool like Prometheus or Amazon CloudWatch.

+

Follow the EKS documentation to install metrics-server in your EKS cluster.

+

Horizontal Pod Autoscaler (HPA)

+

HPA can automatically scale your application in response to demand and help you avoid impacting your customers during peak traffic. It is implemented as a control loop in Kubernetes that periodically queries metrics from APIs that provide resource metrics.

+

HPA can retrieve metrics from the following APIs: +1. metrics.k8s.io also known as Resource Metrics API — Provides CPU and memory usage for pods +2. custom.metrics.k8s.io — Provides metrics from other metric collectors like Prometheus; these metrics are internal to your Kubernetes cluster. +3. external.metrics.k8s.io — Provides metrics that are external to your Kubernetes cluster (E.g., SQS Queue Depth, ELB latency).

+

You must use one of these three APIs to provide the metric to scale your application.

+

Scaling applications based on custom or external metrics

+

You can use custom or external metrics to scale your application on metrics other than CPU or memory utilization. Custom Metrics API servers provide the custom-metrics.k8s.io API that HPA can use to autoscale applications.

+

You can use the Prometheus Adapter for Kubernetes Metrics APIs to collect metrics from Prometheus and use with the HPA. In this case, Prometheus adapter will expose Prometheus metrics in Metrics API format.

+

Once you deploy the Prometheus Adapter, you can query custom metrics using kubectl. +kubectl get —raw /apis/custom.metrics.k8s.io/v1beta1/

+

External metrics, as the name suggests, provide the Horizontal Pod Autoscaler the ability to scale deployments using metrics that are external to the Kubernetes cluster. For example, in batch processing workloads, it is common to scale the number of replicas based on the number of jobs in flight in an SQS queue.

+

To autoscale Kubernetes workloads you can use KEDA (Kubernetes Event-driven Autoscaling), an open-source project that can drive container scaling based on a number of custom events. This AWS blog outlines how to use Amazon Managed Service for Prometheus for Kubernetes workload auto-scaling.

+

Vertical Pod Autoscaler (VPA)

+

VPA automatically adjusts the CPU and memory reservation for your Pods to help you “right-size” your applications. For applications that need to be scaled vertically - which is done by increasing resource allocation - you can use VPA to automatically scale Pod replicas or provide scaling recommendations.

+

Your application may become temporarily unavailable if VPA needs to scale it because VPA’s current implementation does not perform in-place adjustments to Pods; instead, it will recreate the Pod that needs to be scaled.

+

EKS Documentation includes a walkthrough for setting up VPA.

+

Fairwinds Goldilocks project provides a dashboard to visualize VPA recommendations for CPU and memory requests and limits. Its VPA update mode allows you to auto-scale Pods based on VPA recommendations.

+

Updating applications

+

Modern applications require rapid innovation with a high degree of stability and availability. Kubernetes gives you the tools to update your applications continuously without disrupting your customers.

+

Let’s look at some of the best practices that make it possible to quickly deploy changes without sacrificing availability.

+

Have a mechanism to perform rollbacks

+

Having an undo button can evade disasters. It is a best practice to test deployments in a separate lower environment (test or development environment) before updating the production cluster. Using a CI/CD pipeline can help you automate and test deployments. With a continuous deployment pipeline, you can quickly revert to the older version if the upgrade happens to be defective.

+

You can use Deployments to update a running application. This is typically done by updating the container image. You can use kubectl to update a Deployment like this:

+
kubectl --record deployment.apps/nginx-deployment set image nginx-deployment nginx=nginx:1.16.1
+
+

The --record argument record the changes to the Deployment and helps you if you need to perform a rollback. kubectl rollout history deployment shows you the recorded changes to Deployments in your cluster. You can rollback a change using kubectl rollout undo deployment <DEPLOYMENT_NAME>.

+

By default, when you update a Deployment that requires a recreation of pods, Deployment will perform a rolling update. In other words, Kubernetes will only update a portion of the running pods in a Deployment and not all the Pods at once. You can control how Kubernetes performs rolling updates through RollingUpdateStrategy property.

+

When performing a rolling update of a Deployment, you can use the Max Unavailable property to specify the maximum number of Pods that can be unavailable during the update. The Max Surge property of Deployment allows you to set the maximum number of Pods that can be created over the desired number of Pods.

+

Consider adjusting max unavailable to ensure that a rollout doesn’t disrupt your customers. For example, Kubernetes sets 25% max unavailable by default, which means if you have 100 Pods, you may have only 75 Pods actively working during a rollout. If your application needs a minimum of 80 Pods, this rollout can be disruptive. Instead, you can set max unavailable to 20% to ensure that there are at least 80 functional Pods throughout the rollout.

+

Use blue/green deployments

+

Changes are inherently risky, but changes that cannot be undone can be potentially catastrophic. Change procedures that allow you to effectively turn back time through a rollback make enhancements and experimentation safer. Blue/green deployments give you a method to quickly retract the changes if things go wrong. In this deployment strategy, you create an environment for the new version. This environment is identical to the current version of the application being updated. Once the new environment is provisioned, traffic is routed to the new environment. If the new version produces the desired results without generating errors, the old environment is terminated. Otherwise, traffic is restored to the old version.

+

You can perform blue/green deployments in Kubernetes by creating a new Deployment that is identical to the existing version’s Deployment. Once you verify that the Pods in the new Deployment are running without errors, you can start sending traffic to the new Deployment by changing the selector spec in the Service that routes traffic to your application’s Pods.

+

Many continuous integration tools such as Flux, Jenkins, and Spinnaker let you automate blue/green deployments. AWS Containers Blog includes a walkthrough using AWS Load Balancer Controller: Using AWS Load Balancer Controller for blue/green deployment, canary deployment and A/B testing

+

Use Canary deployments

+

Canary deployments are a variant of blue/green deployments that can significantly remove risk from changes. In this deployment strategy, you create a new Deployment with fewer Pods alongside your old Deployment, and divert a small percentage of traffic to the new Deployment. If metrics indicate that the new version is performing as well or better than the existing version, you progressively increase traffic to the new Deployment while scaling it up until all traffic is diverted to the new Deployment. If there's an issue, you can route all traffic to the old Deployment and stop sending traffic to the new Deployment.

+

Although Kubernetes offers no native way to perform canary deployments, you can use tools such as Flagger with Istio or App Mesh.

+

Health checks and self-healing

+

No software is bug-free, but Kubernetes can help you to minimize the impact of software failures. In the past, if an application crashed, someone had to remediate the situation by restarting the application manually. Kubernetes gives you the ability to detect software failures in your Pods and automatically replace them with new replicas. With Kubernetes you can monitor the health of your applications and automatically replace unhealthy instances.

+

Kubernetes supports three types of health-checks:

+
    +
  1. Liveness probe
  2. +
  3. Startup probe (supported in Kubernetes version 1.16+)
  4. +
  5. Readiness probe
  6. +
+

Kubelet, the Kubernetes agent, is responsible for running all the above-mentioned checks. Kubelet can check a Pods' health in three ways: kubelet can either run a shell command inside a Pod's container, send an HTTP GET request to its container, or open a TCP socket on a specified port.

+

If you choose an exec-based probe, which runs a shell script inside a container, ensure that the shell command exits before the timeoutSeconds value expires. Otherwise, your node will have <defunct> processes, leading to node failure.

+

Recommendations

+

Use Liveness Probe to remove unhealthy pods

+

The Liveness probe can detect deadlock conditions where the process continues to run, but the application becomes unresponsive. For example, if you are running a web service that listens on port 80, you can configure a Liveness probe to send an HTTP GET request on Pod’s port 80. Kubelet will periodically send a GET request to the Pod and expect a response; if the Pod responds between 200-399 then the kubelet considers that Pod is healthy; otherwise, the Pod will be marked as unhealthy. If a Pod fails health-checks continuously, the kubelet will terminate it.

+

You can use initialDelaySeconds to delay the first probe.

+

When using the Liveness Probe, ensure that your application doesn’t run into a situation in which all Pods simultaneously fail the Liveness Probe because Kubernetes will try to replace all your Pods, which will render your application offline. Furthermore, Kubernetes will continue to create new Pods that will also fail Liveness Probes, putting unnecessary strain on the control plane. Avoid configuring the Liveness Probe to depend on an a factor that is external to your Pod, for example, a external database. In other words, a non-responsive external-to-your-Pod database shouldn’t make your Pods fail their Liveness Probes.

+

Sandor Szücs’s post LIVENESS PROBES ARE DANGEROUS describes problems that can be caused by misconfigured probes.

+

Use Startup Probe for applications that take longer to start

+

When your app needs additional time to startup, you can use the Startup Probe to delay the Liveness and Readiness Probe. For example, a Java app that needs to hydrate cache from a database may need up to two minutes before it is fully functional. Any Liveness or Readiness Probe until it becomes fully functional might fail. Configuring a Startup Probe will allow the Java app to become healthy before Liveness or Readiness Probe are executed.

+

Until the Startup Probe succeeds, all the other Probes are disabled. You can define the maximum time Kubernetes should wait for application startup. If, after the maximum configured time, the Pod still fails Startup Probes, it will be terminated, and a new Pod will be created.

+

The Startup Probe is similar to the Liveness Probe -- if they fail, the Pod is recreated. As Ricardo A. explains in his post Fantastic Probes And How To Configure Them, Startup Probes should be used when the startup time of an application is unpredictable. If you know your application needs ten seconds to start, you should use Liveness/Readiness Probe with initialDelaySeconds instead.

+

Use Readiness Probe to detect partial unavailability

+

While the Liveness probe detects failures in an app that are resolved by terminating the Pod (hence, restarting the app), Readiness Probe detects conditions where the app may be temporarily unavailable. In these situations, the app may become temporarily unresponsive; however, it is expected to be healthy again once this operation completes.

+

For example, during intense disk I/O operations, applications may be temporarily unavailable to handle requests. Here, terminating the application’s Pod is not a remedy; at the same time, additional requests sent to the Pod can fail.

+

You can use the Readiness Probe to detect temporary unavailability in your app and stop sending requests to its Pod until it becomes functional again. Unlike Liveness Probe, where a failure would result in a recreation of Pod, a failed Readiness Probe would mean that Pod will not receive any traffic from Kubernetes Service. When the Readiness Probe succeeds, Pod will resume receiving traffic from Service.

+

Just like the Liveness Probe, avoid configuring Readiness Probes that depend on a resource that’s external to the Pod (such as a database). Here’s a scenario where a poorly configured Readiness can render the application nonfunctional - if a Pod’s Readiness Probe fails when the app’s database is unreachable, other Pod replicas will also fail simultaneously since they share the same health-check criteria. Setting the probe in this way will ensure that whenever the database is unavailable, the Pod’s Readiness Probes will fail, and Kubernetes will stop sending traffic all Pods.

+

A side-effect of using Readiness Probes is that they can increase the time it takes to update Deployments. New replicas will not receive traffic unless Readiness Probes are successful; until then, old replicas will continue to receive traffic.

+
+

Dealing with disruptions

+

Pods have a finite lifetime - even if you have long-running Pods, it’s prudent to ensure Pods terminate correctly when the time comes. Depending on your upgrade strategy, Kubernetes cluster upgrades may require you to create new worker nodes, which requires all Pods to be recreated on newer nodes. Proper termination handling and Pod Disruption Budgets can help you avoid service disruptions as Pods are removed from older nodes and recreated on newer nodes.

+

The preferred way to upgrade worker nodes is by creating new worker nodes and terminating old ones. Before terminating worker nodes, you should drain it. When a worker node is drained, all its pods are safely evicted. Safely is a key word here; when pods on a worker are evicted, they are not simply sent a SIGKILL signal. Instead, a SIGTERM signal is sent to the main process (PID 1) of each container in the Pods being evicted. After the SIGTERM signal is sent, Kubernetes will give the process some time (grace period) before a SIGKILL signal is sent. This grace period is 30 seconds by default; you can override the default by using grace-period flag in kubectl or declare terminationGracePeriodSeconds in your Podspec.

+

kubectl delete pod <pod name> —grace-period=<seconds>

+

It is common to have containers in which the main process doesn’t have PID 1. Consider this Python-based sample container:

+
$ kubectl exec python-app -it ps
+ PID USER TIME COMMAND
+ 1   root 0:00 {script.sh} /bin/sh ./script.sh
+ 5   root 0:00 python app.py
+
+

In this example, the shell script receives SIGTERM, the main process, which happens to be a Python application in this example, doesn’t get a SIGTERM signal. When the Pod is terminated, the Python application will be killed abruptly. This can be remediated by changing the ENTRYPOINT of the container to launch the Python application. Alternatively, you can use a tool like dumb-init to ensure that your application can handle signals.

+

You can also use Container hooks to execute a script or an HTTP request at container start or stop. The PreStop hook action runs before the container receives a SIGTERM signal and must complete before this signal is sent. The terminationGracePeriodSeconds value applies from when the PreStop hook action begins executing, not when the SIGTERM signal is sent.

+

Recommendations

+

Protect critical workload with Pod Disruption Budgets

+

Pod Disruption Budget or PDB can temporarily halt the eviction process if the number of replicas of an application falls below the declared threshold. The eviction process will continue once the number of available replicas is over the threshold. You can use PDB to declare the minAvailable and maxUnavailable number of replicas. For example, if you want at least three copies of your app to be available, you can create a PDB.

+
apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+  name: my-svc-pdb
+spec:
+  minAvailable: 3
+  selector:
+    matchLabels:
+      app: my-svc
+
+

The above PDB policy tells Kubernetes to halt the eviction process until three or more replicas are available. Node draining respects PodDisruptionBudgets. During an EKS managed node group upgrade, nodes are drained with a fifteen-minute timeout. After fifteen minutes, if the update is not forced (the option is called Rolling update in the EKS console), the update fails. If the update is forced, the pods are deleted.

+

For self-managed nodes, you can also use tools like AWS Node Termination Handler, which ensures that the Kubernetes control plane responds appropriately to events that can cause your EC2 instance to become unavailable, such as EC2 maintenance events and EC2 Spot interruptions. It uses the Kubernetes API to cordon the node to ensure no new Pods are scheduled, then drains it, terminating any running Pods.

+

You can use Pod anti-affinity to schedule a Deployment‘s Pods on different nodes and avoid PDB related delays during node upgrades.

+

Practice chaos engineering

+
+

Chaos Engineering is the discipline of experimenting on a distributed system in order to build confidence in the system’s capability to withstand turbulent conditions in production.

+
+

In his blog, Dominik Tornow explains that Kubernetes is a declarative system where “the user supplies a representation of the desired state of the system to the system. The system then considers the current state and the desired state to determine the sequence of commands to transition from the current state to the desired state.” This means Kubernetes always stores the desired state and if the system deviates, Kubernetes will take action to restore the state. For example, if a worker node becomes unavailable, Kubernetes will reschedule the Pods onto another worker node. Similarly, if a replica crashes, the Deployment Contoller will create a new replica. In this way, Kubernetes controllers automatically fix failures.

+

Chaos engineering tools like Gremlin help you test the resiliency of your Kubernetes cluster and identify single points of failure. Tools that introduce artificial chaos in your cluster (and beyond) can uncover systemic weaknesses, present an opportunity to identify bottlenecks and misconfigurations, and rectify problems in a controlled environment. The Chaos Engineering philosophy advocates breaking things on purpose and stress testing infrastructure to minimize unanticipated downtime.

+

Use a Service Mesh

+

You can use a service mesh to improve your application’s resiliency. Service meshes enable service-to-service communication and increase the observability of your microservices network. Most service mesh products work by having a small network proxy run alongside each service that intercepts and inspects the application’s network traffic. You can place your application in a mesh without modifying your application. Using service proxy’s built-in features, you can have it generate network statistics, create access logs, and add HTTP headers to outbound requests for distributed tracing.

+

A service mesh can help you make your microservices more resilient with features like automatic request retries, timeouts, circuit-breaking, and rate-limiting.

+

If you operate multiple clusters, you can use a service mesh to enable cross-cluster service-to-service communication.

+

Service Meshes

+ +
+

Observability

+

Observability is an umbrella term that includes monitoring, logging, and tracing. Microservices based applications are distributed by nature. Unlike monolithic applications where monitoring a single system is sufficient, in a distributed application architecture, you need to monitor each component’s performance. You can use cluster-level monitoring, logging, and distributed tracing systems to identify issues in your cluster before they disrupt your customers.

+

Kubernetes built-in tools for troubleshooting and monitoring are limited. The metrics-server collects resource metrics and stores them in memory but doesn’t persist them. You can view the logs of a Pod using kubectl, but Kubernetes doesn't automatically retain logs. And the implementation of distributed tracing is done either at the application code level or using services meshes.

+

Kubernetes' extensibility shines here. Kubernetes allows you to bring your preferred centralized monitoring, logging, and tracing solution.

+

Recommendations

+

Monitor your applications

+

The number of metrics you need to monitor in modern applications is growing continuously. It helps if you have an automated way to track your applications so you can focus on solving your customer’s challenges. Cluster-wide monitoring tools like Prometheus or CloudWatch Container Insights can monitor your cluster and workload and provide you signals when, or preferably, before things go wrong.

+

Monitoring tools allow you to create alerts that your operations team can subscribe to. Consider rules to activate alarms for events that can, when exacerbated, lead to an outage or impact application performance.

+

If you’re unclear on which metrics you should monitor, you can take inspiration from these methods:

+
    +
  • RED method. Stands for requests, errors, and duration.
  • +
  • USE method. Stands for utilization, saturation, and errors.
  • +
+

Sysdig’s post Best practices for alerting on Kubernetes includes a comprehensive list of components that can impact the availability of your applications.

+

Use Prometheus client library to expose application metrics

+

In addition to monitoring the state of the application and aggregating standard metrics, you can also use the Prometheus client library to expose application-specific custom metrics to improve the application's observability.

+

Use centralized logging tools to collect and persist logs

+

Logging in EKS falls under two categories: control plane logs and application logs. EKS control plane logging provides audit and diagnostic logs directly from the control plane to CloudWatch Logs in your account. Application logs are logs produced by Pods running inside your cluster. Application logs include logs produced by Pods that run the business logic applications and Kubernetes system components such as CoreDNS, Cluster Autoscaler, Prometheus, etc.

+

EKS provide five types of control plane logs:

+
    +
  1. Kubernetes API server component logs
  2. +
  3. Audit
  4. +
  5. Authenticator
  6. +
  7. Controller manager
  8. +
  9. Scheduler
  10. +
+

The controller manager and scheduler logs can help diagnose control plane problems such as bottlenecks and errors. By default, EKS control plane logs aren’t sent to CloudWatch Logs. You can enable control plane logging and select the types of EKS control plane logs you’d like to capture for each cluster in your account

+

Collecting application logs requires installing a log aggregator tool like Fluent Bit, Fluentd, or CloudWatch Container Insights in your cluster.

+

Kubernetes log aggregator tools run as DaemonSets and scrape container logs from nodes. Application logs are then sent to a centralized destination for storage. For example, CloudWatch Container Insights can use either Fluent Bit or Fluentd to collect logs and ship them to CloudWatch Logs for storage. Fluent Bit and Fluentd support many popular log analytics systems such as Elasticsearch and InfluxDB giving you the ability to change the storage backend for your logs by modifying Fluent bit or Fluentd’s log configuration.

+

Use a distributed tracing system to identify bottlenecks

+

A typical modern application has components distributed over the network, and its reliability depends on the proper functioning of each of the components that make up the application. You can use a distributed tracing solution to understand how requests flow and how systems communicate. Traces can show you where bottlenecks exist in your application network and prevent problems that can cause cascading failures.

+

You have two options to implement tracing in your applications: you can either implement distributed tracing at the code level using shared libraries or use a service mesh.

+

Implementing tracing at the code level can be disadvantageous. In this method, you have to make changes to your code. This is further complicated if you have polyglot applications. You’re also responsible for maintaining yet another library, across your services.

+

Service Meshes like LinkerD, Istio, and AWS App Mesh can be used to implement distributed tracing in your application with minimal changes to the application code. You can use service mesh to standardize metrics generation, logging, and tracing.

+

Tracing tools like AWS X-Ray, Jaeger support both shared library and service mesh implementations.

+

Consider using a tracing tool like AWS X-Ray or Jaeger that supports both (shared library and service mesh) implementations so you will not have to switch tools if you later adopt service mesh.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/reliability/docs/controlplane/index.html b/reliability/docs/controlplane/index.html new file mode 100644 index 000000000..3f56e4593 --- /dev/null +++ b/reliability/docs/controlplane/index.html @@ -0,0 +1,2529 @@ + + + + + + + + + + + + + + + + + + + + + + + Control Plane - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

EKS Control Plane

+

Amazon Elastic Kubernetes Service (EKS) is a managed Kubernetes service that makes it easy for you to run Kubernetes on AWS without needing to install, operate, and maintain your own Kubernetes control plane or worker nodes. It runs upstream Kubernetes and is certified Kubernetes conformant. This conformance ensures that EKS supports the Kubernetes APIs, just like the open-source community version that you can install on EC2 or on-premises. Existing applications running on upstream Kubernetes are compatible with Amazon EKS.

+

EKS automatically manages the availability and scalability of the Kubernetes control plane nodes, and it automatically replaces unhealthy control plane nodes.

+

EKS Architecture

+

EKS architecture is designed to eliminate any single points of failure that may compromise the availability and durability of the Kubernetes control plane.

+

The Kubernetes control plane managed by EKS runs inside an EKS managed VPC. The EKS control plane comprises the Kubernetes API server nodes, etcd cluster. Kubernetes API server nodes that run components like the API server, scheduler, and kube-controller-manager run in an auto-scaling group. EKS runs a minimum of two API server nodes in distinct Availability Zones (AZs) within in AWS region. Likewise, for durability, the etcd server nodes also run in an auto-scaling group that spans three AZs. EKS runs a NAT Gateway in each AZ, and API servers and etcd servers run in a private subnet. This architecture ensures that an event in a single AZ doesn’t affect the EKS cluster's availability.

+

When you create a new cluster, Amazon EKS creates a highly-available endpoint for the managed Kubernetes API server that you use to communicate with your cluster (using tools like kubectl). The managed endpoint uses NLB to load balance Kubernetes API servers. EKS also provisions two ENIs in different AZs to facilitate communication to your worker nodes.

+

EKS Data plane network connectivity

+

You can configure whether your Kubernetes cluster’s API server is reachable from the public internet (using the public endpoint) or through your VPC (using the EKS-managed ENIs) or both.

+

Whether users and worker nodes connect to the API server using the public endpoint or the EKS-managed ENI, there are redundant paths for connection.

+

Recommendations

+

Monitor Control Plane Metrics

+

Monitoring Kubernetes API metrics can give you insights into control plane performance and identify issues. An unhealthy control plane can compromise the availability of the workloads running inside the cluster. For example, poorly written controllers can overload the API servers, affecting your application's availability.

+

Kubernetes exposes control plane metrics at the /metrics endpoint.

+

You can view the metrics exposed using kubectl:

+
kubectl get --raw /metrics
+
+

These metrics are represented in a Prometheus text format.

+

You can use Prometheus to collect and store these metrics. In May 2020, CloudWatch added support for monitoring Prometheus metrics in CloudWatch Container Insights. So you can also use Amazon CloudWatch to monitor the EKS control plane. You can use Tutorial for Adding a New Prometheus Scrape Target: Prometheus KPI Server Metrics to collect metrics and create CloudWatch dashboard to monitor your cluster’s control plane.

+

You can find Kubernetes API server metrics here. For example, apiserver_request_duration_seconds can indicate how long API requests are taking to run.

+

Consider monitoring these control plane metrics:

+

API Server

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricDescription
apiserver_request_totalCounter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code.
apiserver_request_duration_seconds*Response latency distribution in seconds for each verb, dry run value, group, version, resource, subresource, scope, and component.
apiserver_admission_controller_admission_duration_secondsAdmission controller latency histogram in seconds, identified by name and broken out for each operation and API resource and type (validate or admit).
apiserver_admission_webhook_rejection_countCount of admission webhook rejections. Identified by name, operation, rejection_code, type (validating or admit), error_type (calling_webhook_error, apiserver_internal_error, no_error)
rest_client_request_duration_secondsRequest latency in seconds. Broken down by verb and URL.
rest_client_requests_totalNumber of HTTP requests, partitioned by status code, method, and host.
+

etcd

+ + + + + + + + + + + + + + + + + +
MetricDescription
etcd_request_duration_secondsEtcd request latency in seconds for each operation and object type.
etcd_db_total_size_in_bytes or
apiserver_storage_db_total_size_in_bytes (starting with EKS v1.26) or
apiserver_storage_size_bytes (starting with EKS v1.28)
Etcd database size.
+

Consider using the Kubernetes Monitoring Overview Dashboard to visualize and monitor Kubernetes API server requests and latency and etcd latency metrics.

+

The following Prometheus query can be used to monitor the current size of etcd. The query assumes there is job called kube-apiserver for scraping metrics from API metrics endpoint and the EKS version is below v1.26.

+
max(etcd_db_total_size_in_bytes{job="kube-apiserver"} / (8 * 1024 * 1024 * 1024))
+
+
+

Attention

+

When the database size limit is exceeded, etcd emits a no space alarm and stops taking further write requests. In other words, the cluster becomes read-only, and all requests to mutate objects such as creating new pods, scaling deployments, etc., will be rejected by the cluster’s API server.

+
+

Cluster Authentication

+

EKS currently supports two types of authentication: bearer/service account tokens and IAM authentication which uses webhook token authentication. When users call the Kubernetes API, a webhook passes an authentication token included in the request to IAM. The token, a base 64 signed URL, is generated by the AWS Command Line Interface (AWS CLI).

+

The IAM user or role that creates the EKS Cluster automatically gets full access to the cluster. You can manage access to the EKS cluster by editing the aws-auth configmap.

+

If you misconfigure the aws-auth configmap and lose access to the cluster, you can still use the cluster creator’s user or role to access your EKS cluster.

+

In the unlikely event that you cannot use the IAM service in the AWS region, you can also use the Kubernetes service account’s bearer token to manage the cluster.

+

Create a “super-admin” account that is permitted to perform all actions in the cluster:

+
kubectl -n kube-system create serviceaccount super-admin
+
+

Create a role binding that gives super-admin cluster-admin role:

+
kubectl create clusterrolebinding super-admin-rb --clusterrole=cluster-admin --serviceaccount=kube-system:super-admin
+
+

Get service account’s secret:

+
SECRET_NAME=`kubectl -n kube-system get serviceaccount/super-admin -o jsonpath='{.secrets[0].name}'`
+
+

Get token associated with the secret:

+
TOKEN=`kubectl -n kube-system get secret $SECRET_NAME -o jsonpath='{.data.token}'| base64 --decode`
+
+

Add service account and token to kubeconfig:

+
kubectl config set-credentials super-admin --token=$TOKEN
+
+

Set the current-context in kubeconfig to use super-admin account:

+
kubectl config set-context --current --user=super-admin
+
+

Final kubeconfig should look like this:

+
apiVersion: v1
+clusters:
+- cluster:
+    certificate-authority-data:<REDACTED>
+    server: https://<CLUSTER>.gr7.us-west-2.eks.amazonaws.com
+  name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+contexts:
+- context:
+    cluster: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+    user: super-admin
+  name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+current-context: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+kind: Config
+preferences: {}
+users:
+#- name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>
+#  user:
+#    exec:
+#      apiVersion: client.authentication.k8s.io/v1alpha1
+#      args:
+#      - --region
+#      - us-west-2
+#      - eks
+#      - get-token
+#      - --cluster-name
+#      - <<cluster name>>
+#      command: aws
+#      env: null
+- name: super-admin
+  user:
+    token: <<super-admin sa’s secret>>
+
+

Admission Webhooks

+

Kubernetes has two types of admission webhooks: validating admission webhooks and mutating admission webhooks. These allow a user to extend the kubernetes API and validate or mutate objects before they are accepted by the API. Poor configurations of these webhooks can distabilize the EKS control plane by blocking cluster critical operations.

+

In order to avoid impacting cluster critical operations either avoid setting "catch-all" webhooks like the following:

+
- name: "pod-policy.example.com"
+  rules:
+  - apiGroups:   ["*"]
+    apiVersions: ["*"]
+    operations:  ["*"]
+    resources:   ["*"]
+    scope: "*"
+
+

Or make sure the webhook has a fail open policy with a timeout shorter than 30 seconds to ensure that if your webhook is unavailable it will not impair cluster critical workloads.

+

Block Pods with unsafe sysctls

+

Sysctl is a Linux utility that allows users to modify kernel parameters during runtime. These kernel parameters control various aspects of the operating system's behavior, such as network, file system, virtual memory, and process management.

+

Kubernetes allows assigning sysctl profiles for Pods. Kubernetes categorizes systcls as safe and unsafe. Safe sysctls are namespaced in the container or Pod, and setting them doesn’t impact other Pods on the node or the node itself. In contrast, unsafe sysctls are disabled by default since they can potentially disrupt other Pods or make the node unstable.

+

As unsafe sysctls are disabled by default, the kubelet will not create a Pod with unsafe sysctl profile. If you create such a Pod, the scheduler will repeatedly assign such Pods to nodes, while the node fails to launch it. This infinite loop ultimately strains the cluster control plane, making the cluster unstable.

+

Consider using OPA Gatekeeper or Kyverno to reject Pods with unsafe sysctls.

+

Handling Cluster Upgrades

+

Since April 2021, Kubernetes release cycle has been changed from four releases a year (once a quarter) to three releases a year. A new minor version (like 1.21 or 1.22) is released approximately every fifteen weeks. Starting with Kubernetes 1.19, each minor version is supported for approximately twelve months after it's first released. With the advent of Kubernetes v1.28, the compatibility skew between the control plane and worker nodes has expanded from n-2 to n-3 minor versions. To learn more, see Best Practices for Cluster Upgrades.

+

Cluster Endpoint Connectivity

+

When working with Amazon EKS (Elastic Kubernetes Service), you may encounter connection timeouts or errors during events such as Kubernetes control plane scaling or patching. These events can cause the kube-apiserver instances to be replaced, potentially resulting in different IP addresses being returned when resolving the FQDN. This document outlines best practices for Kubernetes API consumers to maintain reliable connectivity. Note: Implementing these best practices may require updates to client configurations or scripts to handle new DNS re-resolution and retry strategies effectively.

+

The main issue stems from DNS client-side caching and the potential for stale IP addresses of EKS endpoint - public NLB for public endpoint or X-ENI for private endpoint. When the kube-apiserver instances are replaced, the Fully Qualified Domain Name (FQDN) may resolve to new IP addresses. However, due to DNS Time to Live (TTL)settings, which are set to 60 seconds in the AWS managed Route 53 zone, clients may continue to use outdated IP addresses for a short period of time.

+

To mitigate these issues, Kubernetes API consumers (such as kubectl, CI/CD pipelines, and custom applications) should implement the following best practices:

+
    +
  • Implement DNS re-resolution
  • +
  • Implement Retries with Backoff and Jitter. For example, see this article titled Failures Happen
  • +
  • +

    Implement Client Timeouts. Set appropriate timeouts to prevent long-running requests from blocking your application. Be aware that some Kubernetes client libraries, particularly those generated by OpenAPI generators, may not allow setting custom timeouts easily.

    +
      +
    • Example 1 with kubectl:
    • +
    +
    kubectl get pods --request-timeout 10s # default: no timeout
    +
    + +
  • +
+

By implementing these best practices, you can significantly improve the reliability and resilience of your applications when interacting with Kubernetes API. Remember to test these implementations thoroughly, especially under simulated failure conditions, to ensure they behave as expected during actual scaling or patching events.

+

Running large clusters

+

EKS actively monitors the load on control plane instances and automatically scales them to ensure high performance. However, you should account for potential performance issues and limits within Kubernetes and quotas in AWS services when running large clusters.

+
    +
  • Clusters with more than 1000 services may experience network latency with using kube-proxy in iptables mode according to the tests performed by the ProjectCalico team. The solution is to switch to running kube-proxy in ipvs mode.
  • +
  • You may also experience EC2 API request throttling if the CNI needs to request IP addresses for Pods or if you need to create new EC2 instances frequently. You can reduce calls EC2 API by configuring the CNI to cache IP addresses. You can use larger EC2 instance types to reduce EC2 scaling events.
  • +
+

Additional Resources:

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/reliability/docs/dataplane/index.html b/reliability/docs/dataplane/index.html new file mode 100644 index 000000000..2b49ebf6b --- /dev/null +++ b/reliability/docs/dataplane/index.html @@ -0,0 +1,2705 @@ + + + + + + + + + + + + + + + + + + + + + + + Data Plane - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

EKS Data Plane

+

To operate high-available and resilient applications, you need a highly-available and resilient data plane. An elastic data plane ensures that Kubernetes can scale and heal your applications automatically. A resilient data plane consists of two or more worker nodes, can grow and shrink with the workload, and automatically recover from failures.

+

You have two choices for worker nodes with EKS: EC2 instances and Fargate. If you choose EC2 instances, you can manage the worker nodes yourself or use EKS managed node groups. You can have a cluster with a mix of managed, self-managed worker nodes, and Fargate.

+

EKS on Fargate offers the easiest path to a resilient data plane. Fargate runs each Pod in an isolated compute environment. Each Pod running on Fargate gets its own worker node. Fargate automatically scales the data plane as Kubernetes scales pods. You can scale both the data plane and your workload by using the horizontal pod autoscaler.

+

The preferred way to scale EC2 worker nodes is by using Kubernetes Cluster Autoscaler, EC2 Auto Scaling groups or community projects like Atlassian’s Escalator.

+

Recommendations

+

Use EC2 Auto Scaling Groups to create worker nodes

+

It is a best practice to create worker nodes using EC2 Auto Scaling groups instead of creating individual EC2 instances and joining them to the cluster. Auto Scaling Groups will automatically replace any terminated or failed nodes ensuring that the cluster always has the capacity to run your workload.

+

Use Kubernetes Cluster Autoscaler to scale nodes

+

Cluster Autoscaler adjusts the size of the data plane when there are pods that cannot be run because the cluster has insufficient resources, and adding another worker node would help. Although Cluster Autoscaler is a reactive process, it waits until pods are in Pending state due to insufficient capacity in the cluster. When such an event occurs, it adds EC2 instances to the cluster. Whenever the cluster runs out of capacity, new replicas - or new pods - will be unavailable (in Pending state) until worker nodes are added. This delay may impact your applications' reliability if the data plane cannot scale fast enough to meet the demands of the workload. If a worker node is consistently underutilized and all of its pods can be scheduled on other worker nodes, Cluster Autoscaler terminates it.

+

Configure over-provisioning with Cluster Autoscaler

+

Cluster Autoscaler triggers a scale-up of the data-plane when Pods in the cluster are already Pending. Hence, there may be a delay between the time your application needs more replicas, and when it, in fact, gets more replicas. An option to account for this possible delay is through adding more than required replicas, inflating the number of replicas for the application.

+

Another pattern that Cluster Autoscaler recommends uses pause Pods and the Priority Preemption feature. The pause Pod runs a pause container, which as the name suggests, does nothing but acts as a placeholder for compute capacity that can be used by other Pods in your cluster. Because it runs with a very low assigned priority, the pause Pod gets evicted from the node when another Pod needs to be created, and the cluster doesn’t have available capacity. The Kubernetes Scheduler notices the eviction of the pause Pod and tries to reschedule it. But since the cluster is running at capacity, the pause Pod remains Pending, to which the Cluster Autoscaler reacts by adding nodes.

+

Using Cluster Autoscaler with multiple Auto Scaling Groups

+

Run the Cluster Autoscaler with the --node-group-auto-discovery flag enabled. Doing so will allow the Cluster Autoscaler to find all autoscaling groups that include a particular defined tag and prevents the need to define and maintain each autoscaling group in the manifest.

+

Using Cluster Autoscaler with local storage

+

By default, the Cluster Autoscaler does not scale-down nodes that have pods deployed with local storage attached. Set the --skip-nodes-with-local-storage flag to false to allow Cluster Autoscaler to scale-down these nodes.

+

Spread worker nodes and workload across multiple AZs

+

You can protect your workloads from failures in an individual AZ by running worker nodes and pods in multiple AZs. You can control the AZ the worker nodes are created in using the subnets you create the nodes in.

+

If you are using Kubernetes 1.18+, the recommended method for spreading pods across AZs is to use Topology Spread Constraints for Pods.

+

The deployment below spreads pods across AZs if possible, letting those pods run anyway if not:

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: web-server
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: web-server
+  template:
+    metadata:
+      labels:
+        app: web-server
+    spec:
+      topologySpreadConstraints:
+        - maxSkew: 1
+          whenUnsatisfiable: ScheduleAnyway
+          topologyKey: topology.kubernetes.io/zone
+          labelSelector:
+            matchLabels:
+              app: web-server
+      containers:
+      - name: web-app
+        image: nginx
+        resources:
+          requests:
+            cpu: 1
+
+
+

Note

+

kube-scheduler is only aware of topology domains via nodes that exist with those labels. If the above deployment is deployed to a cluster with nodes only in a single zone, all of the pods will schedule on those nodes as kube-scheduler isn't aware of the other zones. For this topology spread to work as expected with the scheduler, nodes must already exist in all zones. This issue will be resolved in Kubernetes 1.24 with the addition of the MinDomainsInPodToplogySpread feature gate which allows specifying a minDomains property to inform the scheduler of the number of eligible domains.

+
+
+

Warning

+

Setting whenUnsatisfiable to DoNotSchedule will cause pods to be unschedulable if the topology spread constraint can't be fulfilled. It should only be set if its preferable for pods to not run instead of violating the topology spread constraint.

+
+

On older versions of Kubernetes, you can use pod anti-affinity rules to schedule pods across multiple AZs. The manifest below informs Kubernetes scheduler to prefer scheduling pods in distinct AZs.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: web-server
+  labels:
+    app: web-server
+spec:
+  replicas: 4
+  selector:
+    matchLabels:
+      app: web-server
+  template:
+    metadata:
+      labels:
+        app: web-server
+    spec:
+      affinity:
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - podAffinityTerm:
+              labelSelector:
+                matchExpressions:
+                - key: app
+                  operator: In
+                  values:
+                  - web-server
+              topologyKey: failure-domain.beta.kubernetes.io/zone
+            weight: 100
+      containers:
+      - name: web-app
+        image: nginx
+
+
+

Warning

+

Do not require that pods be scheduled across distinct AZs otherwise, the number of pods in a deployment will never exceed the number of AZs.

+
+

Ensure capacity in each AZ when using EBS volumes

+

If you use Amazon EBS to provide Persistent Volumes, then you need to ensure that the pods and associated EBS volume are located in the same AZ. At the time of writing, EBS volumes are only available within a single AZ. A Pod cannot access EBS-backed persistent volumes located in a different AZ. Kubernetes scheduler knows which AZ a worker node is located in. Kubernetes will always schedule a Pod that requires an EBS volume in the same AZ as the volume. However, if there are no worker nodes available in the AZ where the volume is located, then the Pod cannot be scheduled.

+

Create Auto Scaling Group for each AZ with enough capacity to ensure that the cluster always has capacity to schedule pods in the same AZ as the EBS volumes they need. In addition, you should enable the --balance-similar-node-groups feature in Cluster Autoscaler.

+

If you are running an application that uses EBS volume but has no requirements to be highly available, then you can restrict the deployment of the application to a single AZ. In EKS, worker nodes are automatically added failure-domain.beta.kubernetes.io/zone label, which contains the name of the AZ. You can see the labels attached to your nodes by running kubectl get nodes --show-labels. More information about built-in node labels is available here. You can use node selectors to schedule a pod in a particular AZ.

+

In the example below, the pod will only be scheduled in us-west-2c AZ:

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: single-az-pod
+spec:
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: failure-domain.beta.kubernetes.io/zone
+            operator: In
+            values:
+            - us-west-2c
+  containers:
+  - name: single-az-container
+    image: kubernetes/pause
+
+

Persistent volumes (backed by EBS) are also automatically labeled with the name of AZ; you can see which AZ your persistent volume belongs to by running kubectl get pv -L topology.ebs.csi.aws.com/zone. When a pod is created and claims a volume, Kubernetes will schedule the Pod on a node in the same AZ as the volume.

+

Consider this scenario; you have an EKS cluster with one node group. This node group has three worker nodes spread across three AZs. You have an application that uses an EBS-backed Persistent Volume. When you create this application and the corresponding volume, its Pod gets created in the first of the three AZs. Then, the worker node that runs this Pod becomes unhealthy and subsequently unavailable for use. Cluster Autoscaler will replace the unhealthy node with a new worker node; however, because the autoscaling group spans across three AZs, the new worker node may get launched in the second or the third AZ, but not in the first AZ as the situation demands. As the AZ-constrained EBS volume only exists in the first AZ, but there are no worker nodes available in that AZ, the Pod cannot be scheduled. Therefore, you should create one node group in each AZ, so there is always enough capacity available to run pods that cannot be scheduled in other AZs.

+

Alternatively, EFS can simplify cluster autoscaling when running applications that need persistent storage. Clients can access EFS file systems concurrently from all the AZs in the region. Even if a Pod using EFS-backed Persistent Volume gets terminated and gets scheduled in different AZ, it will be able to mount the volume.

+

Run node-problem-detector

+

Failures in worker nodes can impact the availability of your applications. node-problem-detector is a Kubernetes add-on that you can install in your cluster to detect worker node issues. You can use a npd’s remedy system to drain and terminate the node automatically.

+

Reserving resources for system and Kubernetes daemons

+

You can improve worker nodes' stability by reserving compute capacity for the operating system and Kubernetes daemons. Pods - especially ones without limits declared - can saturate system resources putting nodes in a situation where operating system processes and Kubernetes daemons (kubelet, container runtime, etc.) compete with pods for system resources. You can use kubelet flags --system-reserved and --kube-reserved to reserve resources for system process (udev, sshd, etc.) and Kubernetes daemons respectively.

+

If you use the EKS-optimized Linux AMI, the CPU, memory, and storage are reserved for the system and Kubernetes daemons by default. When worker nodes based on this AMI launch, EC2 user-data is configured to trigger the bootstrap.sh script. This script calculates CPU and memory reservations based on the number of CPU cores and total memory available on the EC2 instance. The calculated values are written to the KubeletConfiguration file located at /etc/kubernetes/kubelet/kubelet-config.json.

+

You may need to increase the system resource reservation if you run custom daemons on the node and the amount of CPU and memory reserved by default is insufficient.

+

eksctl offers the easiest way to customize resource reservation for system and Kubernetes daemons.

+

Implement QoS

+

For critical applications, consider defining requests=limits for the container in the Pod. This will ensure that the container will not be killed if another Pod requests resources.

+

It is a best practice to implement CPU and memory limits for all containers as it prevents a container inadvertently consuming system resources impacting the availability of other co-located processes.

+

Configure and Size Resource Requests/Limits for all Workloads

+

Some general guidance can be applied to sizing resource requests and limits for workloads:

+
    +
  • +

    Do not specify resource limits on CPU. In the absence of limits, the request acts as a weight on how much relative CPU time containers get. This allows your workloads to use the full CPU without an artificial limit or starvation.

    +
  • +
  • +

    For non-CPU resources, configuring requests=limits provides the most predictable behavior. If requests!=limits, the container also has its QOS reduced from Guaranteed to Burstable making it more likely to be evicted in the event of node pressure.

    +
  • +
  • +

    For non-CPU resources, do not specify a limit that is much larger than the request. The larger limits are configured relative to requests, the more likely nodes will be overcommitted leading to high chances of workload interruption.

    +
  • +
  • +

    Correctly sized requests are particularly important when using a node auto-scaling solution like Karpenter or Cluster AutoScaler. These tools look at your workload requests to determine the number and size of nodes to be provisioned. If your requests are too small with larger limits, you may find your workloads evicted or OOM killed if they have been tightly packed on a node.

    +
  • +
+

Determining resource requests can be difficult, but tools like the Vertical Pod Autoscaler can help you 'right-size' the requests by observing container resource usage at runtime. Other tools that may be useful for determining request sizes include:

+ +

Configure resource quotas for namespaces

+

Namespaces are intended for use in environments with many users spread across multiple teams, or projects. They provide a scope for names and are a way to divide cluster resources between multiple teams, projects, workloads. You can limit the aggregate resource consumption in a namespace. The ResourceQuota object can limit the quantity of objects that can be created in a namespace by type, as well as the total amount of compute resources that may be consumed by resources in that project. You can limit the total sum of storage and/or compute (CPU and memory) resources that can be requested in a given namespace.

+
+

If resource quota is enabled for a namespace for compute resources like CPU and memory, users must specify requests or limits for each container in that namespace.

+
+

Consider configuring quotas for each namespace. Consider using LimitRanges to automatically apply preconfigured limits to containers within a namespaces.

+

Limit container resource usage within a namespace

+

Resource Quotas help limit the amount of resources a namespace can use. The LimitRange object can help you implement minimum and maximum resources a container can request. Using LimitRange you can set a default request and limits for containers, which is helpful if setting compute resource limits is not a standard practice in your organization. As the name suggests, LimitRange can enforce minimum and maximum compute resources usage per Pod or Container in a namespace. As well as, enforce minimum and maximum storage request per PersistentVolumeClaim in a namespace.

+

Consider using LimitRange in conjunction with ResourceQuota to enforce limits at a container as well as namespace level. Setting these limits will ensure that a container or a namespace does not impinge on resources used by other tenants in the cluster.

+

CoreDNS

+

CoreDNS fulfills name resolution and service discovery functions in Kubernetes. It is installed by default on EKS clusters. For interoperability, the Kubernetes Service for CoreDNS is still named kube-dns. CoreDNS Pods run as part of a Deployment in kube-system namespace, and in EKS, by default, it runs two replicas with declared requests and limits. DNS queries are sent to the kube-dns Service that runs in the kube-system Namespace.

+

Recommendations

+

Monitor CoreDNS metrics

+

CoreDNS has built in support for Prometheus. You should especially consider monitoring CoreDNS latency (coredns_dns_request_duration_seconds_sum, before 1.7.0 version the metric was called core_dns_response_rcode_count_total), errors (coredns_dns_responses_total, NXDOMAIN, SERVFAIL, FormErr) and CoreDNS Pod’s memory consumption.

+

For troubleshooting purposes, you can use kubectl to view CoreDNS logs:

+
for p in $(kubectl get pods -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[*].metadata.name}'); do kubectl logs $p -n kube-system; done
+
+

Use NodeLocal DNSCache

+

You can improve the Cluster DNS performance by running NodeLocal DNSCache. This feature runs a DNS caching agent on cluster nodes as a DaemonSet. All the pods use the DNS caching agent running on the node for name resolution instead of using kube-dns Service.

+

Configure cluster-proportional-scaler for CoreDNS

+

Another method of improving Cluster DNS performance is by automatically horizontally scaling the CoreDNS Deployment based on the number of nodes and CPU cores in the cluster. Horizontal cluster-proportional-autoscaler is a container that resizes the number of replicas of a Deployment based on the size of the schedulable data-plane.

+

Nodes and the aggregate of CPU cores in the nodes are the two metrics with which you can scale CoreDNS. You can use both metrics simultaneously. If you use larger nodes, CoreDNS scaling is based on the number of CPU cores. Whereas, if you use smaller nodes, the number of CoreDNS replicas depends on the CPU cores in your data-plane. Proportional autoscaler configuration looks like this:

+
linear: '{"coresPerReplica":256,"min":1,"nodesPerReplica":16}'
+
+

Choosing an AMI with Node Group

+

EKS provides optimized EC2 AMIs that are used by customers to create both self-managed and managed nodegroups. These AMIs are published in every region for every supported Kubernetes version. EKS marks these AMIs as deprecated when any CVEs or bugs are discovered. Hence, the recommendation is not to consume deprecated AMIs while choosing an AMI for the node group.

+

Deprecated AMIs can be filtered using Ec2 describe-images api using below command:

+
aws ec2 describe-images --image-id ami-0d551c4f633e7679c --no-include-deprecated
+
+

You can also recognize a deprecated AMI by verifying if the describe-image output contains a DeprecationTime as a field. For ex:

+
aws ec2 describe-images --image-id ami-xxx --no-include-deprecated
+{
+    "Images": [
+        {
+            "Architecture": "x86_64",
+            "CreationDate": "2022-07-13T15:54:06.000Z",
+            "ImageId": "ami-xxx",
+            "ImageLocation": "123456789012/eks_xxx",
+            "ImageType": "machine",
+            "Public": false,
+            "OwnerId": "123456789012",
+            "PlatformDetails": "Linux/UNIX",
+            "UsageOperation": "RunInstances",
+            "State": "available",
+            "BlockDeviceMappings": [
+                {
+                    "DeviceName": "/dev/xvda",
+                    "Ebs": {
+                        "DeleteOnTermination": true,
+                        "SnapshotId": "snap-0993a2fc4bbf4f7f4",
+                        "VolumeSize": 20,
+                        "VolumeType": "gp2",
+                        "Encrypted": false
+                    }
+                }
+            ],
+            "Description": "EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.19.15, docker: 20.10.13-2.amzn2, containerd: 1.4.13-3.amzn2)",
+            "EnaSupport": true,
+            "Hypervisor": "xen",
+            "Name": "aws_eks_optimized_xxx",
+            "RootDeviceName": "/dev/xvda",
+            "RootDeviceType": "ebs",
+            "SriovNetSupport": "simple",
+            "VirtualizationType": "hvm",
+            "DeprecationTime": "2023-02-09T19:41:00.000Z"
+        }
+    ]
+}
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/reliability/docs/images/SRM-Fargate.jpeg b/reliability/docs/images/SRM-Fargate.jpeg new file mode 100644 index 000000000..8bcc43a00 Binary files /dev/null and b/reliability/docs/images/SRM-Fargate.jpeg differ diff --git a/reliability/docs/images/SRM-MNG.jpeg b/reliability/docs/images/SRM-MNG.jpeg new file mode 100644 index 000000000..e447b29fb Binary files /dev/null and b/reliability/docs/images/SRM-MNG.jpeg differ diff --git a/reliability/docs/images/eks-data-plane-connectivity.jpeg b/reliability/docs/images/eks-data-plane-connectivity.jpeg new file mode 100644 index 000000000..253d6294e Binary files /dev/null and b/reliability/docs/images/eks-data-plane-connectivity.jpeg differ diff --git a/reliability/docs/images/pod-topology-spread-constraints.jpg b/reliability/docs/images/pod-topology-spread-constraints.jpg new file mode 100644 index 000000000..2b67ea6b8 Binary files /dev/null and b/reliability/docs/images/pod-topology-spread-constraints.jpg differ diff --git a/reliability/docs/index.html b/reliability/docs/index.html new file mode 100644 index 000000000..891c901fd --- /dev/null +++ b/reliability/docs/index.html @@ -0,0 +1,2181 @@ + + + + + + + + + + + + + + + + + + + + + + + Home - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Amazon EKS Best Practices Guide for Reliability

+

This section provides guidance about making workloads running on EKS resilient and highly-available

+

How to use this guide

+

This guide is meant for developers and architects who want to develop and operate highly-available and fault-tolerant services in EKS. The guide is organized into different topic areas for easier consumption. Each topic starts with a brief overview, followed by a list of recommendations and best practices for the reliability of your EKS clusters.

+

Introduction

+

The reliability best practices for EKS have been grouped under the following topics:

+
    +
  • Applications
  • +
  • Control Plane
  • +
  • Data Plane
  • +
+
+

What makes a system reliable? If a system can function consistently and meet demands in spite of changes in its environment over a period of time, it can be called reliable. To achieve this, the system has to detect failures, automatically heal itself, and have the ability to scale based on demand.

+

Customers can use Kubernetes as a foundation to operate mission-critical applications and services reliably. But aside from incorporating container-based application design principles, running workloads reliably also requires a reliable infrastructure. In Kubernetes, infrastructure comprises the control plane and data plane.

+

EKS provides a production-grade Kubernetes control plane that is designed to be highly-available and fault-tolerant.

+

In EKS, AWS is responsible for the reliability of the Kubernetes control plane. EKS runs Kubernetes control plane across three availability zones in an AWS Region. It automatically manages the availability and scalability of the Kubernetes API servers and the etcd cluster.

+

The responsibility for the data plane’s reliability is shared between you, the customer, and AWS. EKS offers three worker node options for deploying the Kubernetes data plane. Fargate, which is the most managed option, handles provisioning and scaling of the data plane. The second option, managed nodes groups, handles provisioning, and updates of the data plane. And finally, self-managed nodes is the least managed option for the data plane. The more AWS-managed data plane you use, the less responsibility you have.

+

Managed node groups automate the provisioning and lifecycle management of EC2 nodes. You can use the EKS API (using EKS console, AWS API, AWS CLI, CloudFormation, Terraform, or eksctl), to create, scale, and upgrade managed nodes. Managed nodes run EKS-optimized Amazon Linux 2 EC2 instances in your account, and you can install custom software packages by enabling SSH access. When you provision managed nodes, they run as part of an EKS-managed Auto Scaling Group that can span multiple Availability Zones; you control this through the subnets you provide when creating managed nodes. EKS also automatically tags managed nodes so they can be used with Cluster Autoscaler.

+
+

Amazon EKS follows the shared responsibility model for CVEs and security patches on managed node groups. Because managed nodes run the Amazon EKS-optimized AMIs, Amazon EKS is responsible for building patched versions of these AMIs when bug fixes. However, you are responsible for deploying these patched AMI versions to your managed node groups.

+
+

EKS also manages updating the nodes although you have to initiate the update process. The process of updating managed node is explained in the EKS documentation.

+

If you run self-managed nodes, you can use Amazon EKS-optimized Linux AMI to create worker nodes. You are responsible for patching and upgrading the AMI and the nodes. It is a best practice to use eksctl, CloudFormation, or infrastructure as code tools to provision self-managed nodes because this will make it easy for you to upgrade self-managed nodes. Consider migrating to new nodes when updating worker nodes because the migration process taints the old node group as NoSchedule and drains the nodes after a new stack is ready to accept the existing pod workload. However, you can also perform an in-place upgrade of self-managed nodes.

+

Shared Responsibility Model - Fargate

+

Shared Responsibility Model - MNG

+

This guide includes a set of recommendations that you can use to improve the reliability of your EKS data plane, Kubernetes core components, and your applications.

+

Feedback

+

This guide is being released on GitHub to collect direct feedback and suggestions from the broader EKS/Kubernetes community. If you have a best practice that you feel we ought to include in the guide, please file an issue or submit a PR in the GitHub repository. We intend to update the guide periodically as new features are added to the service or when a new best practice evolves.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/cluster-services/index.html b/scalability/docs/cluster-services/index.html new file mode 100644 index 000000000..9a74ae84b --- /dev/null +++ b/scalability/docs/cluster-services/index.html @@ -0,0 +1,2294 @@ + + + + + + + + + + + + + + + + + + + + + + + Cluster Services - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Cluster Services

+

Cluster services run inside an EKS cluster, but they are not user workloads. If you have a Linux server you often need to run services like NTP, syslog, and a container runtime to support your workloads. Cluster services are similar, supporting services that help you automate and operate your cluster. In Kubernetes these are usually run in the kube-system namespace and some are run as DaemonSets.

+

Cluster services are expected to have a high up-time and are often critical during outages and for troubleshooting. If a core cluster service is not available you may lose access to data that can help recover or prevent an outage (e.g. high disk utilization). They should run on dedicated compute instances such as a separate node group or AWS Fargate. This will ensure that the cluster services are not impacted on shared instances by workloads that may be scaling up or using more resources.

+

Scale CoreDNS

+

Scaling CoreDNS has two primary mechanisms. Reducing the number of calls to the CoreDNS service and increasing the number of replicas.

+

Reduce external queries by lowering ndots

+

The ndots setting specifies how many periods (a.k.a. "dots") in a domain name are considered enough to avoid querying DNS. If your application has an ndots setting of 5 (default) and you request resources from an external domain such as api.example.com (2 dots) then CoreDNS will be queried for each search domain defined in /etc/resolv.conf for a more specific domain. By default the following domains will be searched before making an external request.

+
api.example.<namespace>.svc.cluster.local
+api.example.svc.cluster.local
+api.example.cluster.local
+api.example.<region>.compute.internal
+
+

The namespace and region values will be replaced with your workloads namespace and your compute region. You may have additional search domains based on your cluster settings.

+

You can reduce the number of requests to CoreDNS by lowering the ndots option of your workload or fully qualifying your domain requests by including a trailing . (e.g. api.example.com. ). If your workload connects to external services via DNS we recommend setting ndots to 2 so workloads do not make unnecessary, cluster DNS queries inside the cluster. You can set a different DNS server and search domain if the workload doesn’t require access to services inside the cluster.

+
spec:
+  dnsPolicy: "None"
+  dnsConfig:
+    options:
+      - name: ndots
+        value: "2"
+      - name: edns0
+
+

If you lower ndots to a value that is too low or the domains you are connecting to do not include enough specificity (including trailing .) then it is possible DNS lookups will fail. Make sure you test how this setting will impact your workloads.

+

Scale CoreDNS Horizontally

+

CoreDNS instances can scale by adding additional replicas to the deployment. It's recommended you use NodeLocal DNS or the cluster proportional autoscaler to scale CoreDNS.

+

NodeLocal DNS will require run one instance per node—as a DaemonSet—which requires more compute resources in the cluster, but it will avoid failed DNS requests and decrease the response time for DNS queries in the cluster. The cluster proportional autoscaler will scale CoreDNS based on the number of nodes or cores in the cluster. This isn’t a direct correlation to request queries, but can be useful depending on your workloads and cluster size. The default proportional scale is to add an additional replica for every 256 cores or 16 nodes in the cluster—whichever happens first.

+

Scale Kubernetes Metrics Server Vertically

+

The Kubernetes Metrics Server supports horizontal and vertical scaling. By horizontally scaling the Metrics Server it will be highly available, but it will not scale horizontally to handle more cluster metrics. You will need to vertically scale the Metrics Server based on their recommendations as nodes and collected metrics are added to the cluster.

+

The Metrics Server keeps the data it collects, aggregates, and serves in memory. As a cluster grows, the amount of data the Metrics Server stores increases. In large clusters the Metrics Server will require more compute resources than the memory and CPU reservation specified in the default installation. You can use the Vertical Pod Autoscaler (VPA) or Addon Resizer to scale the Metrics Server. The Addon Resizer scales vertically in proportion to worker nodes and VPA scales based on CPU and memory usage.

+

CoreDNS lameduck duration

+

Pods use the kube-dns Service for name resolution. Kubernetes uses destination NAT (DNAT) to redirect kube-dns traffic from nodes to CoreDNS backend pods. As you scale the CoreDNS Deployment, kube-proxy updates iptables rules and chains on nodes to redirect DNS traffic to CoreDNS pods. Propagating new endpoints when you scale up and deleting rules when you scale down CoreDNS can take between 1 to 10 seconds depending on the size of the cluster.

+

This propagation delay can cause DNS lookup failures when a CoreDNS pod gets terminated yet the node’s iptables rules haven’t been updated. In this scenario, the node may continue to send DNS queries to a terminated CoreDNS Pod.

+

You can reduce DNS lookup failures by setting a lameduck duration in your CoreDNS pods. While in lameduck mode, CoreDNS will continue to respond to in-flight requests. Setting a lameduck duration will delay the CoreDNS shutdown process, allowing nodes the time they need to update their iptables rules and chains.

+

We recommend setting CoreDNS lameduck duration to 30 seconds.

+

CoreDNS readiness probe

+

We recommend using /ready instead of /health for CoreDNS's readiness probe.

+

In alignment with the earlier recommendation to set the lameduck duration to 30 seconds, providing ample time for the node's iptables rules to be updated before pod termination, employing /ready instead of /health for the CoreDNS readiness probe ensures that the CoreDNS pod is fully prepared at startup to promptly respond to DNS requests.

+
readinessProbe:
+  httpGet:
+    path: /ready
+    port: 8181
+    scheme: HTTP
+
+

For more information about the CoreDNS Ready plugin please refer to https://coredns.io/plugins/ready/

+

Logging and monitoring agents

+

Logging and monitoring agents can add significant load to your cluster control plane because the agents query the API server to enrich logs and metrics with workload metadata. The agent on a node only has access to the local node resources to see things like container and process name. Querying the API server it can add more details such as Kubernetes deployment name and labels. This can be extremely helpful for troubleshooting but detrimental to scaling.

+

Because there are so many different options for logging and monitoring we cannot show examples for every provider. With fluentbit we recommend enabling Use_Kubelet to fetch metadata from the local kubelet instead of the Kubernetes API Server and set Kube_Meta_Cache_TTL to a number that reduces repeated calls when data can be cached (e.g. 60).

+

Scaling monitoring and logging has two general options:

+
    +
  • Disable integrations
  • +
  • Sampling and filtering
  • +
+

Disabling integrations is often not an option because you lose log metadata. This eliminates the API scaling problem, but it will introduce other issues by not having the required metadata when needed.

+

Sampling and filtering reduces the number of metrics and logs that are collected. This will lower the amount of requests to the Kubernetes API, and it will reduce the amount of storage needed for the metrics and logs that are collected. Reducing the storage costs will lower the cost for the overall system.

+

The ability to configure sampling depends on the agent software and can be implemented at different points of ingestion. It’s important to add sampling as close to the agent as possible because that is likely where the API server calls happen. Contact your provider to find out more about sampling support.

+

If you are using CloudWatch and CloudWatch Logs you can add agent filtering using patterns described in the documentation.

+

To avoid losing logs and metrics you should send your data to a system that can buffer data in case of an outage on the receiving endpoint. With fluentbit you can use Amazon Kinesis Data Firehose to temporarily keep data which can reduce the chance of overloading your final data storage location.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/control-plane/index.html b/scalability/docs/control-plane/index.html new file mode 100644 index 000000000..71275bafa --- /dev/null +++ b/scalability/docs/control-plane/index.html @@ -0,0 +1,2684 @@ + + + + + + + + + + + + + + + + + + + + + + + Control Plane - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Kubernetes Control Plane

+

The Kubernetes control plane consists of the Kubernetes API Server, Kubernetes Controller Manager, Scheduler and other components that are required for Kubernetes to function. Scalability limits of these components are different depending on what you’re running in the cluster, but the areas with the biggest impact to scaling include the Kubernetes version, utilization, and individual Node scaling.

+

Use EKS 1.24 or above

+

EKS 1.24 introduced a number of changes and switches the container runtime to containerd instead of docker. Containerd helps clusters scale by increasing individual node performance by limiting container runtime features to closely align with Kubernetes’ needs. Containerd is available in every supported version of EKS and if you would like to switch to containerd in versions prior to 1.24 please use the --container-runtime bootstrap flag.

+

Limit workload and node bursting

+
+

Attention

+

To avoid reaching API limits on the control plane you should limit scaling spikes that increase cluster size by double digit percentages at a time (e.g. 1000 nodes to 1100 nodes or 4000 to 4500 pods at once).

+
+

The EKS control plane will automatically scale as your cluster grows, but there are limits on how fast it will scale. When you first create an EKS cluster the Control Plane will not immediately be able to scale to hundreds of nodes or thousands of pods. To read more about how EKS has made scaling improvements see this blog post.

+

Scaling large applications requires infrastructure to adapt to become fully ready (e.g. warming load balancers). To control the speed of scaling make sure you are scaling based on the right metrics for your application. CPU and memory scaling may not accurately predict your application constraints and using custom metrics (e.g. requests per second) in Kubernetes Horizontal Pod Autoscaler (HPA) may be a better scaling option.

+

To use a custom metric see the examples in the Kubernetes documentation. If you have more advanced scaling needs or need to scale based on external sources (e.g. AWS SQS queue) then use KEDA for event based workload scaling.

+

Scale nodes and pods down safely

+

Replace long running instances

+

Replacing nodes regularly keeps your cluster healthy by avoiding configuration drift and issues that only happen after extended uptime (e.g. slow memory leaks). Automated replacement will give you good process and practices for node upgrades and security patching. If every node in your cluster is replaced regularly then there is less toil required to maintain separate processes for ongoing maintenance.

+

Use Karpenter’s time to live (TTL) settings to replace instances after they’ve been running for a specified amount of time. Self managed node groups can use the max-instance-lifetime setting to cycle nodes automatically. Managed node groups do not currently have this feature but you can track the request here on GitHub.

+

Remove underutilized nodes

+

You can remove nodes when they have no running workloads using the scale down threshold in the Kubernetes Cluster Autoscaler with the --scale-down-utilization-threshold or in Karpenter you can use the ttlSecondsAfterEmpty provisioner setting.

+

Use pod disruption budgets and safe node shutdown

+

Removing pods and nodes from a Kubernetes cluster requires controllers to make updates to multiple resources (e.g. EndpointSlices). Doing this frequently or too quickly can cause API server throttling and application outages as changes propogate to controllers. Pod Disruption Budgets are a best practice to slow down churn to protect workload availability as nodes are removed or rescheduled in a cluster.

+

Use Client-Side Cache when running Kubectl

+

Using the kubectl command inefficiently can add additional load to the Kubernetes API Server. You should avoid running scripts or automation that uses kubectl repeatedly (e.g. in a for loop) or running commands without a local cache.

+

kubectl has a client-side cache that caches discovery information from the cluster to reduce the amount of API calls required. The cache is enabled by default and is refreshed every 10 minutes.

+

If you run kubectl from a container or without a client-side cache you may run into API throttling issues. It is recommended to retain your cluster cache by mounting the --cache-dir to avoid making uncessesary API calls.

+

Disable kubectl Compression

+

Disabling kubectl compression in your kubeconfig file can reduce API and client CPU usage. By default the server will compress data sent to the client to optimize network bandwidth. This adds CPU load on the client and server for every request and disabling compression can reduce the overhead and latency if you have adequate bandwidth. To disable compression you can use the --disable-compression=true flag or set disable-compression: true in your kubeconfig file.

+
apiVersion: v1
+clusters:
+- cluster:
+    server: serverURL
+    disable-compression: true
+  name: cluster
+
+

Shard Cluster Autoscaler

+

The Kubernetes Cluster Autoscaler has been tested to scale up to 1000 nodes. On a large cluster with more than 1000 nodes, it is recommended to run multiple instances of the Cluster Autoscaler in shard mode. Each Cluster Autoscaler instance is configured to scale a set of node groups. The following example shows 2 cluster autoscaling configurations that are configured to each scale 4 node groups.

+

ClusterAutoscaler-1

+
autoscalingGroups:
+- name: eks-core-node-grp-20220823190924690000000011-80c1660e-030d-476d-cb0d-d04d585a8fcb
+  maxSize: 50
+  minSize: 2
+- name: eks-data_m1-20220824130553925600000011-5ec167fa-ca93-8ca4-53a5-003e1ed8d306
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m2-20220824130733258600000015-aac167fb-8bf7-429d-d032-e195af4e25f5
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m3-20220824130553914900000003-18c167fa-ca7f-23c9-0fea-f9edefbda002
+  maxSize: 450
+  minSize: 2
+
+

ClusterAutoscaler-2

+
autoscalingGroups:
+- name: eks-data_m4-2022082413055392550000000f-5ec167fa-ca86-6b83-ae9d-1e07ade3e7c4
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m5-20220824130744542100000017-02c167fb-a1f7-3d9e-a583-43b4975c050c
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m6-2022082413055392430000000d-9cc167fa-ca94-132a-04ad-e43166cef41f
+  maxSize: 450
+  minSize: 2
+- name: eks-data_m7-20220824130553921000000009-96c167fa-ca91-d767-0427-91c879ddf5af
+  maxSize: 450
+  minSize: 2
+
+

API Priority and Fairness

+

+

Overview

+ + +

To protect itself from being overloaded during periods of increased requests, the API Server limits the number of inflight requests it can have outstanding at a given time. Once this limit is exceeded, the API Server will start rejecting requests and return a 429 HTTP response code for "Too Many Requests" back to clients. The server dropping requests and having clients try again later is preferable to having no server-side limits on the number of requests and overloading the control plane, which could result in degraded performance or unavailability.

+

The mechanism used by Kubernetes to configure how these inflights requests are divided among different request types is called API Priority and Fairness. The API Server configures the total number of inflight requests it can accept by summing together the values specified by the --max-requests-inflight and --max-mutating-requests-inflight flags. EKS uses the default values of 400 and 200 requests for these flags, allowing a total of 600 requests to be dispatched at a given time. However, as it scales the control-plane to larger sizes in response to increased utilization and workload churn, it correspondingly increases the inflight request quota all the way till 2000 (subject to change). APF specifies how these inflight request quota is further sub-divided among different request types. Note that EKS control planes are highly available with at least 2 API Servers registered to each cluster. This means the total number of inflight requests your cluster can handle is twice (or higher if horizontally scaled out further) the inflight quota set per kube-apiserver. This amounts to several thousands of requests/second on the largest EKS clusters.

+

Two kinds of Kubernetes objects, called PriorityLevelConfigurations and FlowSchemas, configure how the total number of requests is divided between different request types. These objects are maintained by the API Server automatically and EKS uses the default configuration of these objects for the given Kubernetes minor version. PriorityLevelConfigurations represent a fraction of the total number of allowed requests. For example, the workload-high PriorityLevelConfiguration is allocated 98 out of the total of 600 requests. The sum of requests allocated to all PriorityLevelConfigurations will equal 600 (or slightly above 600 because the API Server will round up if a given level is granted a fraction of a request). To check the PriorityLevelConfigurations in your cluster and the number of requests allocated to each, you can run the following command. These are the defaults on EKS 1.24:

+
$ kubectl get --raw /metrics | grep apiserver_flowcontrol_request_concurrency_limit
+apiserver_flowcontrol_request_concurrency_limit{priority_level="catch-all"} 13
+apiserver_flowcontrol_request_concurrency_limit{priority_level="global-default"} 49
+apiserver_flowcontrol_request_concurrency_limit{priority_level="leader-election"} 25
+apiserver_flowcontrol_request_concurrency_limit{priority_level="node-high"} 98
+apiserver_flowcontrol_request_concurrency_limit{priority_level="system"} 74
+apiserver_flowcontrol_request_concurrency_limit{priority_level="workload-high"} 98
+apiserver_flowcontrol_request_concurrency_limit{priority_level="workload-low"} 245
+
+

The second type of object are FlowSchemas. API Server requests with a given set of properties are classified under the same FlowSchema. These properties include either the authenticated user or attributes of the request, such as the API group, namespace, or resource. A FlowSchema also specifies which PriorityLevelConfiguration this type of request should map to. The two objects together say, "I want this type of request to count towards this share of inflight requests." When a request hits the API Server, it will check each of its FlowSchemas until it finds one that matches all the required properties. If multiple FlowSchemas match a request, the API Server will choose the FlowSchema with the smallest matching precedence which is specified as a property in the object.

+

The mapping of FlowSchemas to PriorityLevelConfigurations can be viewed using this command:

+
$ kubectl get flowschemas
+NAME                           PRIORITYLEVEL     MATCHINGPRECEDENCE   DISTINGUISHERMETHOD   AGE     MISSINGPL
+exempt                         exempt            1                    <none>                7h19m   False
+eks-exempt                     exempt            2                    <none>                7h19m   False
+probes                         exempt            2                    <none>                7h19m   False
+system-leader-election         leader-election   100                  ByUser                7h19m   False
+endpoint-controller            workload-high     150                  ByUser                7h19m   False
+workload-leader-election       leader-election   200                  ByUser                7h19m   False
+system-node-high               node-high         400                  ByUser                7h19m   False
+system-nodes                   system            500                  ByUser                7h19m   False
+kube-controller-manager        workload-high     800                  ByNamespace           7h19m   False
+kube-scheduler                 workload-high     800                  ByNamespace           7h19m   False
+kube-system-service-accounts   workload-high     900                  ByNamespace           7h19m   False
+eks-workload-high              workload-high     1000                 ByUser                7h14m   False
+service-accounts               workload-low      9000                 ByUser                7h19m   False
+global-default                 global-default    9900                 ByUser                7h19m   False
+catch-all                      catch-all         10000                ByUser                7h19m   False
+
+

PriorityLevelConfigurations can have a type of Queue, Reject, or Exempt. For types Queue and Reject, a limit is enforced on the maximum number of inflight requests for that priority level, however, the behavior differs when that limit is reached. For example, the workload-high PriorityLevelConfiguration uses type Queue and has 98 requests available for use by the controller-manager, endpoint-controller, scheduler,eks related controllers and from pods running in the kube-system namespace. Since type Queue is used, the API Server will attempt to keep requests in memory and hope that the number of inflight requests drops below 98 before these requests time out. If a given request times out in the queue or if too many requests are already queued, the API Server has no choice but to drop the request and return the client a 429. Note that queuing may prevent a request from receiving a 429, but it comes with the tradeoff of increased end-to-end latency on the request.

+

Now consider the catch-all FlowSchema that maps to the catch-all PriorityLevelConfiguration with type Reject. If clients reach the limit of 13 inflight requests, the API Server will not exercise queuing and will drop the requests instantly with a 429 response code. Finally, requests mapping to a PriorityLevelConfiguration with type Exempt will never receive a 429 and always be dispatched immediately. This is used for high-priority requests such as healthz requests or requests coming from the system:masters group.

+

Monitoring APF and Dropped Requests

+

To confirm if any requests are being dropped due to APF, the API Server metrics for apiserver_flowcontrol_rejected_requests_total can be monitored to check the impacted FlowSchemas and PriorityLevelConfigurations. For example, this metric shows that 100 requests from the service-accounts FlowSchema were dropped due to requests timing out in workload-low queues:

+
% kubectl get --raw /metrics | grep apiserver_flowcontrol_rejected_requests_total
+apiserver_flowcontrol_rejected_requests_total{flow_schema="service-accounts",priority_level="workload-low",reason="time-out"} 100
+
+

To check how close a given PriorityLevelConfiguration is to receiving 429s or experiencing increased latency due to queuing, you can compare the difference between the concurrency limit and the concurrency in use. In this example, we have a buffer of 100 requests.

+
% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_request_concurrency_limit.*workload-low'
+apiserver_flowcontrol_request_concurrency_limit{priority_level="workload-low"} 245
+
+% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_request_concurrency_in_use.*workload-low'
+apiserver_flowcontrol_request_concurrency_in_use{flow_schema="service-accounts",priority_level="workload-low"} 145
+
+

To check if a given PriorityLevelConfiguration is experiencing queuing but not necessarily dropped requests, the metric for apiserver_flowcontrol_current_inqueue_requests can be referenced:

+
% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_current_inqueue_requests.*workload-low'
+apiserver_flowcontrol_current_inqueue_requests{flow_schema="service-accounts",priority_level="workload-low"} 10
+
+

Other useful Prometheus metrics include:

+
    +
  • apiserver_flowcontrol_dispatched_requests_total
  • +
  • apiserver_flowcontrol_request_execution_seconds
  • +
  • apiserver_flowcontrol_request_wait_duration_seconds
  • +
+

See the upstream documentation for a complete list of APF metrics.

+

Preventing Dropped Requests

+

Prevent 429s by changing your workload

+

When APF is dropping requests due to a given PriorityLevelConfiguration exceeding its maximum number of allowed inflight requests, clients in the affected FlowSchemas can decrease the number of requests executing at a given time. This can be accomplished by reducing the total number of requests made over the period where 429s are occurring. Note that long-running requests such as expensive list calls are especially problematic because they count as an inflight request for the entire duration they are executing. Reducing the number of these expensive requests or optimizing the latency of these list calls (for example, by reducing the number of objects fetched per request or switching to using a watch request) can help reduce the total concurrency required by the given workload.

+

Prevent 429s by changing your APF settings

+
+

Warning

+

Only change default APF settings if you know what you are doing. Misconfigured APF settings can result in dropped API Server requests and significant workload disruptions.

+
+

One other approach for preventing dropped requests is changing the default FlowSchemas or PriorityLevelConfigurations installed on EKS clusters. EKS installs the upstream default settings for FlowSchemas and PriorityLevelConfigurations for the given Kubernetes minor version. The API Server will automatically reconcile these objects back to their defaults if modified unless the following annotation on the objects is set to false:

+
  metadata:
+    annotations:
+      apf.kubernetes.io/autoupdate-spec: "false"
+
+

At a high-level, APF settings can be modified to either:

+
    +
  • Allocate more inflight capacity to requests you care about.
  • +
  • Isolate non-essential or expensive requests that can starve capacity for other request types.
  • +
+

This can be accomplished by either changing the default FlowSchemas and PriorityLevelConfigurations or by creating new objects of these types. Operators can increase the values for assuredConcurrencyShares for the relevant PriorityLevelConfigurations objects to increase the fraction of inflight requests they are allocated. Additionally, the number of requests that can be queued at a given time can also be increased if the application can handle the additional latency caused by requests being queued before they are dispatched.

+

Alternatively, new FlowSchema and PriorityLevelConfigurations objects can be created that are specific to the customer's workload. Be aware that allocating more assuredConcurrencyShares to either existing PriorityLevelConfigurations or to new PriorityLevelConfigurations will cause the number of requests that can be handled by other buckets to be reduced as the overall limit will stay as 600 inflight per API Server.

+

When making changes to APF defaults, these metrics should be monitored on a non-production cluster to ensure changing the settings do not cause unintended 429s:

+
    +
  1. The metric for apiserver_flowcontrol_rejected_requests_total should be monitored for all FlowSchemas to ensure that no buckets start to drop requests.
  2. +
  3. The values for apiserver_flowcontrol_request_concurrency_limit and apiserver_flowcontrol_request_concurrency_in_use should be compared to ensure that the concurrency in use is not at risk for breaching the limit for that priority level.
  4. +
+

One common use-case for defining a new FlowSchema and PriorityLevelConfiguration is for isolation. Suppose we want to isolate long-running list event calls from pods to their own share of requests. This will prevent important requests from pods using the existing service-accounts FlowSchema from receiving 429s and being starved of request capacity. Recall that the total number of inflight requests is finite, however, this example shows APF settings can be modified to better divide request capacity for the given workload:

+

Example FlowSchema object to isolate list event requests:

+
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
+kind: FlowSchema
+metadata:
+  name: list-events-default-service-accounts
+spec:
+  distinguisherMethod:
+    type: ByUser
+  matchingPrecedence: 8000
+  priorityLevelConfiguration:
+    name: catch-all
+  rules:
+  - resourceRules:
+    - apiGroups:
+      - '*'
+      namespaces:
+      - default
+      resources:
+      - events
+      verbs:
+      - list
+    subjects:
+    - kind: ServiceAccount
+      serviceAccount:
+        name: default
+        namespace: default
+
+
    +
  • This FlowSchema captures all list event calls made by service accounts in the default namespace.
  • +
  • The matching precedence 8000 is lower than the value of 9000 used by the existing service-accounts FlowSchema so these list event calls will match list-events-default-service-accounts rather than service-accounts.
  • +
  • We're using the catch-all PriorityLevelConfiguration to isolate these requests. This bucket only allows 13 inflight requests to be used by these long-running list event calls. Pods will start to receive 429s as soon they try to issue more than 13 of these requests concurrently.
  • +
+

Retrieving resources in the API server

+

Getting information from the API server is an expected behavior for clusters of any size. As you scale the number of resources in the cluster the frequency of requests and volume of data can quickly become a bottleneck for the control plane and will lead to API latency and slowness. Depending on the severity of the latency it cause unexpected downtime if you are not careful.

+

Being aware of what you are requesting and how often are the first steps to avoiding these types of problems. Here is guidance to limit the volume of queries based on the scaling best practices. Suggestions in this section are provided in order starting with the options that are known to scale the best.

+

Use Shared Informers

+

When building controllers and automation that integrate with the Kubernetes API you will often need to get information from Kubernetes resources. If you poll for these resources regularly it can cause a significant load on the API server.

+

Using an informer from the client-go library will give you benefits of watching for changes to the resources based on events instead of polling for changes. Informers further reduce the load by using shared cache for the events and changes so multiple controllers watching the same resources do not add additional load.

+

Controllers should avoid polling cluster wide resources without labels and field selectors especially in large clusters. Each un-filtered poll requires a lot of unnecessary data to be sent from etcd through the API server to be filtered by the client. By filtering based on labels and namespaces you can reduce the amount of work the API server needs to perform to fullfil the request and data sent to the client.

+

Optimize Kubernetes API usage

+

When calling the Kubernetes API with custom controllers or automation it's important that you limit the calls to only the resources you need. Without limits you can cause unneeded load on the API server and etcd.

+

It is recommended that you use the watch argument whenever possible. With no arguments the default behavior is to list objects. To use watch instead of list you can append ?watch=true to the end of your API request. For example, to get all pods in the default namespace with a watch use:

+
/api/v1/namespaces/default/pods?watch=true
+
+

If you are listing objects you should limit the scope of what you are listing and the amount of data returned. You can limit the returned data by adding limit=500 argument to requests. The fieldSelector argument and /namespace/ path can be useful to make sure your lists are as narrowly scoped as needed. For example, to list only running pods in the default namespace use the following API path and arguments.

+
/api/v1/namespaces/default/pods?fieldSelector=status.phase=Running&limit=500
+
+

Or list all pods that are running with:

+
/api/v1/pods?fieldSelector=status.phase=Running&limit=500
+
+

Another option to limit watch calls or listed objects is to use resourceVersions which you can read about in the Kubernetes documentation. Without a resourceVersion argument you will receive the most recent version available which requires an etcd quorum read which is the most expensive and slowest read for the database. The resourceVersion depends on what resources you are trying to query and can be found in the metadata.resourseVersion field. This is also recommended in case of using watch calls and not just list calls

+

There is a special resourceVersion=0 available that will return results from the API server cache. This can reduce etcd load but it does not support pagination.

+

/api/v1/namespaces/default/pods?resourceVersion=0
+
+It's recommended to use watch with a resourceVersion set to be the most recent known value received from its preceding list or watch. This is handled automatically in client-go. But it's suggested to double check it if you are using a k8s client in other languages.

+

/api/v1/namespaces/default/pods?watch=true&resourceVersion=362812295
+
+If you call the API without any arguments it will be the most resource intensive for the API server and etcd. This call will get all pods in all namespaces without pagination or limiting the scope and require a quorum read from etcd.

+
/api/v1/pods
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/data-plane/index.html b/scalability/docs/data-plane/index.html new file mode 100644 index 000000000..86f7d64b8 --- /dev/null +++ b/scalability/docs/data-plane/index.html @@ -0,0 +1,2436 @@ + + + + + + + + + + + + + + + + + + + + + + + Data Plane - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Kubernetes Data Plane

+

The Kubernetes Data Plane includes EC2 instances, load balancers, storage, and other APIs used by the Kubernetes Control Plane. For organization purposes we grouped cluster services in a separate page and load balancer scaling can be found in the workloads section. This section will focus on scaling compute resources.

+

Selecting EC2 instance types is possibly one of the hardest decisions customers face because in clusters with multiple workloads. There is no one-size-fits all solution. Here are some tips to help you avoid common pitfalls with scaling compute.

+

Automatic node autoscaling

+

We recommend you use node autoscaling that reduces toil and integrates deeply with Kubernetes. Managed node groups and Karpenter are recommended for large scale clusters.

+

Managed node groups will give you the flexibility of Amazon EC2 Auto Scaling groups with added benefits for managed upgrades and configuration. It can be scaled with the Kubernetes Cluster Autoscaler and is a common option for clusters that have a variety of compute needs.

+

Karpenter is an open source, workload-native node autoscaler created by AWS. It scales nodes in a cluster based on the workload requirements for resources (e.g. GPU) and taints and tolerations (e.g. zone spread) without managing node groups. Nodes are created directly from EC2 which avoids default node group quotas—450 nodes per group—and provides greater instance selection flexibility with less operational overhead. We recommend customers use Karpenter when possible.

+

Use many different EC2 instance types

+

Each AWS region has a limited number of available instances per instance type. If you create a cluster that uses only one instance type and scale the number of nodes beyond the capacity of the region you will receive an error that no instances are available. To avoid this issue you should not arbitrarily limit the type of instances that can be use in your cluster.

+

Karpenter will use a broad set of compatible instance types by default and will pick an instance at provisioning time based on pending workload requirements, availability, and cost. You can broaden the list of instance types used in the karpenter.k8s.aws/instance-category key of NodePools.

+

The Kubernetes Cluster Autoscaler requires node groups to be similarly sized so they can be consistently scaled. You should create multiple groups based on CPU and memory size and scale them independently. Use the ec2-instance-selector to identify instances that are similarly sized for your node groups.

+
ec2-instance-selector --service eks --vcpus-min 8 --memory-min 16
+a1.2xlarge
+a1.4xlarge
+a1.metal
+c4.4xlarge
+c4.8xlarge
+c5.12xlarge
+c5.18xlarge
+c5.24xlarge
+c5.2xlarge
+c5.4xlarge
+c5.9xlarge
+c5.metal
+
+

Prefer larger nodes to reduce API server load

+

When deciding what instance types to use, fewer, large nodes will put less load on the Kubernetes Control Plane because there will be fewer kubelets and DaemonSets running. However, large nodes may not be utilized fully like smaller nodes. Node sizes should be evaluated based on your workload availability and scale requirements.

+

A cluster with three u-24tb1.metal instances (24 TB memory and 448 cores) has 3 kubelets, and would be limited to 110 pods per node by default. If your pods use 4 cores each then this might be expected (4 cores x 110 = 440 cores/node). With a 3 node cluster your ability to handle an instance incident would be low because 1 instance outage could impact 1/3 of the cluster. You should specify node requirements and pod spread in your workloads so the Kubernetes scheduler can place workloads properly.

+

Workloads should define the resources they need and the availability required via taints, tolerations, and PodTopologySpread. They should prefer the largest nodes that can be fully utilized and meet availability goals to reduce control plane load, lower operations, and reduce cost.

+

The Kubernetes Scheduler will automatically try to spread workloads across availability zones and hosts if resources are available. If no capacity is available the Kubernetes Cluster Autoscaler will attempt to add nodes in each Availability Zone evenly. Karpenter will attempt to add nodes as quickly and cheaply as possible unless the workload specifies other requirements.

+

To force workloads to spread with the scheduler and new nodes to be created across availability zones you should use topologySpreadConstraints:

+
spec:
+  topologySpreadConstraints:
+    - maxSkew: 3
+      topologyKey: "topology.kubernetes.io/zone"
+      whenUnsatisfiable: ScheduleAnyway
+      labelSelector:
+        matchLabels:
+          dev: my-deployment
+    - maxSkew: 2
+      topologyKey: "kubernetes.io/hostname"
+      whenUnsatisfiable: ScheduleAnyway
+      labelSelector:
+        matchLabels:
+          dev: my-deployment
+
+

Use similar node sizes for consistent workload performance

+

Workloads should define what size nodes they need to be run on to allow consistent performance and predictable scaling. A workload requesting 500m CPU will perform differently on an instance with 4 cores vs one with 16 cores. Avoid instance types that use burstable CPUs like T series instances.

+

To make sure your workloads get consistent performance a workload can use the supported Karpenter labels to target specific instances sizes.

+
kind: deployment
+...
+spec:
+  template:
+    spec:
+    containers:
+    nodeSelector:
+      karpenter.k8s.aws/instance-size: 8xlarge
+
+

Workloads being scheduled in a cluster with the Kubernetes Cluster Autoscaler should match a node selector to node groups based on label matching.

+
spec:
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: eks.amazonaws.com/nodegroup
+            operator: In
+            values:
+            - 8-core-node-group    # match your node group name
+
+

Use compute resources efficiently

+

Compute resources include EC2 instances and availability zones. Using compute resources effectively will increase your scalability, availability, performance, and reduce your total cost. Efficient resource usage is extremely difficult to predict in an autoscaling environment with multiple applications. Karpenter was created to provision instances on-demand based on the workload needs to maximize utilization and flexibility.

+

Karpenter allows workloads to declare the type of compute resources it needs without first creating node groups or configuring label taints for specific nodes. See the Karpenter best practices for more information. Consider enabling consolidation in your Karpenter provisioner to replace nodes that are under utilized.

+

Automate Amazon Machine Image (AMI) updates

+

Keeping worker node components up to date will make sure you have the latest security patches and compatible features with the Kubernetes API. Updating the kubelet is the most important component for Kubernetes functionality, but automating OS, kernel, and locally installed application patches will reduce maintenance as you scale.

+

It is recommended that you use the latest Amazon EKS optimized Amazon Linux 2 or Amazon EKS optimized Bottlerocket AMI for your node image. Karpenter will automatically use the latest available AMI to provision new nodes in the cluster. Managed node groups will update the AMI during a node group update but will not update the AMI ID at node provisioning time.

+

For Managed Node Groups you need to update the Auto Scaling Group (ASG) launch template with new AMI IDs when they are available for patch releases. AMI minor versions (e.g. 1.23.5 to 1.24.3) will be available in the EKS console and API as upgrades for the node group. Patch release versions (e.g. 1.23.5 to 1.23.6) will not be presented as upgrades for the node groups. If you want to keep your node group up to date with AMI patch releases you need to create new launch template version and let the node group replace instances with the new AMI release.

+

You can find the latest available AMI from this page or use the AWS CLI.

+
aws ssm get-parameter \
+  --name /aws/service/eks/optimized-ami/1.24/amazon-linux-2/recommended/image_id \
+  --query "Parameter.Value" \
+  --output text
+
+

Use multiple EBS volumes for containers

+

EBS volumes have input/output (I/O) quota based on the type of volume (e.g. gp3) and the size of the disk. If your applications share a single EBS root volume with the host this can exhaust the disk quota for the entire host and cause other applications to wait for available capacity. Applications write to disk if they write files to their overlay partition, mount a local volume from the host, and also when they log to standard out (STDOUT) depending on the logging agent used.

+

To avoid disk I/O exhaustion you should mount a second volume to the container state folder (e.g. /run/containerd), use separate EBS volumes for workload storage, and disable unnecessary local logging.

+

To mount a second volume to your EC2 instances using eksctl you can use a node group with this configuration:

+
managedNodeGroups:
+  - name: al2-workers
+    amiFamily: AmazonLinux2
+    desiredCapacity: 2
+    volumeSize: 80
+    additionalVolumes:
+      - volumeName: '/dev/sdz'
+        volumeSize: 100
+    preBootstrapCommands:
+    - |
+      "systemctl stop containerd"
+      "mkfs -t ext4 /dev/nvme1n1"
+      "rm -rf /var/lib/containerd/*"
+      "mount /dev/nvme1n1 /var/lib/containerd/"
+      "systemctl start containerd"
+
+

If you are using terraform to provision your node groups please see examples in EKS Blueprints for terraform. If you are using Karpenter to provision nodes you can use blockDeviceMappings with node user-data to add additional volumes.

+

To mount an EBS volume directly to your pod you should use the AWS EBS CSI driver and consume a volume with a storage class.

+
---
+apiVersion: storage.k8s.io/v1
+kind: StorageClass
+metadata:
+  name: ebs-sc
+provisioner: ebs.csi.aws.com
+volumeBindingMode: WaitForFirstConsumer
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: ebs-claim
+spec:
+  accessModes:
+    - ReadWriteOnce
+  storageClassName: ebs-sc
+  resources:
+    requests:
+      storage: 4Gi
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: app
+spec:
+  containers:
+  - name: app
+    image: public.ecr.aws/docker/library/nginx
+    volumeMounts:
+    - name: persistent-storage
+      mountPath: /data
+  volumes:
+  - name: persistent-storage
+    persistentVolumeClaim:
+      claimName: ebs-claim
+
+

Avoid instances with low EBS attach limits if workloads use EBS volumes

+

EBS is one of the easiest ways for workloads to have persistent storage, but it also comes with scalability limitations. Each instance type has a maximum number of EBS volumes that can be attached. Workloads need to declare what instance types they should run on and limit the number of replicas on a single instance with Kubernetes taints.

+

Disable unnecessary logging to disk

+

Avoid unnecessary local logging by not running your applications with debug logging in production and disabling logging that reads and writes to disk frequently. Journald is the local logging service that keeps a log buffer in memory and flushes to disk periodically. Journald is preferred over syslog which logs every line immediately to disk. Disabling syslog also lowers the total amount of storage you need and avoids needing complicated log rotation rules. To disable syslog you can add the following snippet to your cloud-init configuration:

+
runcmd:
+  - [ systemctl, disable, --now, syslog.service ]
+
+

Patch instances in place when OS update speed is a necessity

+
+

Attention

+

Patching instances in place should only be done when required. Amazon recommends treating infrastructure as immutable and thoroughly testing updates that are promoted through lower environments the same way applications are. This section applies when that is not possible.

+
+

It takes seconds to install a package on an existing Linux host without disrupting containerized workloads. The package can be installed and validated without cordoning, draining, or replacing the instance.

+

To replace an instance you first need to create, validate, and distribute new AMIs. The instance needs to have a replacement created, and the old instance needs to be cordoned and drained. Then workloads need to be created on the new instance, verified, and repeated for all instances that need to be patched. It takes hours, days, or weeks to replace instances safely without disrupting workloads.

+

Amazon recommends using immutable infrastructure that is built, tested, and promoted from an automated, declarative system, but if you have a requirement to patch systems quickly then you will need to patch systems in place and replace them as new AMIs are made available. Because of the large time differential between patching and replacing systems we recommend using AWS Systems Manager Patch Manager to automate patching nodes when required to do so.

+

Patching nodes will allow you to quickly roll out security updates and replace the instances on a regular schedule after your AMI has been updated. If you are using an operating system with a read-only root file system like Flatcar Container Linux or Bottlerocket OS we recommend using the update operators that work with those operating systems. The Flatcar Linux update operator and Bottlerocket update operator will reboot instances to keep nodes up to date automatically.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/index.html b/scalability/docs/index.html new file mode 100644 index 000000000..72fa50e9f --- /dev/null +++ b/scalability/docs/index.html @@ -0,0 +1,2173 @@ + + + + + + + + + + + + + + + + + + + + + + + Home - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

EKS Scalability best practices

+

This guide provides advice for scaling EKS clusters. The goal of scaling an EKS cluster is to maximize the amount of work a single cluster can perform. Using a single, large EKS cluster can reduce operational load compared to using multiple clusters, but it has trade-offs for things like multi-region deployments, tenant isolation, and cluster upgrades. In this document we will focus on how to achieve maximum scalability with a single cluster.

+

How to use this guide

+

This guide is meant for developers and administrators responsible for creating and managing EKS clusters in AWS. It focuses on some generic Kubernetes scaling practices, but it does not have specifics for self-managed Kubernetes clusters or clusters that run outside of an AWS region with EKS Anywhere.

+

Each topic has a brief overview, followed by recommendations and best practices for operating EKS clusters at scale. Topics do not need to be read in a particular order and recommendations should not be applied without testing and verifying they work in your clusters.

+

Understanding scaling dimensions

+

Scalability is different from performance and reliability, and all three should be considered when planning your cluster and workload needs. As clusters scale, they need to be monitored, but this guide will not cover monitoring best practices. EKS can scale to large sizes, but you will need to plan how you are going to scale a cluster beyond 300 nodes or 5000 pods. These are not absolute numbers, but they come from collaborating this guide with multiple users, engineers, and support professionals.

+

Scaling in Kubernetes is multi-dimensional and there are no specific settings or recommendations that work in every situation. The main areas areas where we can provide guidance for scaling include:

+ +

Kubernetes Control Plane in an EKS cluster includes all of the services AWS runs and scales for you automatically (e.g. Kubernetes API server). Scaling the Control Plane is AWS's responsibility, but using the Control Plane responsibly is your responsibility.

+

Kubernetes Data Plane scaling deals with AWS resources that are required for your cluster and workloads, but they are outside of the EKS Control Plane. Resources including EC2 instances, kubelet, and storage all need to be scaled as your cluster scales.

+

Cluster services are Kubernetes controllers and applications that run inside the cluster and provide functionality for your cluster and workloads. These can be EKS Add-ons and also other services or Helm charts you install for compliance and integrations. These services are often depended on by workloads and as your workloads scale your cluster services will need to scale with them.

+

Workloads are the reason you have a cluster and should scale horizontally with the cluster. There are integrations and settings that workloads have in Kubernetes that can help the cluster scale. There are also architectural considerations with Kubernetes abstractions such as namespaces and services.

+

Extra large scaling

+

If you are scaling a single cluster beyond 1000 nodes or 50,000 pods we would love to talk to you. We recommend reaching out to your support team or technical account manager to get in touch with specialists who can help you plan and scale beyond the information provided in this guide.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/kcp_monitoring/index.html b/scalability/docs/kcp_monitoring/index.html new file mode 100644 index 000000000..41aa94b01 --- /dev/null +++ b/scalability/docs/kcp_monitoring/index.html @@ -0,0 +1,2508 @@ + + + + + + + + + + + + + + + + + + + + + + + Control plane monitoring - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Control Plane Monitoring

+

API Server

+

When looking at our API server it’s important to remember that one of its functions is to throttle inbound requests to prevent overloading the control plane. What can seem like a bottleneck at the API server level might actually be protecting it from more serious issues. We need to factor in the pros and cons of increasing the volume of requests moving through the system. To make a determination if the API server values should be increased, here is small sampling of the things we need to be mindful of:

+
    +
  1. What is the latency of requests moving through the system?
  2. +
  3. Is that latency the API server itself, or something “downstream” like etcd?
  4. +
  5. Is the API server queue depth a factor in this latency?
  6. +
  7. Are the API Priority and Fairness (APF) queues setup correctly for the API call patterns we want?
  8. +
+

Where is the issue?

+

To start, we can use the metric for API latency to give us insight into how long it’s taking the API server to service requests. Let’s use the below PromQL and Grafana heatmap to display this data.

+
max(increase(apiserver_request_duration_seconds_bucket{subresource!="status",subresource!="token",subresource!="scale",subresource!="/healthz",subresource!="binding",subresource!="proxy",verb!="WATCH"}[$__rate_interval])) by (le)
+
+
+

Tip

+

For an in depth write up on how to monitor the API server with the API dashboard used in this article, please see the following blog

+
+

API request duration heatmap

+

These requests are all under the one second mark, which is a good indication that the control plane is handling requests in a timely fashion. But what if that was not the case?

+

The format we are using in the above API Request Duration is a heatmap. What’s nice about the heatmap format, is that it tells us the timeout value for the API by default (60 sec). However, what we really need to know is at what threshold should this value be of concern before we reach the timeout threshold. For a rough guideline of what acceptable thresholds are we can use the upstream Kubernetes SLO, which can be found here

+
+

Tip

+

Notice the max function on this statement? When using metrics that are aggregating multiple servers (by default two API servers on EKS) it’s important not to average those servers together.

+
+

Asymmetrical traffic patterns

+

What if one API server [pod] was lightly loaded, and the other heavily loaded? If we averaged those two numbers together we might misinterpret what was happening. For example, here we have three API servers but all of the load is on one of these API servers. As a rule anything that has multiple servers such as etcd and API servers should be broken out when investing scale and performance issues.

+

Total inflight requests

+

With the move to API Priority and Fairness the total number of requests on the system is only one factor to check to see if the API server is oversubscribed. Since the system now works off a series of queues, we must look to see if any of these queues are full and if the traffic for that queue is getting dropped.

+

Let’s look at these queues with the following query:

+
max without(instance)(apiserver_flowcontrol_request_concurrency_limit{})
+
+
+

Note

+

For more information on how API A&F works please see the following best practices guide

+
+

Here we see the seven different priority groups that come by default on the cluster

+

Shared concurrency

+

Next we want to see what percentage of that priority group is being used, so that we can understand if a certain priority level is being saturated. Throttling requests in the workload-low level might be desirable, however drops in a leader election level would not be.

+

The API Priority and Fairness (APF) system has a number of complex options, some of those options can have unintended consequences. A common issue we see in the field is increasing the queue depth to the point it starts adding unnecessary latency. We can monitor this problem by using the apiserver_flowcontrol_current_inqueue_request metric. We can check for drops using the apiserver_flowcontrol_rejected_requests_total. These metrics will be a non-zero value if any bucket exceeds its concurrency.

+

Requests in use

+

Increasing the queue depth can make the API Server a significant source of latency and should be done with care. We recommend being judicious with the number of queues created. For example, the number of shares on a EKS system is 600, if we create too many queues, this can reduce the shares in important queues that need the throughput such as the leader-election queue or system queue. Creating too many extra queues can make it more difficult to size theses queues correctly.

+

To focus on a simple impactful change you can make in APF we simply take shares from underutilized buckets and increase the size of buckets that are at their max usage. By intelligently redistributing the shares among these buckets, you can make drops less likely.

+

For more information, visit API Priority and Fairness settings in the EKS Best Practices Guide.

+

API vs. etcd latency

+

How can we use the metrics/logs of the API server to determine whether there’s a problem with API server, or a problem that’s upstream/downstream of the API server, or a combination of both. To understand this better, lets look at how API Server and etcd can be related, and how easy it can be to troubleshoot the wrong system.

+

In the below chart we see API server latency, but we also see much of this latency is correlated to the etcd server due to the bars in the graph showing most of the latency at the etcd level. If there is 15 secs of etcd latency at the same time there is 20 seconds of API server latency, then the majority of the latency is actually at the etcd level.

+

By looking at the whole flow, we see that it’s wise to not focus solely on the API Server, but also look for signals that indicate that etcd is under duress (i.e. slow apply counters increasing). Being able to quickly move to the right problem area with just a glance is what makes a dashboard powerful.

+
+

Tip

+

The dashboard in section can be found at https://github.com/RiskyAdventure/Troubleshooting-Dashboards/blob/main/api-troubleshooter.json

+
+

ETCD duress

+

Control plane vs. Client side issues

+

In this chart we are looking for the API calls that took the most time to complete for that period. In this case we see a custom resource (CRD) is calling a APPLY function that is the most latent call during the 05:40 time frame.

+

Slowest requests

+

Armed with this data we can use an Ad-Hoc PromQL or a CloudWatch Insights query to pull LIST requests from the audit log during that time frame to see which application this might be.

+

Finding the Source with CloudWatch

+

Metrics are best used to find the problem area we want to look at and narrow both the timeframe and the search parameters of the problem. Once we have this data we want to transition to logs for more detailed times and errors. To do this we will turn our logs into metrics using CloudWatch Logs Insights.

+

For example, to investigate the issue above, we will use the following CloudWatch Logs Insights query to pull the userAgent and requestURI so that we can pin down which application is causing this latency.

+
+

Tip

+

An appropriate Count needs to be used as to not pull normal List/Resync behavior on a Watch.

+
+
fields *@timestamp*, *@message*
+| filter *@logStream* like "kube-apiserver-audit"
+| filter ispresent(requestURI)
+| filter verb = "list"
+| parse requestReceivedTimestamp /\d+-\d+-(?<StartDay>\d+)T(?<StartHour>\d+):(?<StartMinute>\d+):(?<StartSec>\d+).(?<StartMsec>\d+)Z/
+| parse stageTimestamp /\d+-\d+-(?<EndDay>\d+)T(?<EndHour>\d+):(?<EndMinute>\d+):(?<EndSec>\d+).(?<EndMsec>\d+)Z/
+| fields (StartHour * 3600 + StartMinute * 60 + StartSec + StartMsec / 1000000) as StartTime, (EndHour * 3600 + EndMinute * 60 + EndSec + EndMsec / 1000000) as EndTime, (EndTime - StartTime) as DeltaTime
+| stats avg(DeltaTime) as AverageDeltaTime, count(*) as CountTime by requestURI, userAgent
+| filter CountTime >=50
+| sort AverageDeltaTime desc
+
+

Using this query we found two different agents running a large number of high latency list operations. Splunk and CloudWatch agent. Armed with the data, we can make a decision to remove, update, or replace this controller with another project.

+

Query results

+
+

Tip

+

For more details on this subject please see the following blog

+
+

Scheduler

+

Since the EKS control plane instances are run in separate AWS account we will not be able to scrape those components for metrics (The API server being the exception). However, since we have access to the audit logs for these components, we can turn those logs into metrics to see if any of the sub-systems are causing a scaling bottleneck. Let’s use CloudWatch Logs Insights to see how many unscheduled pods are in the scheduler queue.

+

Unscheduled pods in the scheduler log

+

If we had access to scrape the scheduler metrics directly on a self managed Kubernetes (such as Kops) we would use the following PromQL to understand the scheduler backlog.

+
max without(instance)(scheduler_pending_pods)
+
+

Since we do not have access to the above metric in EKS, we will use the below CloudWatch Logs Insights query to see the backlog by checking for how many pods were unable to unscheduled during a particular time frame. Then we could dive further into into the messages at the peak time frame to understand the nature of the bottleneck. For example, nodes not spinning up fast enough, or the rate limiter in the scheduler itself.

+
fields timestamp, pod, err, *@message*
+| filter *@logStream* like "scheduler"
+| filter *@message* like "Unable to schedule pod"
+| parse *@message*  /^.(?<date>\d{4})\s+(?<timestamp>\d+:\d+:\d+\.\d+)\s+\S*\s+\S+\]\s\"(.*?)\"\s+pod=(?<pod>\"(.*?)\")\s+err=(?<err>\"(.*?)\")/
+| count(*) as count by pod, err
+| sort count desc
+
+

Here we see the errors from the scheduler saying the pod did not deploy because the storage PVC was unavailable.

+

CloudWatch Logs query

+
+

Note

+

Audit logging must be turned on the control plane to enable this function. It is also a best practice to limit the log retention as to not drive up cost over time unnecessarily. An example for turning on all logging functions using the EKSCTL tool below.

+
+
cloudWatch:
+  clusterLogging:
+    enableTypes: ["*"]
+    logRetentionInDays: 10
+
+

Kube Controller Manager

+

Kube Controller Manager, like all other controllers, has limits on how many operations it can do at once. Let’s review what some of those flags are by looking at a KOPS configuration where we can set these parameters.

+
  kubeControllerManager:
+    concurrentEndpointSyncs: 5
+    concurrentReplicasetSyncs: 5
+    concurrentNamespaceSyncs: 10
+    concurrentServiceaccountTokenSyncs: 5
+    concurrentServiceSyncs: 5
+    concurrentResourceQuotaSyncs: 5
+    concurrentGcSyncs: 20
+    kubeAPIBurst: 20
+    kubeAPIQPS: "30"
+
+

These controllers have queues that fill up during times of high churn on a cluster. In this case we see the replicaset set controller has a large backlog in its queue.

+

Queues

+

We have two different ways of addressing such a situation. If running self managed we could simply increase the concurrent goroutines, however this would have an impact on etcd by processing more data in the KCM. The other option would be to reduce the number of replicaset objects using .spec.revisionHistoryLimit on the deployment to reduce the number of replicaset objects we can rollback, thus reducing the pressure on this controller.

+
spec:
+  revisionHistoryLimit: 2
+
+

Other Kubernetes features can be tuned or turned off to reduce pressure in high churn rate systems. For example, if the application in our pods doesn’t need to speak to the k8s API directly then turning off the projected secret into those pods would decrease the load on ServiceaccountTokenSyncs. This is the more desirable way to address such issues if possible.

+
kind: Pod
+spec:
+  automountServiceAccountToken: false
+
+

In systems where we can’t get access to the metrics, we can again look at the logs to detect contention. If we wanted to see the number of requests being being processed on a per controller or an aggregate level we would use the following CloudWatch Logs Insights Query.

+

Total Volume Processed by the KCM

+
# Query to count API qps coming from kube-controller-manager, split by controller type.
+# If you're seeing values close to 20/sec for any particular controller, it's most likely seeing client-side API throttling.
+fields @timestamp, @logStream, @message
+| filter @logStream like /kube-apiserver-audit/
+| filter userAgent like /kube-controller-manager/
+# Exclude lease-related calls (not counted under kcm qps)
+| filter requestURI not like "apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager"
+# Exclude API discovery calls (not counted under kcm qps)
+| filter requestURI not like "?timeout=32s"
+# Exclude watch calls (not counted under kcm qps)
+| filter verb != "watch"
+# If you want to get counts of API calls coming from a specific controller, uncomment the appropriate line below:
+# | filter user.username like "system:serviceaccount:kube-system:job-controller"
+# | filter user.username like "system:serviceaccount:kube-system:cronjob-controller"
+# | filter user.username like "system:serviceaccount:kube-system:deployment-controller"
+# | filter user.username like "system:serviceaccount:kube-system:replicaset-controller"
+# | filter user.username like "system:serviceaccount:kube-system:horizontal-pod-autoscaler"
+# | filter user.username like "system:serviceaccount:kube-system:persistent-volume-binder"
+# | filter user.username like "system:serviceaccount:kube-system:endpointslice-controller"
+# | filter user.username like "system:serviceaccount:kube-system:endpoint-controller"
+# | filter user.username like "system:serviceaccount:kube-system:generic-garbage-controller"
+| stats count(*) as count by user.username
+| sort count desc
+
+

The key takeaway here is when looking into scalability issues, to look at every step in the path (API, scheduler, KCM, etcd) before moving to the detailed troubleshooting phase. Often in production you will find that it takes adjustments to more than one part of Kubernetes to allow the system to work at its most performant. It’s easy to inadvertently troubleshoot what is just a symptom (such as a node timeout) of a much larger bottle neck.

+

ETCD

+

etcd uses a memory mapped file to store key value pairs efficiently. There is a protection mechanism to set the size of this memory space available set commonly at the 2, 4, and 8GB limits. Fewer objects in the database means less clean up etcd needs to do when objects are updated and older versions needs to be cleaned out. This process of cleaning old versions of an object out is referred to as compaction. After a number of compaction operations, there is a subsequent process that recovers usable space space called defragging that happens above a certain threshold or on a fixed schedule of time.

+

There are a couple user related items we can do to limit the number of objects in Kubernetes and thus reduce the impact of both the compaction and de-fragmentation process. For example, Helm keeps a high revisionHistoryLimit. This keeps older objects such as ReplicaSets on the system to be able to do rollbacks. By setting the history limits down to 2 we can reduce the number of objects (like ReplicaSets) from ten to two which in turn would put less load on the system.

+
apiVersion: apps/v1
+kind: Deployment
+spec:
+  revisionHistoryLimit: 2
+
+

From a monitoring standpoint, if system latency spikes occur in a set pattern separated by hours, checking to see if this defragmentation process is the source can be helpful. We can see this by using CloudWatch Logs.

+

If you want to see start/end times of defrag use the following query:

+
fields *@timestamp*, *@message*
+| filter *@logStream* like /etcd-manager/
+| filter *@message* like /defraging|defraged/
+| sort *@timestamp* asc
+
+

Defrag query

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/kubernetes_slos/index.html b/scalability/docs/kubernetes_slos/index.html new file mode 100644 index 000000000..3211fcea1 --- /dev/null +++ b/scalability/docs/kubernetes_slos/index.html @@ -0,0 +1,2440 @@ + + + + + + + + + + + + + + + + + + + + + + + Kubernetes SLOs - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Kubernetes Upstream SLOs

+

Amazon EKS runs the same code as the upstream Kubernetes releases and ensures that EKS clusters operate within the SLOs defined by the Kubernetes community. The KubernetesScalability Special Interest Group (SIG) defines the scalability goals and investigates bottlenecks in performance through SLIs and SLOs.

+

SLIs are how we measure a system like metrics or measures that can be used to determine how “well” the system is running, e.g. request latency or count. SLOs define the values that are expected for when the system is running “well”, e.g. request latency remains less than 3 seconds. The Kubernetes SLOs and SLIs focus on the performance of the Kubernetes components and are completely independent from the Amazon EKS Service SLAs which focus on availability of the EKS cluster endpoint.

+

Kubernetes has a number of features that allow users to extend the system with custom add-ons or drivers, like CSI drivers, admission webhooks, and auto-scalers. These extensions can drastically impact the performance of a Kubernetes cluster in different ways, i.e. an admission webhook with failurePolicy=Ignore could add latency to K8s API requests if the webhook target is unavailable. The Kubernetes Scalability SIG defines scalability using a "you promise, we promise" framework:

+
+

If you promise to:
+ - correctly configure your cluster
+ - use extensibility features "reasonably"
+ - keep the load in the cluster within recommended limits

+

then we promise that your cluster scales, i.e.:
+ - all the SLOs are satisfied.

+
+

Kubernetes SLOs

+

The Kubernetes SLOs don’t account for all of the plugins and external limitations that could impact a cluster, such as worker node scaling or admission webhooks. These SLOs focus on Kubernetes components and ensure that Kubernetes actions and resources are operating within expectations. The SLOs help Kubernetes developers ensure that changes to Kubernetes code do not degrade performance for the entire system.

+

The Kuberntes Scalability SIG defines the following official SLO/SLIs. The Amazon EKS team regularly runs scalability tests on EKS clusters for these SLOs/SLIs to monitor for performance degradation as changes are made and new versions are released.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ObjectiveDefinitionSLO
API request latency (mutating)Latency of processing mutating API calls for single objects for every (resource, verb) pair, measured as 99th percentile over last 5 minutesIn default Kubernetes installation, for every (resource, verb) pair, excluding virtual and aggregated resources and Custom Resource Definitions, 99th percentile per cluster-day <= 1s
API request latency (read-only)Latency of processing non-streaming read-only API calls for every (resource, scope) pair, measured as 99th percentile over last 5 minutesIn default Kubernetes installation, for every (resource, scope) pair, excluding virtual and aggregated resources and Custom Resource Definitions, 99th percentile per cluster-day: (a) <= 1s if scope=resource (b) <= 30s otherwise (if scope=namespace or scope=cluster)
Pod startup latencyStartup latency of schedulable stateless pods, excluding time to pull images and run init containers, measured from pod creation timestamp to when all its containers are reported as started and observed via watch, measured as 99th percentile over last 5 minutesIn default Kubernetes installation, 99th percentile per cluster-day <= 5s
+

API Request Latency

+

The kube-apiserver has --request-timeout defined as 1m0s by default, which means a request can run for up to one minute (60 seconds) before being timed out and cancelled. The SLOs defined for Latency are broken out by the type of request that is being made, which can be mutating or read-only:

+

Mutating

+

Mutating requests in Kubernetes make changes to a resource, such as creations, deletions, or updates. These requests are expensive because those changes must be written to the etcd backend before the updated object is returned. Etcd is a distributed key-value store that is used for all Kubernetes cluster data.

+

This latency is measured as the 99th percentile over 5min for (resource, verb) pairs of Kubernetes resources, for example this would measure the latency for Create Pod requests and Update Node requests. The request latency must be <= 1 second to satisfy the SLO.

+

Read-only

+

Read-only requests retrieve a single resource (such as Get Pod X) or a collection (such as “Get all Pods from Namespace X”). The kube-apiserver maintains a cache of objects, so the requested resources may be returned from cache or they may need to be retrieved from etcd first. +These latencies are also measured by the 99th percentile over 5 minutes, however read-only requests can have separate scopes. The SLO defines two different objectives:

+
    +
  • For requests made for a single resource (i.e. kubectl get pod -n mynamespace my-controller-xxx ), the request latency should remain <= 1 second.
  • +
  • For requests that are made for multiple resources in a namespace or a cluster (for example, kubectl get pods -A) the latency should remain <= 30 seconds
  • +
+

The SLO has different target values for different request scopes because requests made for a list of Kubernetes resources expect the details of all objects in the request to be returned within the SLO. On large clusters, or large collections of resources, this can result in large response sizes which can take some time to return. For example, in a cluster running tens of thousands of Pods with each Pod being roughly 1 KiB when encoded in JSON, returning all Pods in the cluster would consist of 10MB or more. Kubernetes clients can help reduce this response size using APIListChunking to retrieve large collections of resources.

+

Pod Startup Latency

+

This SLO is primarily concerned with the time it takes from Pod creation to when the containers in that Pod actually begin execution. To measure this the difference from the creation timestamp recorded on the Pod, and when a WATCH on that Pod reports the containers have started is calculated (excluding time for container image pulls and init container execution). To satisfy the SLO the 99th percentile per cluster-day of this Pod Startup Latency must remain <=5 seconds.

+

Note that this SLO assumes that the worker nodes already exist in this cluster in a ready state for the Pod to be scheduled on. This SLO does not account for image pulls or init container executions, and also limits the test to “stateless pods” which don’t leverage persistent storage plugins.

+

Kubernetes SLI Metrics

+

Kubernetes is also improving the Observability around the SLIs by adding Prometheus metrics to Kubernetes components that track these SLIs over time. Using Prometheus Query Language (PromQL) we can build queries that display the SLI performance over time in tools like Prometheus or Grafana dashboards, below are some examples for the SLOs above.

+

API Server Request Latency

+ + + + + + + + + + + + + + + + + +
MetricDefinition
apiserver_request_sli_duration_secondsResponse latency distribution (not counting webhook duration and priority & fairness queue wait times) in seconds for each verb, group, version, resource, subresource, scope and component.
apiserver_request_duration_secondsResponse latency distribution in seconds for each verb, dry run value, group, version, resource, subresource, scope and component.
+

Note: The apiserver_request_sli_duration_seconds metric is available starting in Kubernetes 1.27.

+

You can use these metrics to investigate the API Server response times and if there are bottlenecks in the Kubernetes components or other plugins/components. The queries below are based on the community SLO dashboard.

+

API Request latency SLI (mutating) - this time does not include webhook execution or time waiting in queue.
+histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"CREATE|DELETE|PATCH|POST|PUT", subresource!~"proxy|attach|log|exec|portforward"}[5m])) by (resource, subresource, verb, scope, le)) > 0

+

API Request latency Total (mutating) - this is the total time the request took on the API server, this time may be longer than the SLI time because it includes webhook execution and API Priority and Fairness wait times.
+histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"CREATE|DELETE|PATCH|POST|PUT", subresource!~"proxy|attach|log|exec|portforward"}[5m])) by (resource, subresource, verb, scope, le)) > 0

+

In these queries we are excluding the streaming API requests which do not return immediately, such as kubectl port-forward or kubectl exec requests (subresource!~"proxy|attach|log|exec|portforward"), and we are filtering for only the Kubernetes verbs that modify objects (verb=~"CREATE|DELETE|PATCH|POST|PUT"). We are then calculating the 99th percentile of that latency over the last 5 minutes.

+

We can use a similar query for the read only API requests, we simply modify the verbs we’re filtering for to include the Read only actions LIST and GET. There are also different SLO thresholds depending on the scope of the request, i.e. getting a single resource or listing a number of resources.

+

API Request latency SLI (read-only) - this time does not include webhook execution or time waiting in queue. +For a single resource (scope=resource, threshold=1s)
+histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"GET", scope=~"resource"}[5m])) by (resource, subresource, verb, scope, le))

+

For a collection of resources in the same namespace (scope=namespace, threshold=5s)
+histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"LIST", scope=~"namespace"}[5m])) by (resource, subresource, verb, scope, le))

+

For a collection of resources across the entire cluster (scope=cluster, threshold=30s)
+histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"LIST", scope=~"cluster"}[5m])) by (resource, subresource, verb, scope, le))

+

API Request latency Total (read-only) - this is the total time the request took on the API server, this time may be longer than the SLI time because it includes webhook execution and wait times. +For a single resource (scope=resource, threshold=1s)
+histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"GET", scope=~"resource"}[5m])) by (resource, subresource, verb, scope, le))

+

For a collection of resources in the same namespace (scope=namespace, threshold=5s)
+histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"LIST", scope=~"namespace"}[5m])) by (resource, subresource, verb, scope, le))

+

For a collection of resources across the entire cluster (scope=cluster, threshold=30s)
+histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"LIST", scope=~"cluster"}[5m])) by (resource, subresource, verb, scope, le))

+

The SLI metrics provide insight into how Kubernetes components are performing by excluding the time that requests spend waiting in API Priority and Fairness queues, working through admission webhooks, or other Kubernetes extensions. The total metrics provide a more holistic view as it reflects the time your applications would be waiting for a response from the API server. Comparing these metrics can provide insight into where the delays in request processing are being introduced.

+

Pod Startup Latency

+ + + + + + + + + + + + + + + + + +
MetricDefinition
kubelet_pod_start_sli_duration_secondsDuration in seconds to start a pod, excluding time to pull images and run init containers, measured from pod creation timestamp to when all its containers are reported as started and observed via watch
kubelet_pod_start_duration_secondsDuration in seconds from kubelet seeing a pod for the first time to the pod starting to run. This does not include the time to schedule the pod or scale out worker node capacity.
+

Note: kubelet_pod_start_sli_duration_seconds is available starting in Kubernetes 1.27.

+

Similar to the queries above you can use these metrics to gain insight into how long node scaling, image pulls and init containers are delaying the pod launch compared to Kubelet actions.

+

Pod startup latency SLI - this is the time from the pod being created to when the application containers reported as running. This includes the time it takes for the worker node capacity to be available and the pod to be scheduled, but this does not include the time it takes to pull images or for the init containers to run.
+histogram_quantile(0.99, sum(rate(kubelet_pod_start_sli_duration_seconds_bucket[5m])) by (le))

+

Pod startup latency Total - this is the time it takes the kubelet to start the pod for the first time. This is measured from when the kubelet recieves the pod via WATCH, which does not include the time for worker node scaling or scheduling. This includes the time to pull images and init containers to run.
+histogram_quantile(0.99, sum(rate(kubelet_pod_start_duration_seconds_bucket[5m])) by (le))

+

SLOs on Your Cluster

+

If you are collecting the Prometheus metrics from the Kubernetes resources in your EKS cluster you can gain deeper insights into the performance of the Kubernetes control plane components.

+

The perf-tests repo includes Grafana dashboards that display the latencies and critical performance metrics for the cluster during tests. The perf-tests configuration leverages the kube-prometheus-stack, an open source project that comes configured to collect Kubernetes metrics, but you can also use Amazon Managed Prometheus and Amazon Managed Grafana.

+

If you are using the kube-prometheus-stack or similar Prometheus solution you can install the same dashboard to observe the SLOs on your cluster in real time.

+
    +
  1. You will first need to install the Prometheus Rules that are used in the dashboards with kubectl apply -f prometheus-rules.yaml. You can download a copy of the rules here: https://github.com/kubernetes/perf-tests/blob/master/clusterloader2/pkg/prometheus/manifests/prometheus-rules.yaml
      +
    1. Be sure to check the namespace in the file matches your environment
    2. +
    3. Verify that the labels match the prometheus.prometheusSpec.ruleSelector helm value if you are using kube-prometheus-stack
    4. +
    +
  2. +
  3. You can then install the dashboards in Grafana. The json dashboards and python scripts to generate them are available here: https://github.com/kubernetes/perf-tests/tree/master/clusterloader2/pkg/prometheus/manifests/dashboards
      +
    1. the slo.json dashboard displays the performance of the cluster in relation to the Kubernetes SLOs
    2. +
    +
  4. +
+

Consider that the SLOs are focused on the performance of the Kubernetes components in your clusters, but there are additional metrics you can review which provide different perspectives or insights in to your cluster. Kubernetes community projects like Kube-state-metrics can help you quickly analyze trends in your cluster. Most common plugins and drivers from the Kubernetes community also emit Prometheus metrics, allowing you to investigate things like autoscalers or custom schedulers.

+

The Observability Best Practices Guide has examples of other Kubernetes metrics you can use to gain further insight.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/node_efficiency/index.html b/scalability/docs/node_efficiency/index.html new file mode 100644 index 000000000..029329438 --- /dev/null +++ b/scalability/docs/node_efficiency/index.html @@ -0,0 +1,2540 @@ + + + + + + + + + + + + + + + + + + + + + + + Node efficiency and scaling - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Node and Workload Efficiency

+

Being efficient with our workloads and nodes reduces complexity/cost while increasing performance and scale. There are many factors to consider when planning this efficiency, and it’s easiest to think in terms of trade offs vs. one best practice setting for each feature. Let’s explore these tradeoffs in depth in the following section.

+

Node Selection

+

Using node sizes that are slightly larger (4-12xlarge) increases the available space that we have for running pods due to the fact it reduces the percentage of the node used for “overhead” such as DaemonSets and Reserves for system components. In the diagram below we see the difference between the usable space on a 2xlarge vs. a 8xlarge system with just a moderate number of DaemonSets.

+
+

Note

+

Since k8s scales horizontally as a general rule, for most applications it does not make sense to take the performance impact of NUMA sizes nodes, thus the recommendation of a range below that node size.

+
+

Node size

+

Large nodes sizes allow us to have a higher percentage of usable space per node. However, this model can be taken to to the extreme by packing the node with so many pods that it causes errors or saturates the node. Monitoring node saturation is key to successfully using larger node sizes.

+

Node selection is rarely a one-size-fits-all proposition. Often it is best to split workloads with dramatically different churn rates into different node groups. Small batch workloads with a high churn rate would be best served by the 4xlarge family of instances, while a large scale application such as Kafka which takes 8 vCPU and has a low churn rate would be better served by the 12xlarge family.

+

Churn rate

+
+

Tip

+

Another factor to consider with very large node sizes is since CGROUPS do not hide the total number of vCPU from the containerized application. Dynamic runtimes can often spawn an unintentional number of OS threads, creating latency that is difficult to troubleshoot. For these application CPU pinning is recommend. For a deeper exploration of topic please see the following video https://www.youtube.com/watch?v=NqtfDy_KAqg

+
+

Node Bin-packing

+

Kubernetes vs. Linux Rules

+

There are two sets of rules we need to be mindful of when dealing with workloads on Kubernetes. The rules of the Kubernetes Scheduler, which uses the request value to schedule pods on a node, and then what happens after the pod is scheduled, which is the realm of Linux, not Kubernetes.

+

After Kubernetes scheduler is finished, a new set of rules takes over, the Linux Completely Fair Scheduler (CFS). The key take away is that Linux CFS doesn’t have a the concept of a core. We will discuss why thinking in cores can lead to major problems with optimizing workloads for scale.

+

Thinking in Cores

+

The confusion starts because the Kubernetes scheduler does have the concept of cores. From a Kubernetes scheduler perspective if we looked at a node with 4 NGINX pods, each with a request of one core set, the node would look like this.

+

+

However, let’s do a thought experiment on how different this looks from a Linux CFS perspective. The most important thing to remember when using the Linux CFS system is: busy containers (CGROUPS) are the only containers that count toward the share system. In this case, only the first container is busy so it is allowed to use all 4 cores on the node.

+

+

Why does this matter? Let’s say we ran our performance testing in a development cluster where an NGINX application was the only busy container on that node. When we move the app to production, the following would happen: the NGINX application wants 4 vCPU of resources however, because all the other pods on the node are busy, our app’s performance is constrained.

+

+

This situation would lead us to add more containers unnecessarily because we were not allowing our applications scale to their “sweet spot“. Let's explore this important concept of a ”sweet spot“ in a bit more detail.

+

Application right sizing

+

Each application has a certain point where it can not take anymore traffic. Going above this point can increase processing times and even drop traffic when pushed well beyond this point. This is known as the application’s saturation point. To avoid scaling issues, we should attempt to scale the application before it reaches its saturation point. Let’s call this point the sweet spot.

+

The sweet spot

+

We need to test each of our applications to understand its sweet spot. There will be no universal guidance here as each application is different. During this testing we are trying to understand the best metric that shows our applications saturation point. Oftentimes, utilization metrics are used to indicate an application is saturated but this can quickly lead to scaling issues (We will explore this topic in detail in a later section). Once we have this “sweet spot“ we can use it to efficiently scale our workloads.

+

Conversely, what would happen if we scale up well before the sweet spot and created unnecessary pods? Let’s explore that in the next section.

+

Pod sprawl

+

To see how creating unnecessary pods could quickly get out of hand, let's look at the first example on the left. The correct vertical scale of this container takes up about two vCPUs worth of utilization when handling 100 requests a second. However, If we were to under-provision the requests value by setting requests to half a core, we would now need 4 pods for each one pods we actually needed. Exacerbating this problem further, if our HPA was set at the default of 50% CPU, those pods would scale half empty, creating an 8:1 ratio.

+

+

Scaling this problem up we can quickly see how this can get out of hand. A deployment of ten pods whose sweet spot was set incorrectly could quickly spiral to 80 pods and the additional infrastructure needed to run them.

+

+

Now that we understand the impact of not allowing applications to operate in their sweet spot, let’s return to the node level and ask why this difference between the Kubernetes scheduler and Linux CFS so important?

+

When scaling up and down with HPA, we can have a scenario where we have a lot of space to allocate more pods. This would be a bad decision because the node depicted on the left is already at 100% CPU utilization. In a unrealistic but theoretically possible scenario, we could have the other extreme where our node is completely full, yet our CPU utilization is zero.

+

+

Setting Requests

+

It would tempting to set the request at the “sweet spot” value for that application, however this would cause inefficiencies as pictured in the diagram below. Here we have set the request value to 2 vCPU, however the average utilization of these pods runs only 1 CPU most of the time. This setting would cause us to waste 50% of our CPU cycles, which would be unacceptable.

+

+

This bring us to the complex answer to problem. Container utilization cannot be thought of in a vacuum; one must take into account the other applications running on the node. In the following example containers that are bursty in nature are mixed in with two low CPU utilization containers that might be memory constrained. In this way we allow the containers to hit their sweet spot without taxing the node.

+

+

The important concept to take away from all this is that using Kubernetes scheduler concept of cores to understand Linux container performance can lead to poor decision making as they are not related.

+
+

Tip

+

Linux CFS has its strong points. This is especially true for I/O based workloads. However, if your application uses full cores without sidecars, and has no I/O requirements, CPU pinning can remove a great deal of complexity from this process and is encouraged with those caveats.

+
+

Utilization vs. Saturation

+

A common mistake in application scaling is only using CPU utilization for your scaling metric. In complex applications this is almost always a poor indicator that an application is actually saturated with requests. In the example on the left, we see all of our requests are actually hitting the web server, so CPU utilization is tracking well with saturation.

+

In real world applications, it’s likely that some of those requests will be getting serviced by a database layer or an authentication layer, etc. In this more common case, notice CPU is not tracking with saturation as the request is being serviced by other entities. In this case CPU is a very poor indicator for saturation.

+

+

Using the wrong metric in application performance is the number one reason for unnecessary and unpredictable scaling in Kubernetes. Great care must be taken in picking the correct saturation metric for the type of application that you're using. It is important to note that there is not a one size fits all recommendation that can be given. Depending on the language used and the type of application in question, there is a diverse set of metrics for saturation.

+

We might think this problem is only with CPU Utilization, however other common metrics such as request per second can also fall into the exact same problem as discussed above. Notice the request can also go to DB layers, auth layers, not being directly serviced by our web server, thus it’s a poor metric for true saturation of the web server itself.

+

+

Unfortunately there are no easy answers when it comes to picking the right saturation metric. Here are some guidelines to take into consideration:

+
    +
  • Understand your language runtime - languages with multiple OS threads will react differently than single threaded applications, thus impacting the node differently.
  • +
  • Understand the correct vertical scale - how much buffer do you want in your applications vertical scale before scaling a new pod?
  • +
  • What metrics truly reflect the saturation of your application - The saturation metric for a Kafka Producer would be quite different than a complex web application.
  • +
  • How do all the other applications on the node effect each other - Application performance is not done in a vacuum the other workloads on the node have a major impact.
  • +
+

To close out this section, it would be easy to dismiss the above as overly complex and unnecessary. It can often be the case that we are experiencing an issue but we are unaware of the true nature of the problem because we are looking at the wrong metrics. In the next section we will look at how that could happen.

+

Node Saturation

+

Now that we have explored application saturation, let’s look at this same concept from a node point of view. Let’s take two CPUs that are 100% utilized to see the difference between utilization vs. saturation.

+

The vCPU on the left is 100% utilized, however no other tasks are waiting to run on this vCPU, so in a purely theoretical sense, this is quite efficient. Meanwhile, we have 20 single threaded applications waiting to get processed by a vCPU in the second example. All 20 applications now will experience some type of latency while they're waiting their turn to be processed by the vCPU. In other words, the vCPU on the right is saturated.

+

Not only would we not see this problem if we where just looking at utilization, but we might attribute this latency to something unrelated such as networking which would lead us down the wrong path.

+

+

It is important to view saturation metrics, not just utilization metrics when increasing the total number of pods running on a node at any given time as we can easily miss the fact we have over-saturated a node. For this task we can use pressure stall information metrics as seen in the below chart.

+

PromQL - Stalled I/O

+
topk(3, ((irate(node_pressure_io_stalled_seconds_total[1m])) * 100))
+
+

+
+

Note

+

For more on Pressure stall metrics, see https://facebookmicrosites.github.io/psi/docs/overview*

+
+

With these metrics we can tell if threads are waiting on CPU, or even if every thread on the box is stalled waiting on resource like memory or I/O. For example, we could see what percentage every thread on the instance was stalled waiting on I/O over the period of 1 min.

+
topk(3, ((irate(node_pressure_io_stalled_seconds_total[1m])) * 100))
+
+

Using this metric, we can see in the above chart every thread on the box was stalled 45% of the time waiting on I/O at the high water mark, meaning we were throwing away all of those CPU cycles in that minute. Understanding that this is happening can help us reclaim a significant amount of vCPU time, thus making scaling more efficient.

+

HPA V2

+

It is recommended to use the autoscaling/v2 version of the HPA API. The older versions of the HPA API could get stuck scaling in certain edge cases. It was also limited to pods only doubling during each scaling step, which created issues for small deployments that needed to scale rapidly.

+

Autoscaling/v2 allows us more flexibility to include multiple criteria to scale on and allows us a great deal of flexibility when using custom and external metrics (non K8s metrics).

+

As an example, we can scaling on the highest of three values (see below). We scale if the average utilization of all the pods are over 50%, if custom metrics the packets per second of the ingress exceed an average of 1,000, or ingress object exceeds 10K request per second.

+
+

Note

+

This is just to show the flexibility of the auto-scaling API, we recommend against overly complex rules that can be difficult to troubleshoot in production.

+
+
apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+  name: php-apache
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: php-apache
+  minReplicas: 1
+  maxReplicas: 10
+  metrics:
+  - type: Resource
+    resource:
+      name: cpu
+      target:
+        type: Utilization
+        averageUtilization: 50
+  - type: Pods
+    pods:
+      metric:
+        name: packets-per-second
+      target:
+        type: AverageValue
+        averageValue: 1k
+  - type: Object
+    object:
+      metric:
+        name: requests-per-second
+      describedObject:
+        apiVersion: networking.k8s.io/v1
+        kind: Ingress
+        name: main-route
+      target:
+        type: Value
+        value: 10k
+
+

However, we learned the danger of using such metrics for complex web applications. In this case we would be better served by using custom or external metric that accurately reflects the saturation of our application vs. the utilization. HPAv2 allows for this by having the ability to scale according to any metric, however we still need to find and export that metric to Kubernetes for use.

+

For example, we can look at the active thread queue count in Apache. This often creates a “smoother” scaling profile (more on that term soon). If a thread is active, it doesn’t matter if that thread is waiting on a database layer or servicing a request locally, if all of the applications threads are being used, it’s a great indication that application is saturated.

+

We can use this thread exhaustion as a signal to create a new pod with a fully available thread pool. This also gives us control over how big a buffer we want in the application to absorb during times of heavy traffic. For example, if we had a total thread pool of 10, scaling at 4 threads used vs. 8 threads used would have a major impact on the buffer we have available when scaling the application. A setting of 4 would make sense for an application that needs to rapidly scale under heavy load, where a setting of 8 would be more efficient with our resources if we had plenty of time to scale due to the number of requests increasing slowly vs. sharply over time.

+

+

What do we mean by the term “smooth” when it comes to scaling? Notice the below chart where we are using CPU as a metric. The pods in this deployment are spiking in a short period for from 50 pods, all the way up to 250 pods only to immediately scale down again. This is highly inefficient scaling is the leading cause on churn on clusters.

+

+

Notice how after we change to a metric that reflects the correct sweet spot of our application (mid-part of chart), we are able to scale smoothly. Our scaling is now efficient, and our pods are allowed to fully scale with the headroom we provided by adjusting requests settings. Now a smaller group of pods are doing the work the hundreds of pods were doing before. Real world data shows that this is the number one factor in scalability of Kubernetes clusters.

+

+

The key takeaway is CPU utilization is only one dimension of both application and node performance. Using CPU utilization as a sole health indicator for our nodes and applications creates problems in scaling, performance and cost which are all tightly linked concepts. The more performant the application and nodes are, the less that you need to scale, which in turn lowers your costs.

+

Finding and using the correct saturation metrics for scaling your particular application also allows you to monitor and alarm on the true bottlenecks for that application. If this critical step is skipped, reports of performance problems will be difficult, if not impossible, to understand.

+

Setting CPU Limits

+

To round out this section on misunderstood topics, we will cover CPU limits. In short, limits are metadata associated with the container that has a counter that resets every 100ms. This helps Linux keep track of how many CPU resources are used node-wide by a specific container in a 100ms period of time.

+

CPU limits

+

A common error with setting limits is assuming that the application is single threaded and only running on it’s “assigned“ vCPU. In the above section we learned that CFS doesn’t assign cores, and in reality a container running large thread pools will schedule on all available vCPU’s on the box.

+

If 64 OS threads are running across 64 available cores (from a Linux node perspective) we will make the total bill of used CPU time in a 100ms period quite large after the time running on all of those 64 cores are added up. Since this might only occur during a garbage collection process it can be quite easy to miss something like this. This is why it is necessary to use metrics to ensure we have the correct usage over time before attempting to set a limit.

+

Fortunately, we have a way to see exactly how much vCPU is being used by all the threads in a application. We will use the metric container_cpu_usage_seconds_total for this purpose.

+

Since throttling logic happens every 100ms and this metric is a per second metric, we will PromQL to match this 100ms period. If you would like to dive deep into this PromQL statement work please see the following blog.

+

PromQL query:

+
topk(3, max by (pod, container)(rate(container_cpu_usage_seconds_total{image!="", instance="$instance"}[$__rate_interval]))) / 10
+
+

+

Once we feel we have the right value, we can put the limit in production. It then becomes necessary to see if our application is being throttled due to something unexpected. We can do this by looking at container_cpu_throttled_seconds_total

+
topk(3, max by (pod, container)(rate(container_cpu_cfs_throttled_seconds_total{image!=``""``, instance=``"$instance"``}[$__rate_interval]))) / 10
+
+

+

Memory

+

The memory allocation is another example where it is easy to confuse Kubernetes scheduling behavior for Linux CGroup behavior. This is a more nuanced topic as there have been major changes in the way that CGroup v2 handles memory in Linux and Kubernetes has changed its syntax to reflect this; read this blog for further details.

+

Unlike CPU requests, memory requests go unused after the scheduling process completes. This is because we can not compress memory in CGroup v1 the same way we can with CPU. That leaves us with just memory limits, which are designed to act as a fail safe for memory leaks by terminating the pod completely. This is an all or nothing style proposition, however we have now been given new ways to address this problem.

+

First, it is important to understand that setting the right amount of memory for containers is not a straightforward as it appears. The file system in Linux will use memory as a cache to improve performance. This cache will grow over time, and it can be hard to know how much memory is just nice to have for the cache but can be reclaimed without a significant impact to application performance. This often results in misinterpreting memory usage.

+

Having the ability to “compress” memory was one of the primary drivers behind CGroup v2. For more history on why CGroup V2 was necessary, please see Chris Down’s presentation at LISA21 where he covers why being unable to set the minimum memory correctly was one of the reasons that drove him to create CGroup v2 and pressure stall metrics.

+

Fortunately, Kubernetes now has the concept of memory.min and memory.high under requests.memory. This gives us the option of aggressive releasing this cached memory for other containers to use. Once the container hits the memory high limit, the kernel can aggressively reclaim that container’s memory up to the value set at memory.min. Thus giving us more flexibility when a node comes under memory pressure.

+

The key question becomes, what value to set memory.min to? This is where memory pressure stall metrics come into play. We can use these metrics to detect memory “thrashing” at a container level. Then we can use controllers such as fbtax to detect the correct values for memory.min by looking for this memory thrashing, and dynamically set the memory.min value to this setting.

+

Summary

+

To sum up the section, it is easy to conflate the following concepts:

+
    +
  • Utilization and Saturation
  • +
  • Linux performance rules with Kubernetes Scheduler logic
  • +
+

Great care must be taken to keep these concepts separated. Performance and scale are linked on a deep level. Unnecessary scaling creates performance problems, which in turn creates scaling problems.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/quotas/index.html b/scalability/docs/quotas/index.html new file mode 100644 index 000000000..6355c666c --- /dev/null +++ b/scalability/docs/quotas/index.html @@ -0,0 +1,2457 @@ + + + + + + + + + + + + + + + + + + + + + + + Known limits and service quotas - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Known Limits and Service Quotas

+

Amazon EKS can be used for a variety of workloads and can interact with a wide range of AWS services, and we have seen customer workloads encounter a similar range of AWS service quotas and other issues that hamper scalability.

+

Your AWS account has default quotas (an upper limit on the number of each AWS resource your team can request). Each AWS service defines their own quota, and quotas are generally region-specific. You can request increases for some quotas (soft limits), and other quotas cannot be increased (hard limits). You should consider these values when architecting your applications. Consider reviewing these service limits periodically and incorporate them during in your application design.

+

You can review the usage in your account and open a quota increase request at the AWS Service Quotas console, or using the AWS CLI. Refer to the AWS documentation from the respective AWS Service for more details on the Service Quotas and any further restrictions or notices on their increase.

+
+

Note

+

Amazon EKS Service Quotas lists the service quotas and has links to request increases where available.

+
+

Other AWS Service Quotas

+

We have seen EKS customers impacted by the quotas listed below for other AWS services. Some of these may only apply to specific use cases or configurations, however you may consider if your solution will encounter any of these as it scales. The Quotas are organized by Service and each Quota has an ID in the format of L-XXXXXXXX you can use to look it up in the AWS Service Quotas console

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ServiceQuota (L-xxxxx)ImpactID (L-xxxxx)default
IAMRoles per accountCan limit the number of clusters or IRSA roles in an account.L-FE177D641,000
IAMOpenId connect providers per accountCan limit the number of Clusters per account, OpenID Connect is used by IRSAL-858F3967100
IAMRole trust policy lengthCan limit the number of of clusters an IAM role is associated with for IRSAL-C07B4B0D2,048
VPCSecurity groups per network interfaceCan limit the control or connectivity of the networking for your clusterL-2AFB92585
VPCIPv4 CIDR blocks per VPCCan limit the number of EKS Worker NodesL-83CA0A9D5
VPCRoutes per route tableCan limit the control or connectivity of the networking for your clusterL-93826ACB50
VPCActive VPC peering connections per VPCCan limit the control or connectivity of the networking for your clusterL-7E9ECCDB50
VPCInbound or outbound rules per security group.Can limit the control or connectivity of the networking for your cluster, some controllers in EKS create new rulesL-0EA8095F50
VPCVPCs per RegionCan limit the number of Clusters per account or the control or connectivity of the networking for your clusterL-F678F1CE5
VPCInternet gateways per RegionCan limit the number of Clusters per account or the control or connectivity of the networking for your clusterL-A4707A725
VPCNetwork interfaces per RegionCan limit the number of EKS Worker nodes, or Impact EKS control plane scaling/update activities.L-DF5E4CA35,000
VPCNetwork Address UsageCan limit the number of Clusters per account or the control or connectivity of the networking for your clusterL-BB24F6E564,000
VPCPeered Network Address UsageCan limit the number of Clusters per account or the control or connectivity of the networking for your clusterL-CD17FD4B128,000
ELBListeners per Network Load BalancerCan limit the control of traffic ingress to the cluster.L-57A373D650
ELBTarget Groups per RegionCan limit the control of traffic ingress to the cluster.L-B22855CB3,000
ELBTargets per Application Load BalancerCan limit the control of traffic ingress to the cluster.L-7E6692B21,000
ELBTargets per Network Load BalancerCan limit the control of traffic ingress to the cluster.L-EEF1AD043,000
ELBTargets per Availability Zone per Network Load BalancerCan limit the control of traffic ingress to the cluster.L-B211E961500
ELBTargets per Target Group per RegionCan limit the control of traffic ingress to the cluster.L-A0D0B8631,000
ELBApplication Load Balancers per RegionCan limit the control of traffic ingress to the cluster.L-53DA6B9750
ELBClassic Load Balancers per RegionCan limit the control of traffic ingress to the cluster.L-E9E9831D20
ELBNetwork Load Balancers per RegionCan limit the control of traffic ingress to the cluster.L-69A177A250
EC2Running On-Demand Standard (A, C, D, H, I, M, R, T, Z) instances (as a maximum vCPU count)Can limit the number of EKS Worker NodesL-1216C47A5
EC2All Standard (A, C, D, H, I, M, R, T, Z) Spot Instance Requests (as a maximum vCPU count)Can limit the number of EKS Worker NodesL-34B43A085
EC2EC2-VPC Elastic IPsCan limit the number of NAT GWs (and thus VPCs), which may limit the number of clusters in a regionL-0263D0A35
EBSSnapshots per RegionCan limit the backup strategy for stateful workloadsL-309BACF6100,000
EBSStorage for General Purpose SSD (gp3) volumes, in TiBCan limit the number of EKS Worker Nodes, or PersistentVolume storageL-7A658B7650
EBSStorage for General Purpose SSD (gp2) volumes, in TiBCan limit the number of EKS Worker Nodes, or PersistentVolume storageL-D18FCD1D50
ECRRegistered repositoriesCan limit the number of workloads in your clustersL-CFEB8E8D10,000
ECRImages per repositoryCan limit the number of workloads in your clustersL-03A36CE110,000
SecretsManagerSecrets per RegionCan limit the number of workloads in your clustersL-2F66C23C500,000
+

AWS Request Throttling

+

AWS services also implement request throttling to ensure that they remain performant and available for all customers. Simliar to Service Quotas, each AWS service maintains their own request throttling thresholds. Consider reviewing the respective AWS Service documentation if your workloads will need to quickly issue a large number of API calls or if you notice request throttling errors in your application.

+

EC2 API requests around provisioning EC2 network interfaces or IP addresses can encounter request throttling in large clusters or when clusters scale drastically. The table below shows some of the API actions that we have seen customers encounter request throttling from. +You can review the EC2 rate limit defaults and the steps to request a rate limit increase in the EC2 documentation on Rate Throttling.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Mutating ActionsRead-only Actions
AssignPrivateIpAddressesDescribeDhcpOptions
AttachNetworkInterfaceDescribeInstances
CreateNetworkInterfaceDescribeNetworkInterfaces
DeleteNetworkInterfaceDescribeSecurityGroups
DeleteTagsDescribeTags
DetachNetworkInterfaceDescribeVpcs
ModifyNetworkInterfaceAttributeDescribeVolumes
UnassignPrivateIpAddresses
+

Other Known Limits

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/scaling_theory/index.html b/scalability/docs/scaling_theory/index.html new file mode 100644 index 000000000..781fb0e87 --- /dev/null +++ b/scalability/docs/scaling_theory/index.html @@ -0,0 +1,2382 @@ + + + + + + + + + + + + + + + + + + + + + + + The theory behind scaling - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Kubernetes Scaling Theory

+

Nodes vs. Churn Rate

+

Often when we discuss the scalability of Kubernetes, we do so in terms of how many nodes there are in a single cluster. Interestingly, this is seldom the most useful metric for understanding scalability. For example, a 5,000 node cluster with a large but fixed number of pods would not put a great deal of stress on the control plane after the initial setup. However, if we took a 1,000 node cluster and tried creating 10,000 short lived jobs in less than a minute, it would put a great deal of sustained pressure on the control plane.

+

Simply using the number of nodes to understand scaling can be misleading. It’s better to think in terms of the rate of change that occurs within a specific period of time (let’s use a 5 minute interval for this discussion, as this is what Prometheus queries typically use by default). Let’s explore why framing the problem in terms of the rate of change can give us a better idea of what to tune to achieve our desired scale.

+

Thinking in Queries Per Second

+

Kubernetes has a number of protection mechanisms for each component - the Kubelet, Scheduler, Kube Controller Manager, and API server - to prevent overwhelming the next link in the Kubernetes chain. For example, the Kubelet has a flag to throttle calls to the API server at a certain rate. These protection mechanisms are generally, but not always, expressed in terms of queries allowed on a per second basis or QPS.

+

Great care must be taken when changing these QPS settings. Removing one bottleneck, such as the queries per second on a Kubelet will have an impact on other down stream components. This can and will overwhelm the system above a certain rate, so understanding and monitoring each part of the service chain is key to successfully scaling workloads on Kubernetes.

+
+

Note

+

The API server has a more complex system with introduction of API Priority and Fairness which we will discuss separately.

+
+
+

Note

+

Caution, some metrics seem like the right fit but are in fact measuring something else. As an example, kubelet_http_inflight_requests relates to just the metrics server in Kubelet, not the number of requests from Kubelet to apiserver requests. This could cause us to misconfigure the QPS flag on the Kubelet. A query on audit logs for a particular Kubelet would be a more reliable way to check metrics.

+
+

Scaling Distributed Components

+

Since EKS is a managed service, let’s split the Kubernetes components into two categories: AWS managed components which include etcd, Kube Controller Manager, and the Scheduler (on the left part of diagram), and customer configurable components such as the Kubelet, Container Runtime, and the various operators that call AWS APIs such as the Networking and Storage drivers (on the right part of diagram). We leave the API server in the middle even though it is AWS managed, as the settings for API Priority and Fairness can be configured by customers.

+

Kubernetes components

+

Upstream and Downstream Bottlenecks

+

As we monitor each service, it’s important to look at metrics in both directions to look for bottlenecks. Let’s learn how to do this by using Kubelet as an example. Kubelet talks both to the API server and the container runtime; how and what do we need to monitor to detect whether either component is experiencing an issue?

+

How many Pods per Node

+

When we look at scaling numbers, such as how many pods can run on a node, we could take the 110 pods per node that upstream supports at face value.

+
+

Note

+

https://kubernetes.io/docs/setup/best-practices/cluster-large/

+
+

However, your workload is likely more complex than what was tested in a scalability test in Upstream. To ensure we can service the number of pods we want to run in production, let’s make sure that the Kubelet is “keeping up” with the Containerd runtime.

+

Keeping up

+

To oversimplify, the Kubelet is getting the status of the pods from the container runtime (in our case Containerd). What if we had too many pods changing status too quickly? If the rate of change is too high, requests [to the container runtime] can timeout.

+
+

Note

+

Kubernetes is constantly evolving, this subsystem is currently undergoing changes. https://github.com/kubernetes/enhancements/issues/3386

+
+

Flow +PLEG duration

+

In the graph above, we see a flat line indicating we have just hit the timeout value for the pod lifecycle event generation duration metric. If you would like to see this in your own cluster you could use the following PromQL syntax.

+
increase(kubelet_pleg_relist_duration_seconds_bucket{instance="$instance"}[$__rate_interval])
+
+

If we witness this timeout behavior, we know we pushed the node over the limit it was capable of. We need to fix the cause of the timeout before proceeding further. This could be achieved by reducing the number of pods per node, or looking for errors that might be causing a high volume of retries (thus effecting the churn rate). The important take-away is that metrics are the best way to understand if a node is able to handle the churn rate of the pods assigned vs. using a fixed number.

+

Scale by Metrics

+

While the concept of using metrics to optimize systems is an old one, it’s often overlooked as people begin their Kubernetes journey. Instead of focusing on specific numbers (i.e. 110 pods per node), we focus our efforts on finding the metrics that help us find bottlenecks in our system. Understanding the right thresholds for these metrics can give us a high degree of confidence our system is optimally configured.

+

The Impact of Changes

+

A common pattern that could get us into trouble is focusing on the first metric or log error that looks suspect. When we saw that the Kubelet was timing out earlier, we could try random things, such as increasing the per second rate that the Kubelet is allowed to send, etc. However, it is wise to look at the whole picture of everything downstream of the error we find first. Make each change with purpose and backed by data.

+

Downstream of the Kubelet would be the Containerd runtime (pod errors), DaemonSets such as the storage driver (CSI) and the network driver (CNI) that talk to the EC2 API, etc.

+

Flow add-ons

+

Let’s continue our earlier example of the Kubelet not keeping up with the runtime. There are a number of points where we could bin pack a node so densely that it triggers errors.

+

Bottlenecks

+

When designing the right node size for our workloads these are easy-to-overlook signals that might be putting unnecessary pressure on the system thus limiting both our scale and performance.

+

The Cost of Unnecessary Errors

+

Kubernetes controllers excel at retrying when error conditions arise, however this comes at a cost. These retries can increase the pressure on components such as the Kube Controller Manager. It is an important tenant of scale testing to monitor for such errors.

+

When fewer errors are occurring, it is easier spot issues in the system. By periodically ensuring that our clusters are error free before major operations (such as upgrades) we can simplify troubleshooting logs when unforeseen events happen.

+

Expanding Our View

+

In large scale clusters with 1,000’s of nodes we don’t want to look for bottlenecks individually. In PromQL we can find the highest values in a data set using a function called topk; K being a variable we place the number of items we want. Here we use three nodes to get an idea whether all of the Kubelets in the cluster are saturated. We have been looking at latency up to this point, now let’s see if the Kubelet is discarding events.

+
topk(3, increase(kubelet_pleg_discard_events{}[$__rate_interval]))
+
+

Breaking this statement down.

+
    +
  • We use the Grafana variable $__rate_interval to ensure it gets the four samples it needs. This bypasses a complex topic in monitoring with a simple variable.
  • +
  • topk give us just the top results and the number 3 limits those results to three. This is a useful function for cluster wide metrics.
  • +
  • {} tell us there are no filters, normally you would put the job name of whatever the scraping rule, however since these names vary we will leave it blank.
  • +
+

Splitting the Problem in Half

+

To address a bottleneck in the system, we will take the approach of finding a metric that shows us there is a problem upstream or downstream as this allows us to split the problem in half. It will also be a core tenet of how we display our metrics data.

+

A good place to start with this process is the API server, as it allow us to see if there’s a problem with a client application or the Control Plane.

+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/docs/workloads/index.html b/scalability/docs/workloads/index.html new file mode 100644 index 000000000..36653a7c3 --- /dev/null +++ b/scalability/docs/workloads/index.html @@ -0,0 +1,2351 @@ + + + + + + + + + + + + + + + + + + + + + + + Workloads - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Workloads

+

Workloads have an impact on how large your cluster can scale. Workloads that use the Kubernetes APIs heavily will limit the total amount of workloads you can have in a single cluster, but there are some defaults you can change to help reduce the load.

+

Workloads in a Kubernetes cluster have access to features that integrate with the Kubernetes API (e.g. Secrets and ServiceAccounts), but these features are not always required and should be disabled if they’re not being used. Limiting workload access and dependence on the Kubernetes control plane will increase the number of workloads you can run in the cluster and improve the security of your clusters by removing unnecessary access to workloads and implementing least privilege practices. Please read the security best practices for more information.

+

Use IPv6 for pod networking

+

You cannot transition a VPC from IPv4 to IPv6 so enabling IPv6 before provisioning a cluster is important. If you enable IPv6 in a VPC it does not mean you have to use it and if your pods and services use IPv6 you can still route traffic to and from IPv4 addresses. Please see the EKS networking best practices for more information.

+

Using IPv6 in your cluster avoids some of the most common cluster and workload scaling limits. IPv6 avoids IP address exhaustion where pods and nodes cannot be created because no IP address is available. It also has per node performance improvements because pods receive IP addresses faster by reducing the number of ENI attachments per node. You can achieve similar node performance by using IPv4 prefix mode in the VPC CNI, but you still need to make sure you have enough IP addresses available in the VPC.

+

Limit number of services per namespace

+

The maximum number of services in a namespaces is 5,000 and the maximum number of services in a cluster is 10,000. To help organize workloads and services, increase performance, and to avoid cascading impact for namespace scoped resources we recommend limiting the number of services per namespace to 500.

+

The number of IP tables rules that are created per node with kube-proxy grows with the total number of services in the cluster. Generating thousands of IP tables rules and routing packets through those rules have a performance impact on the nodes and add network latency.

+

Create Kubernetes namespaces that encompass a single application environment so long as the number of services per namespace is under 500. This will keep service discovery small enough to avoid service discovery limits and can also help you avoid service naming collisions. Applications environments (e.g. dev, test, prod) should use separate EKS clusters instead of namespaces.

+

Understand Elastic Load Balancer Quotas

+

When creating your services consider what type of load balancing you will use (e.g. Network Load Balancer (NLB) or Application Load Balancer (ALB)). Each load balancer type provides different functionality and have different quotas. Some of the default quotas can be adjusted, but there are some quota maximums which cannot be changed. To view your account quotas and usage view the Service Quotas dashboard in the AWS console.

+

For example, the default ALB targets is 1000. If you have a service with more than 1000 endpoints you will need to increase the quota or split the service across multiple ALBs or use Kubernetes Ingress. The default NLB targets is 3000, but is limited to 500 targets per AZ. If your cluster runs more than 500 pods for an NLB service you will need to use multiple AZs or request a quota limit increase.

+

An alternative to using a load balancer coupled to a service is to use an ingress controller. The AWS Load Balancer controller can create ALBs for ingress resources, but you may consider running a dedicated controller in your cluster. An in-cluster ingress controller allows you to expose multiple Kubernetes services from a single load balancer by running a reverse proxy inside your cluster. Controllers have different features such as support for the Gateway API which may have benefits depending on how many and how large your workloads are.

+

Use Route 53, Global Accelerator, or CloudFront

+

To make a service using multiple load balancers available as a single endpoint you need to use Amazon CloudFront, AWS Global Accelerator, or Amazon Route 53 to expose all of the load balancers as a single, customer facing endpoint. Each options has different benefits and can be used separately or together depending on your needs.

+

Route 53 can expose multiple load balancers under a common name and can send traffic to each of them based on the weight assigned. You can read more about DNS weights in the documentation and you can read how to implement them with the Kubernetes external DNS controller in the AWS Load Balancer Controller documentation.

+

Global Accelerator can route workloads to the nearest region based on request IP address. This may be useful for workloads that are deployed to multiple regions, but it does not improve routing to a single cluster in a single region. Using Route 53 in combination with the Global Accelerator has additional benefits such as health checking and automatic failover if an AZ is not available. You can see an example of using Global Accelerator with Route 53 in this blog post.

+

CloudFront can be use with Route 53 and Global Accelerator or by itself to route traffic to multiple destinations. CloudFront caches assets being served from the origin sources which may reduce bandwidth requirements depending on what you are serving.

+

Use EndpointSlices instead of Endpoints

+

When discovering pods that match a service label you should use EndpointSlices instead of Endpoints. Endpoints were a simple way to expose services at small scales but large services that automatically scale or have updates causes a lot of traffic on the Kubernetes control plane. EndpointSlices have automatic grouping which enable things like topology aware hints.

+

Not all controllers use EndpointSlices by default. You should verify your controller settings and enable it if needed. For the AWS Load Balancer Controller you should enable the --enable-endpoint-slices optional flag to use EndpointSlices.

+

Use immutable and external secrets if possible

+

The kubelet keeps a cache of the current keys and values for the Secrets that are used in volumes for pods on that node. The kubelet sets a watch on the Secrets to detect changes. As the cluster scales, the growing number of watches can negatively impact the API server performance.

+

There are two strategies to reduce the number of watches on Secrets:

+
    +
  • For applications that don’t need access to Kubernetes resources, you can disable auto-mounting service account secrets by setting automountServiceAccountToken: false
  • +
  • If your application’s secrets are static and will not be modified in the future, mark the secret as immutable. The kubelet does not maintain an API watch for immutable secrets.
  • +
+

To disable automatically mounting a service account to pods you can use the following setting in your workload. You can override these settings if specific workloads need a service account.

+
apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: app
+automountServiceAccountToken: true
+
+

Monitor the number of secrets in the cluster before it exceeds the limit of 10,000. You can see a total count of secrets in a cluster with the following command. You should monitor this limit through your cluster monitoring tooling.

+
kubectl get secrets -A | wc -l
+
+

You should set up monitoring to alert a cluster admin before this limit is reached. Consider using external secrets management options such as AWS Key Management Service (AWS KMS) or Hashicorp Vault with the Secrets Store CSI driver.

+

Limit Deployment history

+

Pods can be slow when creating, updating, or deleting because old objects are still tracked in the cluster. You can reduce the revisionHistoryLimit of deployments to cleanup older ReplicaSets which will lower to total amount of objects tracked by the Kubernetes Controller Manager. The default history limit for Deployments in 10.

+

If your cluster creates a lot of job objects through CronJobs or other mechanisms you should use the ttlSecondsAfterFinished setting to automatically clean up old pods in the cluster. This will remove successfully executed jobs from the job history after a specified amount of time.

+ +

When a Pod runs on a Node, the kubelet adds a set of environment variables for each active Service. Linux processes have a maximum size for their environment which can be reached if you have too many services in your namespace. The number of services per namespace should not exceed 5,000. After this, the number of service environment variables outgrows shell limits, causing Pods to crash on startup.

+

There are other reasons pods should not use service environment variables for service discovery. Environment variable name clashes, leaking service names, and total environment size are a few. You should use CoreDNS for discovering service endpoints.

+

Limit dynamic admission webhooks per resource

+

Dynamic Admission Webhooks include admission webhooks and mutating webhooks. They are API endpoints not part of the Kubernetes Control Plane that are called in sequence when a resource is sent to the Kubernetes API. Each webhook has a default timeout of 10 seconds and can increase the amount of time an API request takes if you have multiple webhooks or any of them timeout.

+

Make sure your webhooks are highly available—especially during an AZ incident—and the failurePolicy is set properly to reject the resource or ignore the failure. Do not call webhooks when not needed by allowing --dry-run kubectl commands to bypass the webhook.

+
apiVersion: admission.k8s.io/v1
+kind: AdmissionReview
+request:
+  dryRun: False
+
+

Mutating webhooks can modify resources in frequent succession. If you have 5 mutating webhooks and deploy 50 resources etcd will store all versions of each resource until compaction runs—every 5 minutes—to remove old versions of modified resources. In this scenario when etcd removes superseded resources there will be 200 resource version removed from etcd and depending on the size of the resources may use considerable space on the etcd host until defragmentation runs every 15 minutes.

+

This defragmentation may cause pauses in etcd which could have other affects on the Kubernetes API and controllers. You should avoid frequent modification of large resources or modifying hundreds of resources in quick succession.

+

Compare workloads across multiple clusters

+

If you have two clusters that should have similar performance but do not, try comparing the metrics to identify the reason.

+

For example, comparing cluster latency is a common issue. This is usually caused by difference in the volume of API requests. You can run the following CloudWatch LogInsight query to understand the difference.

+
filter @logStream like "kube-apiserver-audit"
+| stats count(*) as cnt by objectRef.apiGroup, objectRef.apiVersion, objectRef.resource, userAgent, verb, responseStatus.code
+| sort cnt desc
+| limit 1000
+
+

You can add additional filters to narrow it down. e.g. focusing on all list request from foo.

+
filter @logStream like "kube-apiserver-audit"
+| filter verb = "list"
+| filter user.username like "foo"
+| stats count(*) as cnt by objectRef.apiGroup, objectRef.apiVersion, objectRef.resource, responseStatus.code
+| sort cnt desc
+| limit 1000
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/scalability/images/APF.jpg b/scalability/images/APF.jpg new file mode 100644 index 000000000..c347bb8c5 Binary files /dev/null and b/scalability/images/APF.jpg differ diff --git a/scalability/images/PLEG-duration.png b/scalability/images/PLEG-duration.png new file mode 100644 index 000000000..b60a6bdee Binary files /dev/null and b/scalability/images/PLEG-duration.png differ diff --git a/scalability/images/api-request-duration.png b/scalability/images/api-request-duration.png new file mode 100644 index 000000000..146775e4c Binary files /dev/null and b/scalability/images/api-request-duration.png differ diff --git a/scalability/images/bad-sweetspot.png b/scalability/images/bad-sweetspot.png new file mode 100644 index 000000000..7ac7b21f2 Binary files /dev/null and b/scalability/images/bad-sweetspot.png differ diff --git a/scalability/images/bottlenecks.png b/scalability/images/bottlenecks.png new file mode 100644 index 000000000..63f811f4e Binary files /dev/null and b/scalability/images/bottlenecks.png differ diff --git a/scalability/images/churn-rate.png b/scalability/images/churn-rate.png new file mode 100644 index 000000000..cce2eda83 Binary files /dev/null and b/scalability/images/churn-rate.png differ diff --git a/scalability/images/cores-1.png b/scalability/images/cores-1.png new file mode 100644 index 000000000..5423516a9 Binary files /dev/null and b/scalability/images/cores-1.png differ diff --git a/scalability/images/cores-2.png b/scalability/images/cores-2.png new file mode 100644 index 000000000..2a2d8cfb7 Binary files /dev/null and b/scalability/images/cores-2.png differ diff --git a/scalability/images/cores-3.png b/scalability/images/cores-3.png new file mode 100644 index 000000000..8d894f38b Binary files /dev/null and b/scalability/images/cores-3.png differ diff --git a/scalability/images/cpu-1.png b/scalability/images/cpu-1.png new file mode 100644 index 000000000..06081668c Binary files /dev/null and b/scalability/images/cpu-1.png differ diff --git a/scalability/images/cpu-2.png b/scalability/images/cpu-2.png new file mode 100644 index 000000000..9a52b22ab Binary files /dev/null and b/scalability/images/cpu-2.png differ diff --git a/scalability/images/cpu-limits.png b/scalability/images/cpu-limits.png new file mode 100644 index 000000000..aab5e7aa1 Binary files /dev/null and b/scalability/images/cpu-limits.png differ diff --git a/scalability/images/cwl-query.png b/scalability/images/cwl-query.png new file mode 100644 index 000000000..edaa4f358 Binary files /dev/null and b/scalability/images/cwl-query.png differ diff --git a/scalability/images/defrag.png b/scalability/images/defrag.png new file mode 100644 index 000000000..d870c49a8 Binary files /dev/null and b/scalability/images/defrag.png differ diff --git a/scalability/images/etcd-duress.png b/scalability/images/etcd-duress.png new file mode 100644 index 000000000..4b57ca065 Binary files /dev/null and b/scalability/images/etcd-duress.png differ diff --git a/scalability/images/flow-addons.png b/scalability/images/flow-addons.png new file mode 100644 index 000000000..fc3dc046c Binary files /dev/null and b/scalability/images/flow-addons.png differ diff --git a/scalability/images/flow.png b/scalability/images/flow.png new file mode 100644 index 000000000..3ebf93d23 Binary files /dev/null and b/scalability/images/flow.png differ diff --git a/scalability/images/hpa-utilization.png b/scalability/images/hpa-utilization.png new file mode 100644 index 000000000..a77801c5c Binary files /dev/null and b/scalability/images/hpa-utilization.png differ diff --git a/scalability/images/inflight-requests.png b/scalability/images/inflight-requests.png new file mode 100644 index 000000000..e894410b6 Binary files /dev/null and b/scalability/images/inflight-requests.png differ diff --git a/scalability/images/k8s-components.png b/scalability/images/k8s-components.png new file mode 100644 index 000000000..fc3dc046c Binary files /dev/null and b/scalability/images/k8s-components.png differ diff --git a/scalability/images/keeping-up.png b/scalability/images/keeping-up.png new file mode 100644 index 000000000..1a6957f3e Binary files /dev/null and b/scalability/images/keeping-up.png differ diff --git a/scalability/images/node-saturation.png b/scalability/images/node-saturation.png new file mode 100644 index 000000000..17a0c0e86 Binary files /dev/null and b/scalability/images/node-saturation.png differ diff --git a/scalability/images/node-size.png b/scalability/images/node-size.png new file mode 100644 index 000000000..2821f5a78 Binary files /dev/null and b/scalability/images/node-size.png differ diff --git a/scalability/images/query-results.png b/scalability/images/query-results.png new file mode 100644 index 000000000..12c0195f5 Binary files /dev/null and b/scalability/images/query-results.png differ diff --git a/scalability/images/queues.png b/scalability/images/queues.png new file mode 100644 index 000000000..ce5d834ff Binary files /dev/null and b/scalability/images/queues.png differ diff --git a/scalability/images/requests-1.png b/scalability/images/requests-1.png new file mode 100644 index 000000000..d58510396 Binary files /dev/null and b/scalability/images/requests-1.png differ diff --git a/scalability/images/requests-2.png b/scalability/images/requests-2.png new file mode 100644 index 000000000..c74dd3e4b Binary files /dev/null and b/scalability/images/requests-2.png differ diff --git a/scalability/images/requests-in-use.png b/scalability/images/requests-in-use.png new file mode 100644 index 000000000..bee27bd15 Binary files /dev/null and b/scalability/images/requests-in-use.png differ diff --git a/scalability/images/scaling-ratio.png b/scalability/images/scaling-ratio.png new file mode 100644 index 000000000..ca9129b4b Binary files /dev/null and b/scalability/images/scaling-ratio.png differ diff --git a/scalability/images/shared-concurrency.png b/scalability/images/shared-concurrency.png new file mode 100644 index 000000000..bcf326361 Binary files /dev/null and b/scalability/images/shared-concurrency.png differ diff --git a/scalability/images/slowest-requests.png b/scalability/images/slowest-requests.png new file mode 100644 index 000000000..682f9f03d Binary files /dev/null and b/scalability/images/slowest-requests.png differ diff --git a/scalability/images/smooth-scaling.png b/scalability/images/smooth-scaling.png new file mode 100644 index 000000000..1d49e49ee Binary files /dev/null and b/scalability/images/smooth-scaling.png differ diff --git a/scalability/images/spiky-scaling.png b/scalability/images/spiky-scaling.png new file mode 100644 index 000000000..1295090c5 Binary files /dev/null and b/scalability/images/spiky-scaling.png differ diff --git a/scalability/images/stalled-io.png b/scalability/images/stalled-io.png new file mode 100644 index 000000000..5cb38d319 Binary files /dev/null and b/scalability/images/stalled-io.png differ diff --git a/scalability/images/sweet-spot.png b/scalability/images/sweet-spot.png new file mode 100644 index 000000000..23ec38567 Binary files /dev/null and b/scalability/images/sweet-spot.png differ diff --git a/scalability/images/thread-pool.png b/scalability/images/thread-pool.png new file mode 100644 index 000000000..5ead69425 Binary files /dev/null and b/scalability/images/thread-pool.png differ diff --git a/scalability/images/util-vs-saturation-1.png b/scalability/images/util-vs-saturation-1.png new file mode 100644 index 000000000..936fd3ed9 Binary files /dev/null and b/scalability/images/util-vs-saturation-1.png differ diff --git a/scalability/images/util-vs-saturation-2.png b/scalability/images/util-vs-saturation-2.png new file mode 100644 index 000000000..17799c9f6 Binary files /dev/null and b/scalability/images/util-vs-saturation-2.png differ diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 000000000..fc4e51c64 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en","ko"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Introduction","text":"

Welcome to the EKS Best Practices Guides. The primary goal of this project is to offer a set of best practices for day 2 operations for Amazon EKS. We elected to publish this guidance to GitHub so we could iterate quickly, provide timely and effective recommendations for variety of concerns, and easily incorporate suggestions from the broader community.

We currently have published guides for the following topics:

  • Best Practices for Security
  • Best Practices for Reliability
  • Best Practices for Cluster Autoscaling: karpenter, cluster-autoscaler
  • Best Practices for Networking
  • Best Practices for Scalability
  • Best Practices for Cluster Upgrades
  • Best Practices for Cost Optimization
  • Best Practices for Running Windows Containers

We also open sourced a Python based CLI (Command Line Interface) called hardeneks to check some of the recommendations from this guide.

In the future we will be publishing best practices guidance for performance, cost optimization, and operational excellence.

"},{"location":"#related-guides","title":"Related guides","text":"

In addition to the EKS User Guide, AWS has published several other guides that may help you with your implementation of EKS.

  • EMR Containers Best Practices Guides
  • Data on EKS
  • AWS Observability Best Practices
  • Amazon EKS Blueprints for Terraform
  • Amazon EKS Blueprints Quick Start
"},{"location":"#contributing","title":"Contributing","text":"

We encourage you to contribute to these guides. If you have implemented a practice that has proven to be effective, please share it with us by opening an issue or a pull request. Similarly, if you discover an error or flaw in the guidance we've already published, please submit a PR to correct it. The guidelines for submitting PRs can be found in our Contributing Guidelines.

"},{"location":"cluster-autoscaling/","title":"Kubernetes Cluster Autoscaler","text":""},{"location":"cluster-autoscaling/#overview","title":"Overview","text":"

The Kubernetes Cluster Autoscaler is a popular Cluster Autoscaling solution maintained by SIG Autoscaling. It is responsible for ensuring that your cluster has enough nodes to schedule your pods without wasting resources. It watches for pods that fail to schedule and for nodes that are underutilized. It then simulates the addition or removal of nodes before applying the change to your cluster. The AWS Cloud Provider implementation within Cluster Autoscaler controls the .DesiredReplicas field of your EC2 Auto Scaling Groups.

This guide will provide a mental model for configuring the Cluster Autoscaler and choosing the best set of tradeoffs to meet your organization\u2019s requirements. While there is no single best configuration, there are a set of configuration options that enable you to trade off performance, scalability, cost, and availability. Additionally, this guide will provide tips and best practices for optimizing your configuration for AWS.

"},{"location":"cluster-autoscaling/#glossary","title":"Glossary","text":"

The following terminology will be used frequently throughout this document. These terms can have broad meaning, but are limited to the definitions below for the purposes of this document.

Scalability refers to how well the Cluster Autoscaler performs as your Kubernetes Cluster increases in number of pods and nodes. As scalability limits are reached, the Cluster Autoscaler\u2019s performance and functionality degrades. As the Cluster Autoscaler exceeds its scalability limits, it may no longer add or remove nodes in your cluster.

Performance refers to how quickly the Cluster Autoscaler is able to make and execute scaling decisions. A perfectly performing Cluster Autoscaler would instantly make a decision and trigger a scaling action in response to stimuli, such as a pod becoming unschedulable.

Availability means that pods can be scheduled quickly and without disruption. This includes when newly created pods need to be scheduled and when a scaled down node terminates any remaining pods scheduled to it.

Cost is determined by the decision behind scale out and scale in events. Resources are wasted if an existing node is underutilized or a new node is added that is too large for incoming pods. Depending on the use case, there can be costs associated with prematurely terminating pods due to an aggressive scale down decision.

Node Groups are an abstract Kubernetes concept for a group of nodes within a cluster. It is not a true Kubernetes resource, but exists as an abstraction in the Cluster Autoscaler, Cluster API, and other components. Nodes within a Node Group share properties like labels and taints, but may consist of multiple Availability Zones or Instance Types.

EC2 Auto Scaling Groups can be used as an implementation of Node Groups on EC2. EC2 Auto Scaling Groups are configured to launch instances that automatically join their Kubernetes Clusters and apply labels and taints to their corresponding Node resource in the Kubernetes API.

EC2 Managed Node Groups are another implementation of Node Groups on EC2. They abstract away the complexity manually configuring EC2 Autoscaling Scaling Groups and provide additional management features like node version upgrade and graceful node termination.

"},{"location":"cluster-autoscaling/#operating-the-cluster-autoscaler","title":"Operating the Cluster Autoscaler","text":"

The Cluster Autoscaler is typically installed as a Deployment in your cluster. It uses leader election to ensure high availability, but work is done by a single replica at a time. It is not horizontally scalable. For basic setups, the default it should work out of the box using the provided installation instructions, but there are a few things to keep in mind.

Ensure that:

  • The Cluster Autoscaler\u2019s version matches the Cluster\u2019s Version. Cross version compatibility is not tested or supported.
  • Auto Discovery is enabled, unless you have specific advanced use cases that prevent use of this mode.
"},{"location":"cluster-autoscaling/#employ-least-privileged-access-to-the-iam-role","title":"Employ least privileged access to the IAM role","text":"

When the Auto Discovery is used, we strongly recommend that you employ least privilege access by limiting Actions autoscaling:SetDesiredCapacity and autoscaling:TerminateInstanceInAutoScalingGroup to the Auto Scaling groups that are scoped to the current cluster.

This will prevents a Cluster Autoscaler running in one cluster from modifying nodegroups in a different cluster even if the --node-group-auto-discovery argument wasn't scoped down to the nodegroups of the cluster using tags (for example k8s.io/cluster-autoscaler/<cluster-name>).

{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"autoscaling:SetDesiredCapacity\",\n                \"autoscaling:TerminateInstanceInAutoScalingGroup\"\n            ],\n            \"Resource\": \"*\",\n            \"Condition\": {\n                \"StringEquals\": {\n                    \"aws:ResourceTag/k8s.io/cluster-autoscaler/enabled\": \"true\",\n                    \"aws:ResourceTag/k8s.io/cluster-autoscaler/<my-cluster>\": \"owned\"\n                }\n            }\n        },\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"autoscaling:DescribeAutoScalingGroups\",\n                \"autoscaling:DescribeAutoScalingInstances\",\n                \"autoscaling:DescribeLaunchConfigurations\",\n                \"autoscaling:DescribeScalingActivities\",\n                \"autoscaling:DescribeTags\",\n                \"ec2:DescribeImages\",\n                \"ec2:DescribeInstanceTypes\",\n                \"ec2:DescribeLaunchTemplateVersions\",\n                \"ec2:GetInstanceTypesFromInstanceRequirements\",\n                \"eks:DescribeNodegroup\"\n            ],\n            \"Resource\": \"*\"\n        }\n    ]\n}\n
"},{"location":"cluster-autoscaling/#configuring-your-node-groups","title":"Configuring your Node Groups","text":"

Effective autoscaling starts with correctly configuring a set of Node Groups for your cluster. Selecting the right set of Node Groups is key to maximizing availability and reducing cost across your workloads. AWS implements Node Groups using EC2 Auto Scaling Groups, which are flexible to a large number of use cases. However, the Cluster Autoscaler makes some assumptions about your Node Groups. Keeping your EC2 Auto Scaling Group configurations consistent with these assumptions will minimize undesired behavior.

Ensure that:

  • Each Node in a Node Group has identical scheduling properties, such as Labels, Taints, and Resources.
  • For MixedInstancePolicies, the Instance Types must be of the same shape for CPU, Memory, and GPU
  • The first Instance Type specified in the policy will be used to simulate scheduling.
  • If your policy has additional Instance Types with more resources, resources may be wasted after scale out.
  • If your policy has additional Instance Types with less resources, pods may fail to schedule on the instances.
  • Node Groups with many nodes are preferred over many Node Groups with fewer nodes. This will have the biggest impact on scalability.
  • Wherever possible, prefer EC2 features when both systems provide support (e.g. Regions, MixedInstancePolicy)

Note: We recommend using EKS Managed Node Groups. Managed Node Groups come with powerful management features, including features for Cluster Autoscaler like automatic EC2 Auto Scaling Group discovery and graceful node termination.

"},{"location":"cluster-autoscaling/#optimizing-for-performance-and-scalability","title":"Optimizing for Performance and Scalability","text":"

Understanding the autoscaling algorithm\u2019s runtime complexity will help you tune the Cluster Autoscaler to continue operating smoothly in large clusters with greater than 1,000 nodes.

The primary knobs for tuning scalability of the Cluster Autoscaler are the resources provided to the process, the scan interval of the algorithm, and the number of Node Groups in the cluster. There are other factors involved in the true runtime complexity of this algorithm, such as scheduling plugin complexity and number of pods. These are considered to be unconfigurable parameters as they are natural to the cluster\u2019s workload and cannot easily be tuned.

The Cluster Autoscaler loads the entire cluster\u2019s state into memory, including Pods, Nodes, and Node Groups. On each scan interval, the algorithm identifies unschedulable pods and simulates scheduling for each Node Group. Tuning these factors come with different tradeoffs which should be carefully considered for your use case.

"},{"location":"cluster-autoscaling/#vertically-autoscaling-the-cluster-autoscaler","title":"Vertically Autoscaling the Cluster Autoscaler","text":"

The simplest way to scale the Cluster Autoscaler to larger clusters is to increase the resource requests for its deployment. Both memory and CPU should be increased for large clusters, though this varies significantly with cluster size. The autoscaling algorithm stores all pods and nodes in memory, which can result in a memory footprint larger than a gigabyte in some cases. Increasing resources is typically done manually. If you find that constant resource tuning is creating an operational burden, consider using the Addon Resizer or Vertical Pod Autoscaler.

"},{"location":"cluster-autoscaling/#reducing-the-number-of-node-groups","title":"Reducing the number of Node Groups","text":"

Minimizing the number of node groups is one way to ensure that the Cluster Autoscaler will continue to perform well on large clusters. This may be challenging for some organizations who structure their node groups per team or per application. While this is fully supported by the Kubernetes API, this is considered to be a Cluster Autoscaler anti-pattern with repercussions for scalability. There are many reasons to use multiple node groups (e.g. Spot or GPUs), but in many cases there are alternative designs that achieve the same effect while using a small number of groups.

Ensure that:

  • Pod isolation is done using Namespaces rather than Node Groups.
  • This may not be possible in low-trust multi-tenant clusters.
  • Pod ResourceRequests and ResourceLimits are properly set to avoid resource contention.
  • Larger instance types will result in more optimal bin packing and reduced system pod overhead.
  • NodeTaints or NodeSelectors are used to schedule pods as the exception, not as the rule.
  • Regional resources are defined as a single EC2 Auto Scaling Group with multiple Availability Zones.
"},{"location":"cluster-autoscaling/#reducing-the-scan-interval","title":"Reducing the Scan Interval","text":"

A low scan interval (e.g. 10 seconds) will ensure that the Cluster Autoscaler responds as quickly as possible when pods become unschedulable. However, each scan results in many API calls to the Kubernetes API and EC2 Auto Scaling Group or EKS Managed Node Group APIs. These API calls can result in rate limiting or even service unavailability for your Kubernetes Control Plane.

The default scan interval is 10 seconds, but on AWS, launching a node takes significantly longer to launch a new instance. This means that it\u2019s possible to increase the interval without significantly increasing overall scale up time. For example, if it takes 2 minutes to launch a node, changing the interval to 1 minute will result a tradeoff of 6x reduced API calls for 38% slower scale ups.

"},{"location":"cluster-autoscaling/#sharding-across-node-groups","title":"Sharding Across Node Groups","text":"

The Cluster Autoscaler can be configured to operate on a specific set of Node Groups. Using this functionality, it\u2019s possible to deploy multiple instances of the Cluster Autoscaler, each configured to operate on a different set of Node Groups. This strategy enables you use arbitrarily large numbers of Node Groups, trading cost for scalability. We only recommend using this as a last resort for improving performance.

The Cluster Autoscaler was not originally designed for this configuration, so there are some side effects. Since the shards do not communicate, it\u2019s possible for multiple autoscalers to attempt to schedule an unschedulable pod. This can result in unnecessary scale out of multiple Node Groups. These extra nodes will scale back in after the scale-down-delay.

metadata:\n  name: cluster-autoscaler\n  namespace: cluster-autoscaler-1\n\n...\n\n--nodes=1:10:k8s-worker-asg-1\n--nodes=1:10:k8s-worker-asg-2\n\n---\n\nmetadata:\n  name: cluster-autoscaler\n  namespace: cluster-autoscaler-2\n\n...\n\n--nodes=1:10:k8s-worker-asg-3\n--nodes=1:10:k8s-worker-asg-4\n

Ensure that:

  • Each shard is configured to point to a unique set of EC2 Auto Scaling Groups
  • Each shard is deployed to a separate namespace to avoid leader election conflicts
"},{"location":"cluster-autoscaling/#optimizing-for-cost-and-availability","title":"Optimizing for Cost and Availability","text":""},{"location":"cluster-autoscaling/#spot-instances","title":"Spot Instances","text":"

You can use Spot Instances in your node groups and save up to 90% off the on-demand price, with the trade-off the Spot Instances can be interrupted at any time when EC2 needs the capacity back. Insufficient Capacity Errors will occur when your EC2 Auto Scaling group cannot scale up due to lack of available capacity. Maximizing diversity by selecting many instance families can increase your chance of achieving your desired scale by tapping into many Spot capacity pools, and decrease the impact of Spot Instance interruptions on your cluster availability. Mixed Instance Policies with Spot Instances are a great way to increase diversity without increasing the number of node groups. Keep in mind, if you need guaranteed resources, use On-Demand Instances instead of Spot Instances.

It\u2019s critical that all Instance Types have similar resource capacity when configuring Mixed Instance Policies. The autoscaler\u2019s scheduling simulator uses the first InstanceType in the MixedInstancePolicy. If subsequent Instance Types are larger, resources may be wasted after a scale up. If smaller, your pods may fail to schedule on the new instances due to insufficient capacity. For example, M4, M5, M5a, and M5n instances all have similar amounts of CPU and Memory and are great candidates for a MixedInstancePolicy. The EC2 Instance Selector tool can help you identify similar instance types.

It's recommended to isolate On-Demand and Spot capacity into separate EC2 Auto Scaling groups. This is preferred over using a base capacity strategy because the scheduling properties are fundamentally different. Since Spot Instances be interrupted at any time (when EC2 needs the capacity back), users will often taint their preemptable nodes, requiring an explicit pod toleration to the preemption behavior. These taints result in different scheduling properties for the nodes, so they should be separated into multiple EC2 Auto Scaling Groups.

The Cluster Autoscaler has a concept of Expanders, which provide different strategies for selecting which Node Group to scale. The strategy --expander=least-waste is a good general purpose default, and if you're going to use multiple node groups for Spot Instance diversification (as described in the image above), it could help further cost-optimize the node groups by scaling the group which would be best utilized after the scaling activity.

"},{"location":"cluster-autoscaling/#prioritizing-a-node-group-asg","title":"Prioritizing a node group / ASG","text":"

You may also configure priority based autoscaling by using the Priority expander. --expander=priority enables your cluster to prioritize a node group / ASG, and if it is unable to scale for any reason, it will choose the next node group in the prioritized list. This is useful in situations where, for example, you want to use P3 instance types because their GPU provides optimal performance for your workload, but as a second option you can also use P2 instance types.

apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cluster-autoscaler-priority-expander\n  namespace: kube-system\ndata:\n  priorities: |-\n    10:\n      - .*p2-node-group.*\n    50:\n      - .*p3-node-group.*\n

Cluster Autoscaler will try to scale up the EC2 Auto Scaling group matching the name p3-node-group. If this operation does not succeed within --max-node-provision-time, it will attempt to scale an EC2 Auto Scaling group matching the name p2-node-group. This value defaults to 15 minutes and can be reduced for more responsive node group selection, though if the value is too low, it can cause unnecessary scale outs.

"},{"location":"cluster-autoscaling/#overprovisioning","title":"Overprovisioning","text":"

The Cluster Autoscaler minimizes costs by ensuring that nodes are only added to the cluster when needed and are removed when unused. This significantly impacts deployment latency because many pods will be forced to wait for a node scale up before they can be scheduled. Nodes can take multiple minutes to become available, which can increase pod scheduling latency by an order of magnitude.

This can be mitigated using overprovisioning, which trades cost for scheduling latency. Overprovisioning is implemented using temporary pods with negative priority, which occupy space in the cluster. When newly created pods are unschedulable and have higher priority, the temporary pods will be preempted to make room. The temporary pods then become unschedulable, triggering the Cluster Autoscaler to scale out new overprovisioned nodes.

There are other less obvious benefits to overprovisioning. Without overprovisioning, one of the side effects of a highly utilized cluster is that pods will make less optimal scheduling decisions using the preferredDuringSchedulingIgnoredDuringExecution rule of Pod or Node Affinity. A common use case for this is to separate pods for a highly available application across availability zones using AntiAffinity. Overprovisioning can significantly increase the chance that a node of the correct zone is available.

The amount of overprovisioned capacity is a careful business decision for your organization. At its core, it\u2019s a tradeoff between performance and cost. One way to make this decision is to determine your average scale up frequency and divide it by the amount of time it takes to scale up a new node. For example, if on average you require a new node every 30 seconds and EC2 takes 30 seconds to provision a new node, a single node of overprovisioning will ensure that there\u2019s always an extra node available, reducing scheduling latency by 30 seconds at the cost of a single additional EC2 Instance. To improve zonal scheduling decisions, overprovision a number of nodes equal to the number of availability zones in your EC2 Auto Scaling Group to ensure that the scheduler can select the best zone for incoming pods.

"},{"location":"cluster-autoscaling/#prevent-scale-down-eviction","title":"Prevent Scale Down Eviction","text":"

Some workloads are expensive to evict. Big data analysis, machine learning tasks, and test runners will eventually complete, but must be restarted if interrupted. The Cluster Autoscaler will attempt to scale down any node under the scale-down-utilization-threshold, which will interrupt any remaining pods on the node. This can be prevented by ensuring that pods that are expensive to evict are protected by a label recognized by the Cluster Autoscaler.

Ensure that:

  • Expensive to evict pods have the annotation cluster-autoscaler.kubernetes.io/safe-to-evict=false
"},{"location":"cluster-autoscaling/#advanced-use-cases","title":"Advanced Use Cases","text":""},{"location":"cluster-autoscaling/#ebs-volumes","title":"EBS Volumes","text":"

Persistent storage is critical for building stateful applications, such as database or distributed caches. EBS Volumes enable this use case on Kubernetes, but are limited to a specific zone. These applications can be highly available if sharded across multiple AZs using a separate EBS Volume for each AZ. The Cluster Autoscaler can then balance the scaling of the EC2 Autoscaling Groups.

Ensure that:

  • Node group balancing is enabled by setting balance-similar-node-groups=true.
  • Node Groups are configured with identical settings except for different availability zones and EBS Volumes.
"},{"location":"cluster-autoscaling/#co-scheduling","title":"Co-Scheduling","text":"

Machine learning distributed training jobs benefit significantly from the minimized latency of same-zone node configurations. These workloads deploy multiple pods to a specific zone. This can be achieved by setting Pod Affinity for all co-scheduled pods or Node Affinity using topologyKey: failure-domain.beta.kubernetes.io/zone. The Cluster Autoscaler will then scale out a specific zone to match demands. You may wish to allocate multiple EC2 Auto Scaling Groups, one per availability zone to enable failover for the entire co-scheduled workload.

Ensure that:

  • Node group balancing is enabled by setting balance-similar-node-groups=false
  • Node Affinity and/or Pod Preemption is used when clusters include both Regional and Zonal Node Groups.
  • Use Node Affinity to force or encourage regional pods to avoid zonal Node Groups, and vice versa.
  • If zonal pods schedule onto regional node groups, this will result in imbalanced capacity for your regional pods.
  • If your zonal workloads can tolerate disruption and relocation, configure Pod Preemption to enable regionally scaled pods to force preemption and rescheduling on a less contested zone.
"},{"location":"cluster-autoscaling/#accelerators","title":"Accelerators","text":"

Some clusters take advantage of specialized hardware accelerators such as GPU. When scaling out, the accelerator device plugin can take several minutes to advertise the resource to the cluster. The Cluster Autoscaler has simulated that this node will have the accelerator, but until the accelerator becomes ready and updates the node\u2019s available resources, pending pods can not be scheduled on the node. This can result in repeated unnecessary scale out.

Additionally, nodes with accelerators and high CPU or Memory utilization will not be considered for scale down, even if the accelerator is unused. This behavior can be expensive due to the relative cost of accelerators. Instead, the Cluster Autoscaler can apply special rules to consider nodes for scale down if they have unoccupied accelerators.

To ensure the correct behavior for these cases, you can configure the kubelet on your accelerator nodes to label the node before it joins the cluster. The Cluster Autoscaler will use this label selector to trigger the accelerator optimized behavior.

Ensure that:

  • The Kubelet for GPU nodes is configured with --node-labels k8s.amazonaws.com/accelerator=$ACCELERATOR_TYPE
  • Nodes with Accelerators adhere to the identical scheduling properties rule noted above.
"},{"location":"cluster-autoscaling/#scaling-from-0","title":"Scaling from 0","text":"

Cluster Autoscaler is capable of scaling Node Groups to and from zero, which can yield significant cost savings. It detects the CPU, memory, and GPU resources of an Auto Scaling Group by inspecting the InstanceType specified in its LaunchConfiguration or LaunchTemplate. Some pods require additional resources like WindowsENI or PrivateIPv4Address or specific NodeSelectors or Taints which cannot be discovered from the LaunchConfiguration. The Cluster Autoscaler can account for these factors by discovering them from tags on the EC2 Auto Scaling Group. For example:

Key: k8s.io/cluster-autoscaler/node-template/resources/$RESOURCE_NAME\nValue: 5\nKey: k8s.io/cluster-autoscaler/node-template/label/$LABEL_KEY\nValue: $LABEL_VALUE\nKey: k8s.io/cluster-autoscaler/node-template/taint/$TAINT_KEY\nValue: NoSchedule\n

Note: Keep in mind, when scaling to zero your capacity is returned to EC2 and may be unavailable in the future.

"},{"location":"cluster-autoscaling/#additional-parameters","title":"Additional Parameters","text":"

There are many configuration options that can be used to tune the behavior and performance of the Cluster Autoscaler. A complete list of parameters is available on GitHub.

Parameter Description Default scan-interval How often cluster is reevaluated for scale up or down 10 seconds max-empty-bulk-delete Maximum number of empty nodes that can be deleted at the same time. 10 scale-down-delay-after-add How long after scale up that scale down evaluation resumes 10 minutes scale-down-delay-after-delete How long after node deletion that scale down evaluation resumes, defaults to scan-interval scan-interval scale-down-delay-after-failure How long after scale down failure that scale down evaluation resumes 3 minutes scale-down-unneeded-time How long a node should be unneeded before it is eligible for scale down 10 minutes scale-down-unready-time How long an unready node should be unneeded before it is eligible for scale down 20 minutes scale-down-utilization-threshold Node utilization level, defined as sum of requested resources divided by capacity, below which a node can be considered for scale down 0.5 scale-down-non-empty-candidates-count Maximum number of non empty nodes considered in one iteration as candidates for scale down with drain. Lower value means better CA responsiveness but possible slower scale down latency. Higher value can affect CA performance with big clusters (hundreds of nodes). Set to non positive value to turn this heuristic off - CA will not limit the number of nodes it considers.\u201c 30 scale-down-candidates-pool-ratio A ratio of nodes that are considered as additional non empty candidates for scale down when some candidates from previous iteration are no longer valid. Lower value means better CA responsiveness but possible slower scale down latency. Higher value can affect CA performance with big clusters (hundreds of nodes). Set to 1.0 to turn this heuristics off - CA will take all nodes as additional candidates. 0.1 scale-down-candidates-pool-min-count Minimum number of nodes that are considered as additional non empty candidates for scale down when some candidates from previous iteration are no longer valid. When calculating the pool size for additional candidates we take max(#nodes * scale-down-candidates-pool-ratio, scale-down-candidates-pool-min-count) 50"},{"location":"cluster-autoscaling/#additional-resources","title":"Additional Resources","text":"

This page contains a list of Cluster Autoscaler presentations and demos. If you'd like to add a presentation or demo here, please send a pull request.

Presentation/Demo Presenters Autoscaling and Cost Optimization on Kubernetes: From 0 to 100 Guy Templeton, Skyscanner & Jiaxin Shan, Amazon SIG-Autoscaling Deep Dive Maciek Pytel & Marcin Wielgus"},{"location":"cluster-autoscaling/#references","title":"References","text":"
  • https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md
  • https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md
  • https://github.com/aws/amazon-ec2-instance-selector
  • https://github.com/aws/aws-node-termination-handler
"},{"location":"cost_optimization/awareness/","title":"Expenditure awareness","text":"

Expenditure awareness is understanding who, where and what is causing expenditures in your EKS cluster. Getting an accurate picture of this data will help raise awareness of your spend and highlight areas to remediate.

"},{"location":"cost_optimization/awareness/#recommendations","title":"Recommendations","text":""},{"location":"cost_optimization/awareness/#use-cost-explorer","title":"Use Cost Explorer","text":"

AWS Cost Explorer has an easy-to-use interface that lets you visualize, understand, and manage your AWS costs and usage over time. You can analyze cost and usage data, at various levels using the filters available in Cost Explorer.

"},{"location":"cost_optimization/awareness/#eks-control-plane-and-eks-fargate-costs","title":"EKS Control Plane and EKS Fargate costs","text":"

Using the filters, we can query the costs incurred for the EKS costs at the Control Plane and Fargate Pod as shown in the diagram below:

Using the filters, we can query the aggregate costs incurred for the Fargate Pods across regions in EKS - which includes both vCPU-Hours per CPU and GB Hrs as shown in the diagram below:

"},{"location":"cost_optimization/awareness/#tagging-of-resources","title":"Tagging of Resources","text":"

Amazon EKS supports adding AWS tags to your Amazon EKS clusters. This makes it easy to control access to the EKS API for managing your clusters. Tags added to an EKS cluster are specific to the AWS EKS cluster resource, they do not propagate to other AWS resources used by the cluster such as EC2 instances or load balancers. Today, cluster tagging is supported for all new and existing EKS clusters via the AWS API, Console, and SDKs.

AWS Fargate is a technology that provides on-demand, right-sized compute capacity for containers. Before you can schedule pods on Fargate in your cluster, you must define at least one Fargate profile that specifies which pods should use Fargate when they are launched.

Adding and Listing tags to an EKS cluster:

$ aws eks tag-resource --resource-arn arn:aws:eks:us-west-2:xxx:cluster/ekscluster1 --tags team=devops,env=staging,bu=cio,costcenter=1234\n$ aws eks list-tags-for-resource --resource-arn arn:aws:eks:us-west-2:xxx:cluster/ekscluster1\n{\n    \"tags\": {\n        \"bu\": \"cio\",\n        \"env\": \"staging\",\n        \"costcenter\": \"1234\",\n        \"team\": \"devops\"\n    }\n}\n
After you activate cost allocation tags in the AWS Cost Explorer, AWS uses the cost allocation tags to organize your resource costs on your cost allocation report, to make it easier for you to categorize and track your AWS costs.

Tags don't have any semantic meaning to Amazon EKS and are interpreted strictly as a string of characters. For example, you can define a set of tags for your Amazon EKS clusters to help you track each cluster's owner and stack level.

"},{"location":"cost_optimization/awareness/#use-aws-trusted-advisor","title":"Use AWS Trusted Advisor","text":"

AWS Trusted Advisor offers a rich set of best practice checks and recommendations across five categories: cost optimization; security; fault tolerance; performance; and service limits.

For Cost Optimization, Trusted Advisor helps eliminate unused and idle resources and recommends making commitments to reserved capacity. The key action items that will help Amazon EKS will be around low utilsed EC2 instances, unassociated Elastic IP addresses, Idle Load Balancers, underutilized EBS volumes among other things. The complete list of checks are provided at https://aws.amazon.com/premiumsupport/technology/trusted-advisor/best-practice-checklist/.

The Trusted Advisor also provides Savings Plans and Reserved Instances recommendations for EC2 instances and Fargate which allows you to commit to a consistent usage amount in exchange for discounted rates.

Note

The recommendations from Trusted Advisor are generic recommendations and not specific to EKS.

"},{"location":"cost_optimization/awareness/#use-the-kubernetes-dashboard","title":"Use the Kubernetes dashboard","text":"

Kubernetes dashboard

Kubernetes Dashboard is a general purpose, web-based UI for Kubernetes clusters, which provides information about the Kubernetes cluster including the resource usage at a cluster, node and pod level. The deployment of the Kubernetes dashboard on an Amazon EKS cluster is described in the Amazon EKS documentation.

Dashboard provides resource usage breakdowns for each node and pod, as well as detailed metadata about pods, services, Deployments, and other Kubernetes objects. This consolidated information provides visibility into your Kubernetes environment.

kubectl top and describe commands

Viewing resource usage metrics with kubectl top and kubectl describe commands. kubectl top will show current CPU and memory usage for the pods or nodes across your cluster, or for a specific pod or node. The kubectl describe command will give more detailed information about a specific node or a pod.

$ kubectl top pods\n$ kubectl top nodes\n$ kubectl top pod pod-name --namespace mynamespace --containers\n

Using the top command, the output will displays the total amount of CPU (in cores) and memory (in MiB) that the node is using, and the percentages of the node\u2019s allocatable capacity those numbers represent. You can then drill-down to the next level, container level within pods by adding a --containers flag.

$ kubectl describe node <node>\n$ kubectl describe pod <pod>\n

kubectl describe returns the percent of total available capacity that each resource request or limit represents.

kubectl top and describe, track the utilization and availability of critical resources such as CPU, memory, and storage across kubernetes pods, nodes and containers. This awareness will help in understanding resource usage and help in controlling costs.

"},{"location":"cost_optimization/awareness/#use-cloudwatch-container-insights","title":"Use CloudWatch Container Insights","text":"

Use CloudWatch Container Insights to collect, aggregate, and summarize metrics and logs from your containerized applications and microservices. Container Insights is available for Amazon Elastic Kubernetes Service on EC2, and Kubernetes platforms on Amazon EC2. The metrics include utilization for resources such as CPU, memory, disk, and network.

The installation of insights is given in the documentation.

CloudWatch creates aggregated metrics at the cluster, node, pod, task, and service level as CloudWatch metrics.

The following query shows a list of nodes, sorted by average node CPU utilization

STATS avg(node_cpu_utilization) as avg_node_cpu_utilization by NodeName\n| SORT avg_node_cpu_utilization DESC \n

CPU usage by Container name

stats pct(container_cpu_usage_total, 50) as CPUPercMedian by kubernetes.container_name \n| filter Type=\"Container\"\n
Disk usage by Container name
stats floor(avg(container_filesystem_usage/1024)) as container_filesystem_usage_avg_kb by InstanceId, kubernetes.container_name, device \n| filter Type=\"ContainerFS\" \n| sort container_filesystem_usage_avg_kb desc\n

More sample queries are given in the Container Insights documention

This awareness will help in understanding resource usage and help in controlling costs.

"},{"location":"cost_optimization/awareness/#using-kubecost-for-expenditure-awareness-and-guidance","title":"Using Kubecost for expenditure awareness and guidance","text":"

Third party tools like kubecost can also be deployed on Amazon EKS to get visibility into cost of running your Kubernetes cluster. Please refer to this AWS blog for tracking costs using Kubecost

Deploying kubecost using Helm 3:

$ curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash\n$ helm version --short\nv3.2.1+gfe51cd1\n$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/\n$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/c^C\n$ kubectl create namespace kubecost \nnamespace/kubecost created\n$ helm repo add kubecost https://kubecost.github.io/cost-analyzer/ \n\"kubecost\" has been added to your repositories\n\n$ helm install kubecost kubecost/cost-analyzer --namespace kubecost --set kubecostToken=\"aGRoZEBqc2pzLmNvbQ==xm343yadf98\"\nNAME: kubecost\nLAST DEPLOYED: Mon May 18 08:49:05 2020\nNAMESPACE: kubecost\nSTATUS: deployed\nREVISION: 1\nTEST SUITE: None\nNOTES:\n--------------------------------------------------Kubecost has been successfully installed. When pods are Ready, you can enable port-forwarding with the following command:\n\n    kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090\n\nNext, navigate to http://localhost:9090 in a web browser.\n$ kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090\n\nNote: If you are using Cloud 9 or have a need to forward it to a different port like 8080, issue the following command\n$ kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 8080:9090\n
Kubecost Dashboard -

"},{"location":"cost_optimization/awareness/#use-kubernetes-cost-allocation-and-capacity-planning-analytics-tool","title":"Use Kubernetes Cost Allocation and Capacity Planning Analytics Tool","text":"

Kubernetes Opex Analytics is a tool to help organizations track the resources being consumed by their Kubernetes clusters to prevent overpaying. To do so it generates, short- (7 days), mid- (14 days) and long-term (12 months) usage reports showing relevant insights on what amount of resources each project is spending over time.

"},{"location":"cost_optimization/awareness/#magalix-kubeadvisor","title":"Magalix Kubeadvisor","text":"

KubeAdvisor continuously scans your Kubernetes clusters and reports how you can fix issues, apply best practices, and optimize your cluster (with recommendations of resources like CPU/Memory around cost-efficiency).

"},{"location":"cost_optimization/awareness/#spotio-previously-called-spotinst","title":"Spot.io, previously called Spotinst","text":"

Spotinst Ocean is an application scaling service. Similar to Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups, Spotinst Ocean is designed to optimize performance and costs by leveraging Spot Instances combined with On-Demand and Reserved Instances. Using a combination of automated Spot Instance management and the variety of instance sizes, the Ocean cluster autoscaler scales according to the pod resource requirements. Spotinst Ocean also includes a prediction algorithm to predict Spot Instance interruption 15 minutes ahead of time and spin up a new node in a different Spot capacity pool.

This is available as an AWS Quickstart developed by Spotinst, Inc. in collaboration with AWS.

The EKS workshop also has a module on Optimized Worker Node on Amazon EKS Management with Ocean by Spot.io which includes sections on cost allocation, right sizing and scaling strategies.

"},{"location":"cost_optimization/awareness/#yotascale","title":"Yotascale","text":"

Yotascale helps with accurately allocating Kubernetes costs. Yotascale Kubernetes Cost Allocation feature utilizes actual cost data, which is inclusive of Reserved Instance discounts and spot instance pricing instead of generic market-rate estimations, to inform the total Kubernetes cost footprint

More details can be found at their website.

"},{"location":"cost_optimization/awareness/#alcide-advisor","title":"Alcide Advisor","text":"

Alcide is an AWS Partner Network (APN) Advanced Technology Partner. Alcide Advisor helps ensure your Amazon EKS cluster, nodes, and pods configuration are tuned to run according to security best practices and internal guidelines. Alcide Advisor is an agentless service for Kubernetes audit and compliance that\u2019s built to ensure a frictionless and secured DevSecOps flow by hardening the development stage before moving to production.

More details can be found in this blog post.

"},{"location":"cost_optimization/awareness/#other-tools","title":"Other tools","text":""},{"location":"cost_optimization/awareness/#kubernetes-garbage-collection","title":"Kubernetes Garbage Collection","text":"

The role of the Kubernetes garbage collector is to delete certain objects that once had an owner, but no longer have an owner.

"},{"location":"cost_optimization/awareness/#fargate-count","title":"Fargate count","text":"

Fargatecount is an useful tool, which allows AWS customers to track, with a custom CloudWatch metric, the total number of EKS pods that have been deployed on Fargate in a specific region of a specific account. This helps in keeping track of all the Fargate pods running across an EKS cluster.

"},{"location":"cost_optimization/awareness/#popeye-a-kubernetes-cluster-sanitizer","title":"Popeye - A Kubernetes Cluster Sanitizer","text":"

Popeye - A Kubernetes Cluster Sanitizer is a utility that scans live Kubernetes cluster and reports potential issues with deployed resources and configurations. It sanitizes your cluster based on what's deployed and not what's sitting on disk. By scanning your cluster, it detects misconfigurations and helps you to ensure that best practices are in place

"},{"location":"cost_optimization/awareness/#resources","title":"Resources","text":"

Refer to the following resources to learn more about best practices for cost optimization.

"},{"location":"cost_optimization/awareness/#documentation-and-blogs","title":"Documentation and Blogs","text":"
  • Amazon EKS supports tagging
"},{"location":"cost_optimization/awareness/#tools","title":"Tools","text":"
  • What is AWS Billing and Cost Management?
  • Amazon CloudWatch Container Insights
  • How to track costs in multi-tenant Amazon EKS clusters using Kubecost
  • Kubecost
  • Kube Opsview
  • Kubernetes Opex Analytics
"},{"location":"cost_optimization/cfm_framework/","title":"Cost Optimization - Introduction","text":"

AWS Cloud Economics is a discipline that helps customers increase efficiency and reduce their costs through the adoption of modern compute technologies like Amazon EKS. The discipline recommends following a methodology called the \u201cCloud Financial Management (CFM) framework\u201d which consists of 4 pillars:

"},{"location":"cost_optimization/cfm_framework/#the-see-pillar-measurement-and-accountability","title":"The See pillar: Measurement and accountability","text":"

The See pillar is a foundational set of activities and technologies that define how to measure, monitor and create accountability for cloud spend. It is often referred to as \u201cObservability\u201d, \u201cInstrumentation\u201d, or \u201cTelemetry\u201d. The capabilities and limitations of the \u201cObservability\u201d infrastructure dictate what can be optimized. Obtaining a clear picture of your costs is a critical first step in cost optimization as you need to know where you are starting from. This type of visibility will also guide the types of activities you will need to do to further optimize your environment.

Here is a brief overview of our best practices for the See pillar:

  • Define and maintain a tagging strategy for your workloads.
    • Use Instance Tagging, tagging EKS clusters allows you to see individual cluster costs and allocate them in your Cost & Usage Reports.
  • Establish reporting and monitoring of EKS usage by using technologies like Kubecost.
    • Enable Cloud Intelligence Dashboards, by having resources properly tagged and using visualizations, you can measure and estimate costs.
  • Allocate cloud costs to applications, Lines of Business (LoBs), and revenue streams.
  • Define, measure, and circulate efficiency/value KPIs with business stakeholders. For example, create a \u201cunit metric\u201d KPI that measures the cost per transaction, e.g. a ride sharing services might have a KPI for \u201ccost per ride\u201d.

For more details on the recommended technologies and activities associated with this pillar, please see the Cost Optimization - Observability section of this guide.

"},{"location":"cost_optimization/cfm_framework/#the-save-pillar-cost-optimization","title":"The Save pillar: Cost optimization","text":"

This pillar is based on the technologies and capabilities developed in the \u201cSee\u201d pillar. The following activities typically fall under this pillar:

  • Identify and eliminate waste in your environment.
  • Architect and design for cost efficiency.
  • Choose the best purchasing option, e.g. on-demand instances vs Spot instances.
  • Adapt as services evolve: as AWS services evolve, the way to efficiently use those services may change. Be willing to adapt to account for these changes.

Since these activities are operational, they are highly dependent on your environment\u2019s characteristics. Ask yourself, what are the main drivers of costs? What business value do your different environments provide? What purchasing options and infrastructure choices, e.g. instance family types, are best suited for each environment?

Below is a prioritized list of the most common cost drivers for EKS clusters:

  1. Compute costs: Combining multiple types of instance families, purchasing options, and balancing scalability with availability require careful consideration. For further information, see the recommendations in the Cost Optimization - Compute section of this guide.
  2. Networking costs: using 3 AZs for EKS clusters can potentially increase inter-AZ traffic costs. For our recommendations on how to balance HA requirements with keeping network traffic costs down, please consult the Cost Optimization - Networking section of this guide.
  3. Storage costs: Depending on the stateful/stateless nature of the workloads in the EKS clusters, and how the different storage types are used, storage can be considered as part of the workload. For considerations relating to EKS storage costs, please consult the Cost Optimization - Storage section of this guide.
"},{"location":"cost_optimization/cfm_framework/#the-plan-pillar-planning-and-forecasting","title":"The Plan pillar: Planning and forecasting","text":"

Once the recommendations in the See pillar are implemented, clusters are optimized on an on-going basis. As experience is gained in operating clusters efficiently, planning and forecasting activities can focus on:

  • Budgeting and forecasting cloud costs dynamically.
  • Quantifying the business value delivered by EKS container services.
  • Integrating EKS cluster cost management with IT financial management planning.
"},{"location":"cost_optimization/cfm_framework/#the-run-pillar","title":"The Run pillar","text":"

Cost optimization is a continuous process and involves a flywheel of incremental improvements:

Securing executive sponsorship for these types of activities is crucial for integrating EKS cluster optimization into the organization\u2019s \u201cFinOps\u201d efforts. It allows stakeholder alignment through a shared understanding of EKS cluster costs, implementation of EKS cluster cost guardrails, and ensuring that the tooling, automation, and activities evolve with the organization\u2019s needs.

"},{"location":"cost_optimization/cfm_framework/#references","title":"References","text":"
  • AWS Cloud Economics, Cloud Financial Management
"},{"location":"cost_optimization/cost_opt_compute/","title":"Cost Optimization - Compute and Autoscaling","text":"

As a developer, you'll make estimates about your application\u2019s resource requirements, e.g. CPU and memory, but if you\u2019re not continually adjusting them they may become outdated which could increase your costs and worsen performance and reliability. Continually adjusting an application's resource requirements is more important than getting them right the first time.

The best practices mentioned below will help you build and operate cost-aware workloads that achieve business outcomes while minimizing costs and allowing your organization to maximize its return on investment. A high level order of importance for optimizing your cluster compute costs are:

  1. Right-size workloads
  2. Reduce unused capacity
  3. Optimize compute capacity types (e.g. Spot) and accelerators (e.g. GPUs)
"},{"location":"cost_optimization/cost_opt_compute/#right-size-your-workloads","title":"Right-size your workloads","text":"

In most EKS clusters, the bulk of cost come from the EC2 instances that are used to run your containerized workloads. You will not be able to right-size your compute resources without understanding your workloads requirements. This is why it is essential that you use the appropriate requests and limits and make adjustments to those settings as necessary. In addition, dependencies, such as instance size and storage selection, may effect workload performance which can have a variety of unintended consequences on costs and reliability.

Requests should align with the actual utilization. If a container's requests are too high there will be unused capacity which is a large factor in total cluster costs. Each container in a pod, e.g. application and sidecars, should have their own requests and limits set to make sure the aggregate pod limits are as accurate as possible.

Utilize tools such as Goldilocks, KRR, and Kubecost which estimate resource requests and limits for your containers. Depending on the nature of the applications, performance/cost requirements, and complexity you need to evaluate which metrics are best to scale on, at what point your application performance degrades (saturation point), and how to tweak request and limits accordingly. Please refer to Application right sizing for further guidance on this topic.

We recommend using the Horizontal Pod Autoscaler (HPA) to control how many replicas of your application should be running, the Vertical Pod Autoscaler (VPA) to adjust how many requests and limits your application needs per replica, and a node autoscaler like Karpenter or Cluster Autoscaler to continually adjust the total number of nodes in your cluster. Cost optimization techniques using Karpenter and Cluster Autoscaler are documented in a later section of this document.

The Vertical Pod Autoscaler can adjust the requests and limits assigned to containers so workloads run optimally. You should run the VPA in auditing mode so it does not automatically make changes and restart your pods. It will suggest changes based on observed metrics. With any changes that affect production workloads you should review and test those changes first in a non-production environment because these can have impact on your application\u2019s reliability and performance.

"},{"location":"cost_optimization/cost_opt_compute/#reduce-consumption","title":"Reduce consumption","text":"

The best way to save money is to provision fewer resources. One way to do that is to adjust workloads based on their current requirements. You should start any cost optimization efforts with making sure your workloads define their requirements and scale dynamically. This will require getting metrics from your applications and setting configurations such as PodDisruptionBudgets and Pod Readiness Gates to make sure your application can safely scale up and down dynamically. Its important to consider that restrictive PodDisruptionBudgets can prevent Cluster Autoscaler and Karpenter from scaling down Nodes, since both Cluster Autoscaler and Karpenter respect PodDisruptionBudgets. The 'minAvailable' value in the PodDisruptionBudget should always be lower than the number of pods in the deployment and you should keep a good buffer between the two e.g. In a deployment of 6 pods where you want a minimum of 4 pods running at all times, set the 'minAvailable' in your PodDisruptionBidget to 4. This will allow Cluster Autoscaler and Karpenter to safely drain and evict pods from the under-utilized nodes during a Node scale-down event. Please refer to Cluster Autoscaler FAQ doc.

The Horizontal Pod Autoscaler is a flexible workload autoscaler that can adjust how many replicas are needed to meet the performance and reliability requirements of your application. It has a flexible model for defining when to scale up and down based on various metrics such as CPU, memory, or custom metrics e.g. queue depth, number of connections to a pod, etc.

The Kubernetes Metrics Server enables scaling in response to built-in metrics like CPU and memory usage, but if you want to scale based on other metrics, such as Amazon CloudWatch or SQS queue depth, you should consider event driven autoscaling projects such as KEDA. Please refer to this blog post on how to use KEDA with CloudWatch metrics. If you are unsure, which metrics to monitor and scale based on, check out the best practices on monitoring metrics that matters.

Reducing workload consumption creates excess capacity in a cluster and with proper autoscaling configuration allows you to scale down nodes automatically and reduce your total spend. We recommend you do not try to optimize compute capacity manually. The Kubernetes scheduler and node autoscalers were designed to handle this process for you.

"},{"location":"cost_optimization/cost_opt_compute/#reduce-unused-capacity","title":"Reduce unused capacity","text":"

After you have determined the correct size for applications, reducing excess requests, you can begin to reduce the provisioned compute capacity. You should be able to do this dynamically if you have taken the time to correctly size your workloads from the sections above. There are two primary node autoscalers used with Kubernetes in AWS.

"},{"location":"cost_optimization/cost_opt_compute/#karpenter-and-cluster-autoscaler","title":"Karpenter and Cluster Autoscaler","text":"

Both Karpenter and the Kubernetes Cluster Autoscaler will scale the number of nodes in your cluster as pods are created or removed and compute requirements change. The primary goal of both is the same, but Karpenter takes a different approach for node management provisioning and de-provisioning which can help reduce costs and optimize cluster wide usage.

As clusters grow in size and the variety of workloads increases it becomes more difficult to pre-configure node groups and instances. Just like with workload requests it\u2019s important to set an initial baseline and continually adjust as needed.

If you are using Cluster Autoscaler, it will respect the \"minimum\" and \"maximum\" values of each Auto Scaling group (ASG) and only adjust the \"desired\" value. It's important to pay attention while setting these values for the underlying ASG since Cluster Autoscaler will not be able to scale down an ASG beyond its \"minimum\" count. Set the \"desired\" count as the number of nodes you need during normal business hours and \"minimum\" as the number of nodes you need during off-business hours. Please refer to Cluster Autoscaler FAQ doc.

"},{"location":"cost_optimization/cost_opt_compute/#cluster-autoscaler-priority-expander","title":"Cluster Autoscaler Priority Expander","text":"

The Kubernetes Cluster Autoscaler works by scaling groups of nodes \u2014 called a node group \u2014 up and down as applications scale up and down. If you are not dynamically scaling workloads then the Cluster Autoscaler will not help you save money. The Cluster Autoscaler requires a cluster admin to create node groups ahead of time for workloads to consume. The node groups need to configured to use instances that have the same \"profile\", i.e. roughly the same amount of CPU and memory.

You can have multiple node groups and the Cluster Autoscaler can be configured to set priority scaling levels and each node group can contain different sized nodes. Node groups can have different capacity types and the priority expander can be used to scale less expensive groups first.

Below is an example of a snippet of cluster configuration that uses a `ConfigMap`` to prioritize reserved capacity before using on-demand instances. You can use the same technique to prioritize Graviton or Spot Instances over other types.

apiVersion: eksctl.io/v1alpha5\nkind: ClusterConfig\nmetadata:\n  name: my-cluster\nmanagedNodeGroups:\n  - name: managed-ondemand\n    minSize: 1\n    maxSize: 7\n    instanceType: m5.xlarge\n  - name: managed-reserved\n    minSize: 2\n    maxSize: 10\n    instanceType: c5.2xlarge\n
apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cluster-autoscaler-priority-expander\n  namespace: kube-system\ndata:\n  priorities: |-\n    10:\n      - .*ondemand.*\n    50:\n      - .*reserved.*\n

Using node groups can help the underlying compute resources do the expected thing by default, e.g. spread nodes across AZs, but not all workloads have the same requirements or expectations and it\u2019s better to let applications declare their requirements explicitly. For more information about Cluster Autoscaler, please see the best practices section.

"},{"location":"cost_optimization/cost_opt_compute/#descheduler","title":"Descheduler","text":"

The Cluster Autoscaler can add and remove node capacity from a cluster based on new pods needing to be scheduled or nodes being underutilized. It does not take a wholistic view of pod placement after it has been scheduled to a node. If you are using the Cluster Autoscaler you should also look at the Kubernetes descheduler to avoid wasting capacity in your cluster.

If you have 10 nodes in a cluster and each node is 60% utilized you are not using 40% of the provisioned capacity in the cluster. With the Cluster Autoscaler you can set the utilization threashold per node to 60%, but that would only try to scale down a single node after utilization dropped below 60%.

With the descheduler it can look at cluster capacity and utilization after pods have been scheduled or nodes have been added to the cluster. It attempts to keep the total capacity of the cluster above a specified threshold. It can also remove pods based on node taints or new nodes that join the cluster to make sure pods are running in their optimal compute environment. Note that, descheduler does not schedule replacement of evicted pods but relies on the default scheduler for that.

"},{"location":"cost_optimization/cost_opt_compute/#karpenter-consolidation","title":"Karpenter Consolidation","text":"

Karpenter takes a \u201cgroupless\u201d approach to node management. This approach is more flexible for different workload types and requires less up front configuration for cluster administrators. Instead of pre-defining groups and scaling each group as workloads need, Karpenter uses provisioners and node templates to define broadly what type of EC2 instances can be created and settings about the instances as they are created.

Bin packing is the practice of utilizing more of the instance\u2019s resources by packing more workloads onto fewer, optimally sized, instances. While this helps to reduce your compute costs by only provisioning resources your workloads use, it has a trade-off. It can take longer to start new workloads because capacity has to be added to the cluster, especially during large scaling events. Consider the balance between cost optimization, performance, and availability when setting up bin packing.

Karpenter can continuously monitor and binpack to improve instance resource utilization and lower your compute costs. Karpenter can also select a more cost efficient worker node for your workload. This can be achieved by turning on \u201cconsolidation\u201d flag to true in the provisioner (sample code snippet below). The example below shows an example provisioner that enables consolidation. At the time of writing this guide, Karpenter won\u2019t replace a running Spot instance with a cheaper Spot instance. For further details on Karpenter consolidation, refer to this blog.

apiVersion: karpenter.sh/v1alpha5\nkind: Provisioner\nmetadata:\n  name: enable-binpacking\nspec:\n  consolidation:\n    enabled: true\n

For workloads that might not be interruptible e.g. long running batch jobs without checkpointing, consider annotating pods with the do-not-evict annotation. By opting pods out of eviction, you are telling Karpenter that it should not voluntarily remove nodes containing this pod. However, if a do-not-evict pod is added to a node while the node is draining, the remaining pods will still evict, but that pod will block termination until it is removed. In either case, the node will be cordoned to prevent additional work from being scheduled on the node. Below is an example showing how set the annotation:

apiVersion: v1\nkind: Pod\nmetadata:\n  name: label-demo\n  labels:\n    environment: production\n  annotations:  \n    \"karpenter.sh/do-not-evict\": \"true\"\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - containerPort: 80\n
"},{"location":"cost_optimization/cost_opt_compute/#remove-under-utilized-nodes-by-adjusting-cluster-autoscaler-parameters","title":"Remove under-utilized nodes by adjusting Cluster Autoscaler parameters","text":"

Node utilization is defined as the sum of requested resources divided by capacity. By default scale-down-utilization-threshold is set to 50%. This parameter can be used along with and scale-down-unneeded-time, which determines how long a node should be unneeded before it is eligible for scale down \u2014 the default is 10 minutes. Pods still running on a node that was scaled down will get scheduled on other nodes by kube-scheduler. Adjusting these settings can help remove nodes that are underutilized, but it\u2019s important you test these values first so you don\u2019t force the cluster to scale down prematurely.

You can prevent scale down from happening by ensuring that pods that are expensive to evict are protected by a label recognized by the Cluster Autoscaler. To do this, ensure that pods that are expensive to evict have the annotation cluster-autoscaler.kubernetes.io/safe-to-evict=false. Below is an example yaml to set the annotation:

apiVersion: v1\nkind: Pod\nmetadata:\n  name: label-demo\n  labels:\n    environment: production\n  annotations:  \n    \"cluster-autoscaler.kubernetes.io/safe-to-evict\": \"false\"\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - containerPort: 80\n
"},{"location":"cost_optimization/cost_opt_compute/#tag-nodes-with-cluster-autoscaler-and-karpenter","title":"Tag nodes with Cluster Autoscaler and Karpenter","text":"

AWS resource tags are used to organize your resources, and to track your AWS costs on a detailed level. They do not directly correlate with Kubernetes labels for cost tracking. It\u2019s recommended to start with Kubernetes resource labeling and utilize tools like Kubecost to get infrastructure cost reporting based on Kubernetes labels on pods, namespaces etc.

Worker nodes need to have tags to show billing information in AWS Cost Explorer. With Cluster Autoscaler, tag your worker nodes inside a managed node group using launch template. For self managed node groups, tag your instances using EC2 auto scaling group. For instances provisioned by Karpenter, tag them using spec.tags in the node template.

"},{"location":"cost_optimization/cost_opt_compute/#multi-tenant-clusters","title":"Multi-tenant clusters","text":"

When working on clusters that are shared by different teams you may not have visibility to other workloads running on the same node. While resource requests can help isolate some \u201cnoisy neighbor\u201d concerns, such as CPU sharing, they may not isolate all resource boundaries such as disk I/O exhaustion. Not every consumable resource by a workload can be isolated or limited. Workloads that consume shared resources at higher rates than other workloads should be isolated through node taints and tolerations. Another advanced technique for such workload is CPU pinning which ensures exclusive CPU instead of shared CPU for the container.

Isolating workloads at a node level can be more expensive, but it may be possible to schedule BestEffort jobs or take advantage of additional savings by using Reserved Instances, Graviton processors, or Spot.

Shared clusters may also have cluster level resource constraints such as IP exhaustion, Kubernetes service limits, or API scaling requests. You should review the scalability best practices guide to make sure your clusters avoid these limits.

You can isolate resources at a namespace or Karpenter provisioner level. Resource Quotas provide a way to set limits on how many resources workloads in a namespace can consume. This can be a good initial guard rail but it should be continually evaluated to make sure it doesn\u2019t artificially restrict workloads from scaling.

Karpenter provisioners can set limits on some of the consumable resources in a cluster (e.g. CPU, GPU), but you will need to configure tenant applications to use the appropriate provisioner. This can prevent a single provisioner from creating too many nodes in a cluster, but it should be continually evaluated to make sure the limit isn\u2019t set too low and in turn, prevent workloads from scaling.

"},{"location":"cost_optimization/cost_opt_compute/#scheduled-autoscaling","title":"Scheduled Autoscaling","text":"

You may have the need to scale down your clusters on weekends and off hours. This is particularly relevant for test and non-production clusters where you want to scale down to zero when they are not in use. Solutions like cluster-turndown can scale down the replicas to zero based on a cron schedule. You can also acheive this with Karpenter, outlined in the following AWS blog.

"},{"location":"cost_optimization/cost_opt_compute/#optimize-compute-capacity-types","title":"Optimize compute capacity types","text":"

After optimizing the total capacity of compute in your cluster and utilizing bin packing, you should look at what type of compute you have provisioned in your clusters and how you pay for those resources. AWS has Compute Savings plans that can reduce the cost for your compute which we will categorize into the following capacity types:

  • Spot
  • Savings Plans
  • On-Demand
  • Fargate

Each capacity type has different trade-offs for management overhead, availability, and long term commitments and you will need to decide which is right for your environment. No environment should rely on a single capacity type and you can mix multiple run types in a single cluster to optimize specific workload requirements and cost.

"},{"location":"cost_optimization/cost_opt_compute/#spot","title":"Spot","text":"

The spot capacity type provisions EC2 instances from spare capacity in an Availability Zone. Spot offers the largest discounts\u2014up to 90% \u2014 but those instances can be interrupted when they are needed elsewhere. Additionally, there may not always be capacity to provision new Spot instances and existing Spot instances can be reclaimed with a 2 minute interruption notice. If your application has a long startup or shutdown process, Spot instances may not be the best option.

Spot compute should use a wide variety of instance types to reduce the likelihood of not having spot capacity available. Instance interruptions need to be handled to safely shutdown nodes. Nodes provisioned with Karpenter or part of a Managed Node Group automatically support instance interruption notifications. If you are using self-managed nodes you will need to run the node termination handler separately to gracefully shutdown spot instances.

It is possible to balance spot and on-demand instances in a single cluster. With Karpenter you can create weighted provisioners to achieve a balance of different capacity types. With Cluster Autoscaler you can create mixed node groups with spot and on-demand or reserved instances.

Here is an example of using Karpenter to prioritize Spot instances ahead of On-Demand instances. When creating a provisioner, you can specify either Spot, On-Demand, or both (as shown below). When you specify both, and if the pod does not explicitly specify whether it needs to use Spot or On-Demand, then Karpenter prioritizes Spot when provisioning a node with price-capacity-optimization allocation strategy .

apiVersion: karpenter.sh/v1alpha5\nkind: Provisioner\nmetadata:\n  name: spot-prioritized\nspec:\n  requirements:\n    - key: \"karpenter.sh/capacity-type\" \n      operator: In\n        values: [\"spot\", \"on-demand\"]\n
"},{"location":"cost_optimization/cost_opt_compute/#savings-plans-reserved-instances-and-aws-edp","title":"Savings Plans, Reserved Instances, and AWS EDP","text":"

You can reduce your compute spend by using a compute savings plan. Savings plans offer reduced prices for a 1 or 3 year commitment of compute usage. The usage can apply to EC2 instances in an EKS cluster but also applies to any compute usage such as Lambda and Fargate. With savings plans you can reduce costs and still pick any EC2 instance type during your commitment period.

Compute savings plan can reduce your EC2 cost by up to 66% without requiring commitments on what instance types, families, or regions you want to use. Savings are automatically applied to instances as you use them.

EC2 Instance Savings Plans provides up to 72% savings on compute with a commitment of usage in a specific region and EC2 family, e.g. instances from the C family. You can shift usage to any AZ within the region, use any generation of the instance family, e.g. c5 or c6, and use any size of instance within the family. The discount will automatically be applied for any instance in your account that matches the savings plan criteria.

Reserved Instances are similar to EC2 Instance Savings Plan but they also guarantee capacity in an Availability Zone or Region and reduce cost\u2014up to 72% \u2014 over on-demand instances. Once you calculate how much reserved capacity you will need you can select how long you would like to reserve them for (1 year or 3 years). The discounts will automatically be applied as you run those EC2 instances in your account.

Customers also have the option to enroll in an Enterprise Agreement with AWS. Enterprise Agreements give customers the option to tailor agreements that best suit their needs. Customers can enjoy discounts on the pricing based on AWS EDP (Enterprise Discount Program). For additional information on Enterprise Agreements please contact your AWS sales representative.

"},{"location":"cost_optimization/cost_opt_compute/#on-demand","title":"On-Demand","text":"

On-Demand EC2 instances have the benefit of availability without interruptions \u2014 compared to spot \u2014 and no long term commitments \u2014 compared to savings plans. If you are looking to reduce costs in a cluster you should reduce your usage of on-demand EC2 instances.

After optimizing your workload requirements you should be able to calculate a minimum and maximum capacity for your clusters. This number may change over time but rarely goes down. Consider using a Savings Plan for everything under the minimum, and spot for capacity that will not affect your application\u2019s availability. Anything else that may not be continuously used or is required for availability can use on-demand.

As mentioned in this section, the best way to reduce your usage is to consume fewer resources and utilize the resources you provision to the fullest extent possible. With the Cluster Autoscaler you can remove underutilized nodes with the scale-down-utilization-threshold setting. With Karpenter it is recommended to enable consolidation.

To manually identify EC2 instance types that can be used with your workloads you should use ec2-instance-selector which can show instances that are available in each region as well as instances compatible with EKS. Example usage for instances with x86 process architecture, 4 Gb of memory, 2 vCPUs and available in the us-east-1 region.

ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 \\\n  -r us-east-1 --service eks\nc5.large\nc5a.large\nc5ad.large\nc5d.large\nc6a.large\nc6i.large\nt2.medium\nt3.medium\nt3a.medium\n

For non-production environments you can automatically have clusters scaled down during unused hours such as night and weekends. The kubecost project cluster-turndown is an example of a controller that can automatically scale your cluster down based on a set schedule.

"},{"location":"cost_optimization/cost_opt_compute/#fargate-compute","title":"Fargate compute","text":"

Fargate compute is a fully managed compute option for EKS clusters. It provides pod isolation by scheduling one pod per node in a Kubernetes cluster. It allows you to size your compute nodes to the CPU and RAM requirements of your workload to tightly control workload usage in a cluster.

Fargate can scale workloads as small as .25 vCPU with 0.5 GB memory and as large as 16 vCPU with 120 GB memory. There are limits on how many pod size variations are available and you will need to understand how your workload best fits into a Fargate configuration. For example, if your workload requires 1 vCPU with 0.5 GB of memory the smallest Fargate pod will be 1 vCPU with 2 GB of memory.

While Fargate has many benefits such as no EC2 instance or operating system management, it may require more compute capacity than traditional EC2 instances due to the fact that every deployed pod is isolated as a separate node in the cluster. This requires more duplication for things like the Kubelet, logging agents, and any DaemonSets you would typically deploy to a node. DaemonSets are not supported in Fargate and they will need to be converted into pod \u201csidecars\u201c and run alongside the application.

Fargate cannot benefit from bin packing or CPU over provisioning because the boundary for the workload is a node which is not burstable or shareable between workloads. Fargate will save you EC2 instance management time which itself has a cost, but CPU and memory costs may be more expensive than other EC2 capacity types. Fargate pods can take advantage of compute savings plan to reduce the on-demand cost.

"},{"location":"cost_optimization/cost_opt_compute/#optimize-compute-usage","title":"Optimize Compute Usage","text":"

Another way to save money on your compute infrastructure is to use more efficient compute for the workload. This can come from more performant general purpose compute like Graviton processors which are up to 20% cheaper and 60% more energy efficient than x86\u2014or workload specific accelerators such as GPUs and FPGAs. You will need to build containers that can run on arm architecture and set up nodes with the right accelerators for your workloads.

EKS has the ability to run clusters with mixed architecture (e.g. amd64 and arm64) and if your containers are compiled for multiple architectures you can take advantage of Graviton processors with Karpenter by allowing both architectures in your provisioner. To keep consistent performance, however, it is recommended you keep each workload on a single compute architecture and only use different architecture if there is no additional capacity available.

Provisioners can be configured with multiple architectures and workloads can also request specific architectures in their workload specification.

apiVersion: karpenter.sh/v1alpha5\nkind: Provisioner\nmetadata:\n  name: default\nspec:\n  requirements:\n  - key: \"kubernetes.io/arch\"\n    operator: In\n    values: [\"arm64\", \"amd64\"]\n

With Cluster Autoscaler you will need to create a node group for Graviton instances and set node tolerations on your workload to utilize the new capacity.

GPUs and FPGAs can greatly increase the performance for your workload, but the workload will need to be optimized to use the accelerator. Many workload types for machine learning and artificial intelligence can use GPUs for compute and instances can be added to a cluster and mounted into a workload using resource requests.

spec:\n  template:\n    spec:\n    - containers:\n      ...\n      resources:\n          limits:\n            nvidia.com/gpu: \"1\"\n

Some GPU hardware can be shared across multiple workloads so a single GPU can be provisioned and used. To see how to configure workload GPU sharing see the virtual GPU device plugin for more information. You can also refer to the following blogs:

  • GPU sharing on Amazon EKS with NVIDIA time-slicing and accelerated EC2 instances
  • Maximizing GPU utilization with NVIDIA\u2019s Multi-Instance GPU (MIG) on Amazon EKS: Running more pods per GPU for enhanced performance
"},{"location":"cost_optimization/cost_opt_networking/","title":"Cost Optimization - Networking","text":"

Architecting systems for high availability (HA) is a best practice in order to accomplish resilience and fault-tolerance. In practice, this means spreading your workloads and the underlying infrastructure across multiple Availability Zones (AZs) in a given AWS Region. Ensuring these characteristics are in place for your Amazon EKS environment will enhance the overall reliability of your system. In conjunction with this, your EKS environments will likely also be composed of a variety of constructs (i.e. VPCs), components (i.e. ELBs), and integrations (i.e. ECR and other container registries).

The combination of highly available systems and other use-case specific components can play a significant role in how data is transferred and processed. This will in turn have an impact on the costs incurred due to data transfer and processing.

The practices detailed below will help you design and optimize your EKS environments in order to achieve cost-effectiveness for different domains and use cases.

"},{"location":"cost_optimization/cost_opt_networking/#pod-to-pod-communication","title":"Pod to Pod Communication","text":"

Depending on your setup, network communication and data transfer between Pods can have a significant impact on the overall cost of running Amazon EKS workloads. This section will cover different concepts and approaches to mitigating the costs tied to inter-pod communication, while considering highly available (HA) architectures, application performance and resilience.

"},{"location":"cost_optimization/cost_opt_networking/#restricting-traffic-to-an-availability-zone","title":"Restricting Traffic to an Availability Zone","text":"

Frequent egress cross-zone traffic (traffic distributed between AZs) can have a major impact on your network-related costs. Below are some strategies on how to control the amount of cross-zone traffic between Pods in your EKS cluster.

If you want granular visibility into the amount of cross-zone traffic between Pods in your cluster (such as the amount of data transferred in bytes), refer to this post.

Using Topology Aware Routing (formerly known as Topology Aware Hints)

When using topology aware routing, it's important to understand how Services, EndpointSlices and the kube-proxy work together when routing traffic. As the diagram above depicts, Services are the stable network abstraction layer that receive traffic destined for your Pods. When a Service is created, multiple EndpointSlices are created. Each EndpointSlice has a list of endpoints containing a subset of Pod addresses along with the nodes they're running on and any additional topology information. kube-proxy is a daemonset that runs on every node in your cluster and also fulfills a role of internal routing, but it does so based on what it consumes from the created EndpointSlices.

When topology aware routing is enabled and implemented on a Kubernetes Service, the EndpointSlice controller will proportionally allocate endpoints to the different zones that your cluster is spread across. For each of those endpoints, the EndpointSlice controller will also set a hint for the zone. Hints describe which zone an endpoint should serve traffic for. kube-proxy will then route traffic from a zone to an endpoint based on the hints that get applied.

The diagram below shows how EndpointSlices with hints are organized in such a way that kube-proxy can know what destination they should go to based on their zonal point of origin. Without hints, there is no such allocation or organization and traffic will be proxied to different zonal destinations regardless of where it\u2019s coming from.

In some cases, the EndpointSlice controller may apply a hint for a different zone, meaning the endpoint could end up serving traffic originating from a different zone. The reason for this is to try and maintain an even distribution of traffic between endpoints in different zones.

Below is a code snippet on how to enable topology aware routing for a Service.

apiVersion: v1\nkind: Service\nmetadata:\n  name: orders-service\n  namespace: ecommerce\n    annotations:\n      service.kubernetes.io/topology-mode: Auto\nspec:\n  selector:\n    app: orders\n  type: ClusterIP\n  ports:\n  - protocol: TCP\n    port: 3003\n    targetPort: 3003\n

The screenshot below shows the result of the EndpointSlice controller having successfully applied a hint to an endpoint for a Pod replica running in the AZ eu-west-1a.

Note

It\u2019s important to note that topology aware routing is still in beta. Also, this feature is more predictable when workloads are widely and evenly distributed across the cluster topology. Therefore, it is highly recommended to use it in conjunction with scheduling constraints that increase the availability of an application such as pod topology spread constraints.

Using Autoscalers: Provision Nodes to a Specific AZ

We strongly recommend running your workloads in highly available environments across multiple AZs. This improves the reliability of your applications, especially when there is an incident of an issue with an AZ. In the case you're willing to sacrifice reliability for the sake of reducing their network-related costs, you can restrict your nodes to a single AZ.

To run all your Pods in the same AZ, either provision the worker nodes in the same AZ or schedule the Pods on the worker nodes running on the same AZ. To provision nodes within a single AZ, define a node group with subnets belonging to the same AZ with Cluster Autoscaler (CA). For Karpenter, use \u201ctopology.kubernetes.io/zone\u201d and specify the AZ where you\u2019d like to create the worker nodes. For example, the below Karpenter provisioner snippet provisions the nodes in the us-west-2a AZ.

Karpenter

apiVersion: karpenter.sh/v1alpha5\nkind: Provisioner\nmetadata:\nname: single-az\nspec:\n  requirements:\n  - key: \"topology.kubernetes.io/zone\u201c\n    operator: In\n    values: [\"us-west-2a\"]\n

Cluster Autoscaler (CA)

apiVersion: eksctl.io/v1alpha5\nkind: ClusterConfig\nmetadata:\n  name: my-ca-cluster\n  region: us-east-1\n  version: \"1.21\"\navailabilityZones:\n- us-east-1a\nmanagedNodeGroups:\n- name: managed-nodes\n  labels:\n    role: managed-nodes\n  instanceType: t3.medium\n  minSize: 1\n  maxSize: 10\n  desiredCapacity: 1\n...\n

Using Pod Assignment and Node Affinity

Alternatively, if you have worker nodes running in multiple AZs, each node would have the label topology.kubernetes.io/zone with the value of its AZ (such as us-west-2a or us-west-2b). You can utilize nodeSelector or nodeAffinity to schedule Pods to the nodes in a single AZ. For example, the following manifest file will schedule the Pod inside a node running in AZ us-west-2a.

apiVersion: v1\nkind: Pod\nmetadata:\n  name: nginx\n  labels:\n    env: test\nspec:\n  nodeSelector:\n    topology.kubernetes.io/zone: us-west-2a\n  containers:\n  - name: nginx\n    image: nginx \n    imagePullPolicy: IfNotPresent\n
"},{"location":"cost_optimization/cost_opt_networking/#restricting-traffic-to-a-node","title":"Restricting Traffic to a Node","text":"

There are cases where restricting traffic at a zonal level isn\u2019t sufficient. Apart from reducing costs, you may have the added requirement of reducing network latency between certain applications that have frequent inter-communication. In order to achieve optimal network performance and reduce costs, you need a way to restrict traffic to a specific node. For example, Microservice A should always talk to Microservice B on Node 1, even in highly available (HA) setups. Having Microservice A on Node 1 talk to Microservice B on Node 2 may have a negative impact on the desired performance for applications of this nature, especially if Node 2 is in a separate AZ altogether.

Using the Service Internal Traffic Policy

In order to restrict Pod network traffic to a node, you can make use of the Service internal traffic policy. By default, traffic sent to a workload\u2019s Service will be randomly distributed across the different generated endpoints. So in a HA architecture, that means traffic from Microservice A could go to any replica of Microservice B on any given node across the different AZs. However, with the Service's internal traffic policy set to Local, traffic will be restricted to endpoints on the node that the traffic originated from. This policy dictates the exclusive use of node-local endpoints. By implication, your network traffic-related costs for that workload will be lower than if the distribution was cluster wide. Also, the latency will be lower, making your application more performant.

Note

It\u2019s important to note that this feature cannot be combined with topology aware routing in Kubernetes.

Below is a code snippet on how to set the internal traffic policy for a Service.

apiVersion: v1\nkind: Service\nmetadata:\n  name: orders-service\n  namespace: ecommerce\nspec:\n  selector:\n    app: orders\n  type: ClusterIP\n  ports:\n  - protocol: TCP\n    port: 3003\n    targetPort: 3003\n  internalTrafficPolicy: Local\n

To avoid unexpected behaviour from your application due to traffic drops, you should consider the following approaches:

  • Run enough replicas for each of the communicating Pods
  • Have a relatively even spread of Pods using topology spread constraints
  • Make use of pod-affinity rules for co-location of communicating Pods

In this example, you have 2 replicas of Microservice A and 3 replicas of Microservice B. If Microservice A has its replicas spread between Nodes 1 and 2, and Microservice B has all 3 of its replicas on Node 3, then they won't be able to communicate because of the Local internal traffic policy. When there are no available node-local endpoints the traffic is dropped.

If Microservice B does have 2 of its 3 replicas on Nodes 1 and 2, then there will be communication between the peer applications. But you would still have an isolated replica of Microservice B without any peer replica to communicate with.

Note

In some scenarios, an isolated replica like the one depicted in the above diagram may not be a cause for concern if it still serves a purpose (such as serving requests from external incoming traffic).

Using the Service Internal Traffic Policy with Topology Spread Constraints

Using the internal traffic policy in conjunction with topology spread constraints can be useful to ensure that you have the right number of replicas for communicating microservices on different nodes.

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: express-test\nspec:\n  replicas: 6\n  selector:\n    matchLabels:\n      app: express-test\n  template:\n    metadata:\n      labels:\n        app: express-test\n        tier: backend\n    spec:\n      topologySpreadConstraints:\n      - maxSkew: 1\n        topologyKey: \"topology.kubernetes.io/zone\"\n        whenUnsatisfiable: ScheduleAnyway\n        labelSelector:\n          matchLabels:\n            app: express-test\n

Using the Service Internal Traffic Policy with Pod Affinity Rules

Another approach is to make use of Pod affinity rules when using the Service internal traffic policy. With Pod affinity, you can influence the scheduler to co-locate certain Pods because of their frequent communication. By applying strict scheduling constraints (requiredDuringSchedulingIgnoredDuringExecution) on certain Pods, this will give you better results for Pod co-location when the Scheduler is placing Pods on nodes.

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: graphql\n  namespace: ecommerce\n  labels:\n    app.kubernetes.io/version: \"0.1.6\"\n    ...\n    spec:\n      serviceAccountName: graphql-service-account\n      affinity:\n        podAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: app\n                operator: In\n                values:\n                - orders\n            topologyKey: \"kubernetes.io/hostname\"\n
"},{"location":"cost_optimization/cost_opt_networking/#load-balancer-to-pod-communication","title":"Load Balancer to Pod Communication","text":"

EKS workloads are typically fronted by a load balancer that distributes traffic to the relevant Pods in your EKS cluster. Your architecture may comprise internal and/or external facing load balancers. Depending on your architecture and network traffic configurations, the communication between load balancers and Pods can contribute a significant amount to data transfer charges.

You can use the AWS Load Balancer Controller to automatically manage the creation of ELB resources (ALB and NLB). The data transfer charges you incur in such setups will depend on the path taken by the network traffic. The AWS Load Balancer Controller supports two network traffic modes, instance mode, and ip mode.

When using instance mode, a NodePort will be opened on each node in your EKS cluster. The load balancer will then proxy traffic evenly across the nodes. If a node has the destination Pod running on it, then there will be no data transfer costs incurred. However, if the destination Pod is on a separate node and in a different AZ than the NodePort receiving the traffic, then there will be an extra network hop from the kube-proxy to the destination Pod. In such a scenario, there will be cross-AZ data transfer charges. Because of the even distribution of traffic across the nodes, it is highly likely that there will be additional data transfer charges associated with cross-zone network traffic hops from kube-proxies to the relevant destination Pods.

The diagram below depicts a network path for traffic flowing from the load balancer to the NodePort, and subsequently from the kube-proxy to the destination Pod on a separate node in a different AZ. This is an example of the instance mode setting.

When using ip mode, network traffic is proxied from the load balancer directly to the destination Pod. As a result, there are no data transfer charges involved in this approach.

Tip

It is recommended that you set your load balancer to ip traffic mode to reduce data transfer charges. For this setup, it\u2019s also important to make sure that your load balancer is deployed across all the subnets in your VPC.

The diagram below depicts network paths for traffic flowing from the load balancer to Pods in the network ip mode.

"},{"location":"cost_optimization/cost_opt_networking/#data-transfer-from-container-registry","title":"Data Transfer from Container Registry","text":""},{"location":"cost_optimization/cost_opt_networking/#amazon-ecr","title":"Amazon ECR","text":"

Data transfer into the Amazon ECR private registry is free. In-region data transfer incurs no cost, but data transfer out to the internet and across regions will be charged at Internet Data Transfer rates on both sides of the transfer.

You should utilize ECRs built-in image replication feature to replicate the relevant container images into the same region as your workloads. This way the replication would be charged once, and all the same region (intra-region) image pulls would be free.

You can further reduce data transfer costs associated with pulling images from ECR (data transfer out) by using Interface VPC Endpoints to connect to the in-region ECR repositories. The alternative approach of connecting to ECR\u2019s public AWS endpoint (via a NAT Gateway and an Internet Gateway) will incur higher data processing and transfer costs. The next section will cover reducing data transfer costs between your workloads and AWS Services in greater detail.

If you\u2019re running workloads with especially large images, you can build your own custom Amazon Machine Images (AMIs) with pre-cached container images. This can reduce the initial image pull time and potential data transfer costs from a container registry to the EKS worker nodes.

"},{"location":"cost_optimization/cost_opt_networking/#data-transfer-to-internet-aws-services","title":"Data Transfer to Internet & AWS Services","text":"

It's a common practice to integrate Kubernetes workloads with other AWS services or third-party tools and platforms via the Internet. The underlying network infrastructure used to route traffic to and from the relevant destination can impact the costs incurred in the data transfer process.

"},{"location":"cost_optimization/cost_opt_networking/#using-nat-gateways","title":"Using NAT Gateways","text":"

NAT Gateways are network components that perform network address translation (NAT). The diagram below depicts Pods in an EKS cluster communicating with other AWS services (Amazon ECR, DynamoDB, and S3), and third-party platforms. In this example, the Pods are running in private subnets in separate AZs. To send and receive traffic from the Internet, a NAT Gateway is deployed to the public subnet of one AZ, allowing any resources with private IP addresses to share a single public IP address to access the Internet. This NAT Gateway in turn communicates with the Internet Gateway component, allowing for packets to be sent to their final destination.

When using NAT Gateways for such use cases, you can minimize the data transfer costs by deploying a NAT Gateway in each AZ. This way, traffic routed to the Internet will go through the NAT Gateway in the same AZ, avoiding inter-AZ data transfer. However, even though you\u2019ll save on the cost of inter-AZ data transfer, the implication of this setup is that you\u2019ll incur the cost of an additional NAT Gateway in your architecture.

This recommended approach is depicted in the diagram below.

"},{"location":"cost_optimization/cost_opt_networking/#using-vpc-endpoints","title":"Using VPC Endpoints","text":"

To further reduce costs in such architectures, you should use VPC Endpoints to establish connectivity between your workloads and AWS services. VPC Endpoints allow you to access AWS services from within a VPC without data/network packets traversing the Internet. All traffic is internal and stays within the AWS network. There are two types of VPC Endpoints: Interface VPC Endpoints (supported by many AWS services) and Gateway VPC Endpoints (only supported by S3 and DynamoDB).

Gateway VPC Endpoints

There are no hourly or data transfer costs associated with Gateway VPC Endpoints. When using Gateway VPC Endpoints, it's important to note that they are not extendable across VPC boundaries. They can't be used in VPC peering, VPN networking, or via Direct Connect.

Interface VPC Endpoint

VPC Endpoints have an hourly charge and, depending on the AWS service, may or may not have an additional charge associated with data processing via the underlying ENI. To reduce inter-AZ data transfer costs related to Interface VPC Endpoints, you can create a VPC Endpoint in each AZ. You can create multiple VPC Endpoints in the same VPC even if they're pointing to the same AWS service.

The diagram below shows Pods communicating with AWS services via VPC Endpoints.

"},{"location":"cost_optimization/cost_opt_networking/#data-transfer-between-vpcs","title":"Data Transfer between VPCs","text":"

In some cases, you may have workloads in distinct VPCs (within the same AWS region) that need to communicate with each other. This can be accomplished by allowing traffic to traverse the public internet through Internet Gateways attached to the respective VPCs. Such communication can be enabled by deploying infrastructure components like EC2 instances, NAT Gateways or NAT instances in public subnets. However, a setup including these components will incur charges for processing/transferring data in and out of the VPCs. If the traffic to and from the separate VPCs is moving across AZs, then there will be an additional charge in the transfer of data. The diagram below depicts a setup that uses NAT Gateways and Internet Gateways to establish communication between workloads in different VPCs.

"},{"location":"cost_optimization/cost_opt_networking/#vpc-peering-connections","title":"VPC Peering Connections","text":"

To reduce costs for such use cases, you can make use of VPC Peering. With a VPC Peering connection, there are no data transfer charges for network traffic that stays within the same AZ. If traffic crosses AZs, there will be a cost incurred. Nonetheless, the VPC Peering approach is recommended for cost-effective communication between workloads in separate VPCs within the same AWS region. However, it\u2019s important to note that VPC peering is primarily effective for 1:1 VPC connectivity because it doesn\u2019t allow for transitive networking.

The diagram below is a high-level representation of workloads communication via a VPC peering connection.

"},{"location":"cost_optimization/cost_opt_networking/#transitive-networking-connections","title":"Transitive Networking Connections","text":"

As pointed out in the previous section, VPC Peering connections do not allow for transitive networking connectivity. If you want to connect 3 or more VPCs with transitive networking requirements, then you should use a Transit Gateway (TGW). This will enable you to overcome the limits of VPC Peering or any operational overhead associated with having multiple VPC Peering connections between multiple VPCs. You are billed on an hourly basis and for data sent to the TGW. There is no destination cost associated with inter-AZ traffic that flows through the TGW.

The diagram below shows inter-AZ traffic flowing through a TGW between workloads in different VPCs but within the same AWS region.

"},{"location":"cost_optimization/cost_opt_networking/#using-a-service-mesh","title":"Using a Service Mesh","text":"

Service meshes offer powerful networking capabilities that can be used to reduce network related costs in your EKS cluster environments. However, you should carefully consider the operational tasks and complexity that a service mesh will introduce to your environment if you adopt one.

"},{"location":"cost_optimization/cost_opt_networking/#restricting-traffic-to-availability-zones","title":"Restricting Traffic to Availability Zones","text":"

Using Istio\u2019s Locality Weighted Distribution

Istio enables you to apply network policies to traffic after routing occurs. This is done using Destination Rules such as locality weighted distribution. Using this feature, you can control the weight (expressed as a percentage) of traffic that can go to a certain destination based on its origin. The source of this traffic can either be from an external (or public facing) load balancer or a Pod within the cluster itself. When all the Pod endpoints are available, the locality will be selected based on a weighted round-robin load balancing algorithm. In the case that certain endpoints are unhealthy or unavailable, the locality weight will be automatically adjusted to reflect this change in the available endpoints.

Note

Before implementing locality weighted distribution, you should start by understanding your network traffic patterns and the implications that the Destination Rule policy may have on your application\u2019s behaviour. As such, it\u2019s important to have distributed tracing mechanisms in place with tools such as AWS X-Ray or Jaeger.

The Istio Destination Rules detailed above can also be applied to manage traffic from a load balancer to Pods in your EKS cluster. Locality weighted distribution rules can be applied to a Service that receives traffic from a highly available load balancer (specifically the Ingress Gateway). These rules allow you to control how much traffic goes where based on its zonal origin - the load balancer in this case. If configured correctly, less egress cross-zone traffic will be incurred compared to a load balancer that distributes traffic evenly or randomly to Pod replicas in different AZs.

Below is a code block example of a Destination Rule resource in Istio. As can be seen below, this resource specifies weighted configurations for incoming traffic from 3 different AZs in the eu-west-1 region. These configurations declare that a majority of the incoming traffic (70% in this case) from a given AZ should be proxied to a destination in the same AZ from which it originates.

apiVersion: networking.istio.io/v1beta1\nkind: DestinationRule\nmetadata:\n  name: express-test-dr\nspec:\n  host: express-test.default.svc.cluster.local\n  trafficPolicy:\n    loadBalancer:                        \n      localityLbSetting:\n        distribute:\n        - from: eu-west-1/eu-west-1a/    \n          to:\n            \"eu-west-1/eu-west-1a/*\": 70 \n            \"eu-west-1/eu-west-1b/*\": 20\n            \"eu-west-1/eu-west-1c/*\": 10\n        - from: eu-west-1/eu-west-1b/*    \n          to:\n            \"eu-west-1/eu-west-1a/*\": 20 \n            \"eu-west-1/eu-west-1b/*\": 70\n            \"eu-west-1/eu-west-1c/*\": 10\n        - from: eu-west-1/eu-west-1c/*    \n          to:\n            \"eu-west-1/eu-west-1a/*\": 20 \n            \"eu-west-1/eu-west-1b/*\": 10\n            \"eu-west-1/eu-west-1c/*\": 70**\n    connectionPool:\n      http:\n        http2MaxRequests: 10\n        maxRequestsPerConnection: 10\n    outlierDetection:\n      consecutiveGatewayErrors: 1\n      interval: 1m\n      baseEjectionTime: 30s\n

Note

The minimum weight that can be distributed destination is 1%. The reason for this is to maintain failover regions and zones in the case that the endpoints in the main destination become unhealthy or unavailable.

The diagram below depicts a scenario in which there is a highly available load balancer in the eu-west-1 region and locality weighted distribution is applied. The Destination Rule policy for this diagram is configured to send 60% of traffic coming from eu-west-1a to Pods in the same AZ, whereas 40% of the traffic from eu-west-1a should go to Pods in eu-west-1b.

"},{"location":"cost_optimization/cost_opt_networking/#restricting-traffic-to-availability-zones-and-nodes","title":"Restricting Traffic to Availability Zones and Nodes","text":"

Using the Service Internal Traffic Policy with Istio

To mitigate network costs associated with external incoming traffic and internal traffic between Pods, you can combine Istio\u2019s Destination Rules and the Kubernetes Service internal traffic policy. The way to combine Istio destination rules with the service internal traffic policy will largely depend on 3 things:

  • The role of the microservices
  • Network traffic patterns across the microservices
  • How the microservices should be deployed across the Kubernetes cluster topology

The diagram below shows what the network flow would look like in the case of a nested request and how the aforementioned policies would control the traffic.

  1. The end user makes a request to APP A, which in turn makes a nested request to APP C. This request is first sent to a highly available load balancer, which has instances in AZ 1 and AZ 2 as the above diagram shows.
  2. The external incoming request is then routed to the correct destination by the Istio Virtual Service.
  3. After the request is routed, the Istio Destination Rule controls how much traffic goes to the respective AZs based on where it originated from (AZ 1 or AZ 2).
  4. The traffic then goes to the Service for APP A, and is then proxied to the respective Pod endpoints. As shown in the diagram, 80% of the incoming traffic is sent to Pod endpoints in AZ 1, and 20% of the incoming traffic is sent to AZ 2.
  5. APP A then makes an internal request to APP C. APP C's Service has an internal traffic policy enabled (internalTrafficPolicy``: Local).
  6. The internal request from APP A (on NODE 1) to APP C is successful because of the available node-local endpoint for APP C.
  7. The internal request from APP A (on NODE 3) to APP C fails because there are no available node-local endpoints for APP C. As the diagram shows, APP C has no replicas on NODE 3. ****

The screenshots below are captured from a live example of this approach. The first set of screenshots demonstrate a successful external request to a graphql and a successful nested request from the graphql to a co-located orders replica on the node ip-10-0-0-151.af-south-1.compute.internal.

With Istio, you can verify and export the statistics of any upstream clusters and endpoints that your proxies are aware of. This can help provide a picture of the network flow as well as the share of distribution among the services of a workload. Continuing with the same example, the orders endpoints that the graphql proxy is aware of can be obtained using the following command:

kubectl exec -it deploy/graphql -n ecommerce -c istio-proxy -- curl localhost:15000/clusters | grep orders \n
...\norders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_error::0**\norders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_success::119**\norders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_timeout::0**\norders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**rq_total::119**\norders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**health_flags::healthy**\norders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**region::af-south-1**\norders-service.ecommerce.svc.cluster.local::10.0.1.33:3003::**zone::af-south-1b**\n...\n

In this case, the graphql proxy is only aware of the orders endpoint for the replica that it shares a node with. If you remove the internalTrafficPolicy: Local setting from the orders Service, and re-run a command like the one above, then the results will return all the endpoints of the replicas spread across the different nodes. Furthermore, by examining the rq_total for the respective endpoints, you'll notice a relatively even share in network distribution. Consequently, if the endpoints are associated with upstream services running in different AZs, then this network distribution across zones will result in higher costs.

As mentioned in a previous section above, you can co-locate frequently communicating Pods by making use of pod-affinity.

...\nspec:\n...\n  template:\n    metadata:\n      labels:\n        app: graphql\n        role: api\n        workload: ecommerce\n    spec:\n      affinity:\n        podAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: app\n                operator: In\n                values:\n                - orders\n            topologyKey: \"kubernetes.io/hostname\"\n      nodeSelector:\n        managedBy: karpenter\n        billing-team: ecommerce\n...\n

When the graphql and orders replicas don't co-exist on the same node (ip-10-0-0-151.af-south-1.compute.internal), the first request to graphql is successful as noted by the 200 response code in the Postman screenshot below, whereas the second nested request from graphql to orders fails with a 503 response code.

"},{"location":"cost_optimization/cost_opt_networking/#additional-resources","title":"Additional Resources","text":"
  • Addressing latency and data transfer costs on EKS using Istio
  • Exploring the effect of Topology Aware Hints on network traffic in Amazon Elastic Kubernetes Service
  • Getting visibility into your Amazon EKS Cross-AZ pod to pod network bytes
  • Optimize AZ Traffic with Istio
  • Optimize AZ Traffic with Topology Aware Routing
  • Optimize Kubernetes Cost & Performance with Service Internal Traffic Policy
  • Optimize Kubernetes Cost & Performance with Istio and Service Internal Traffic Policy
  • Overview of Data Transfer Costs for Common Architectures
  • Understanding data transfer costs for AWS container services
"},{"location":"cost_optimization/cost_opt_observability/","title":"Cost Optimization - Observability","text":""},{"location":"cost_optimization/cost_opt_observability/#introduction","title":"Introduction","text":"

Observability tools help you efficiently detect, remediate and investigate your workloads. The cost of telemetry data naturally increases as your use of EKS increases. At times, it can be challenging to balance your operational needs and measuring what matters to your business and keeping observability costs in check. This guide focuses on cost optimization strategies for the three pillars of observability: logs, metrics and traces. Each of these best practices can be applied independently to fit your organization\u2019s optimization goals.

"},{"location":"cost_optimization/cost_opt_observability/#logging","title":"Logging","text":"

Logging plays a vital role in monitoring and troubleshooting the applications in your cluster. There are several strategies that can be employed to optimize logging costs. The best practice strategies listed below include examining your log retention policies to implement granular controls on how long log data is kept, sending log data to different storage options based on importance, and utilizing log filtering to narrow down the types of logs messages that are stored. Efficiently managing log telemetry can lead to cost savings for your environments.

"},{"location":"cost_optimization/cost_opt_observability/#eks-control-plane","title":"EKS Control Plane","text":""},{"location":"cost_optimization/cost_opt_observability/#optimize-your-control-plane-logs","title":"Optimize Your Control Plane Logs","text":"

The Kubernetes control plane is a set of components that manage the clusters and these components send different types of information as log streams to a log group in Amazon CloudWatch. While there are benefits to enabling all control plane log types, you should be aware of the information in each log and the associated costs to storing all the log telemetry. You are charged for the standard CloudWatch Logs data ingestion and storage costs for logs sent to Amazon CloudWatch Logs from your clusters. Before enabling them, evaluate whether each log stream is necessary.

For example, in non-production clusters, selectively enable specific log types, such as the api server logs, only for analysis and deactivate afterward. But for production clusters, where you might not be able to reproduce events, and resolving issues requires more log information, then you can enable all log types. Further control plane cost optimization implementation details are in this blog post.

"},{"location":"cost_optimization/cost_opt_observability/#stream-logs-to-s3","title":"Stream Logs to S3","text":"

Another cost optimization best practice is streaming control plane logs to S3 via CloudWatch Logs subscriptions. Leveraging CloudWatch Logs subscriptions allows you to selectively forward logs to S3 which provides more cost efficient long term storage compared to retaining logs indefinitely in CloudWatch. For example, for production clusters, you can create a critical log group and leverage subscriptions to stream these logs to S3 after 15 days. This will ensure you have have quick access to the logs for analysis but also save on cost by moving logs to a more cost efficient storage.

Attention

As of 9/5/2023 EKS logs are classified as Vended Logs in Amazon CloudWatch Logs. Vended Logs are specific AWS service logs natively published by AWS services on behalf of the customer and available at volume discount pricing. Please visit the Amazon CloudWatch pricing page to learn more about Vended Logs pricing.

"},{"location":"cost_optimization/cost_opt_observability/#eks-data-plane","title":"EKS Data Plane","text":""},{"location":"cost_optimization/cost_opt_observability/#log-retention","title":"Log Retention","text":"

Amazon CloudWatch\u2019s default retention policy is to keep logs indefinitely and never expire, incurring storage costs applicable to your AWS region. In order to reduce the storage costs, you can customize the retention policy for each log group based on your workload requirements.

In a development environment, a lengthy retention period may not be necessary. But in a production environment, you can set a longer retention policy to meet troubleshooting, compliance, and capacity planning requirements. For example, if you are running an e-commerce application during the peak holiday season the system is under heavier load and issues can arise that may not be immediately noticeable, you will want to set a longer log retention for detailed troubleshooting and post event analysis.

You can configure your retention periods in the AWS CloudWatch console or AWS API with the duration from 1 day to 10 years based on each log group. Having a flexible retention period can save log storage costs, while also maintaining critical logs.

"},{"location":"cost_optimization/cost_opt_observability/#log-storage-options","title":"Log Storage Options","text":"

Storage is a large driver of observability costs therefore it is crucial to optimize your log storage strategy. Your strategies should align with your workloads requirements while maintaining performance and scalability. One strategy to reduce the costs of storing logs is to leverage AWS S3 buckets and its different storage tiers.

"},{"location":"cost_optimization/cost_opt_observability/#forward-logs-directly-to-s3","title":"Forward logs directly to S3","text":"

Consider forwarding less critical logs, such as development environments, directly to S3 instead of Cloudwatch. This can have an immediate impact on log storage costs. One option is to forward the logs straight to S3 using Fluentbit. You define this in the [OUTPUT] section, the destination where FluentBit transmits container logs for retention. Review additional configurations parameter here.

[OUTPUT]\n        Name eks_to_s3\n        Match application.* \n        bucket $S3_BUCKET name\n        region us-east-2\n        store_dir /var/log/fluentbit\n        total_file_size 30M\n        upload_timeout 3m\n
"},{"location":"cost_optimization/cost_opt_observability/#forward-logs-to-cloudwatch-only-for-short-term-analysis","title":"Forward logs to CloudWatch only for short term analysis","text":"

For more critical logs, such as a production environments where you might need to perform immediate analysis on the data, consider forwarding the logs to CloudWatch. You define this in the [OUTPUT] section, the destination where FluentBit transmits container logs for retention. Review additional configurations parameter here.

[OUTPUT]\n        Name eks_to_cloudwatch_logs\n        Match application.*\n        region us-east-2\n        log_group_name fluent-bit-cloudwatch\n        log_stream_prefix from-fluent-bit-\n        auto_create_group On\n

However, this will not have an instant affect on your cost savings. For additional savings, you will have to export these logs to Amazon S3.

"},{"location":"cost_optimization/cost_opt_observability/#export-to-amazon-s3-from-cloudwatch","title":"Export to Amazon S3 from CloudWatch","text":"

For storing Amazon CloudWatch logs long term, we recommend exporting your Amazon EKS CloudWatch logs to Amazon Simple Storage Service (Amazon S3). You can forward the logs to Amazon S3 bucket by creating an export task via the Console or the API. After you have done so, Amazon S3 presents many options to further reduce cost. You can define your own Amazon S3 Lifecycle rules to move your logs to a storage class that a fits your needs, or leverage the Amazon S3 Intelligent-Tiering storage class to have AWS automatically move data to long-term storage based on your usage pattern. Please refer to this blog for more details. For example, for your production environment logs reside in CloudWatch for more than 30 days then exported to Amazon S3 bucket. You can then use Amazon Athena to query the data in Amazon S3 bucket if you need to refer back to the logs at a later time.

"},{"location":"cost_optimization/cost_opt_observability/#reduce-log-levels","title":"Reduce Log Levels","text":"

Practice selective logging for your application. Both your applications and nodes output logs by default. For your application logs, adjust the log levels to align with the criticality of the workload and environment. For example, the java application below is outputting INFO logs which is the typical default application configuration and depending on the code can result in a high volume of log data.

import org.apache.log4j.*;\n\npublic class LogClass {\n   private static org.apache.log4j.Logger log = Logger.getLogger(LogClass.class);\n\n   public static void main(String[] args) {\n      log.setLevel(Level.INFO);\n\n      log.debug(\"This is a DEBUG message, check this out!\");\n      log.info(\"This is an INFO message, nothing to see here!\");\n      log.warn(\"This is a WARN message, investigate this!\");\n      log.error(\"This is an ERROR message, check this out!\");\n      log.fatal(\"This is a FATAL message, investigate this!\");\n   }\n}\n

In a development environment, change your log level to DEBUG, as this can help you debug issues or catch potential ones before they get into production.

      log.setLevel(Level.DEBUG);\n

In a production environment, consider modifying your log level to ERROR or FATAL. This will output log only when your application has errors, reducing the log output and help you focus on important data about your application status.

      log.setLevel(Level.ERROR);\n

You can fine tune various Kubernetes components log levels. For example, if you are using Bottlerocket as your EKS Node operating system, there are configuration settings that allow you to adjust the kubelet process log level. A snippet of this configuration setting is below. Note the default log level of 2 which adjusts the logging verbosity of the kubelet process.

[settings.kubernetes]\nlog-level = \"2\"\nimage-gc-high-threshold-percent = \"85\"\nimage-gc-low-threshold-percent = \"80\"\n

For a development environment, you can set the log level greater than 2 in order to view additional events, this is good for debugging. For a production environment, you can set the level to 0 in order to view only critical events.

"},{"location":"cost_optimization/cost_opt_observability/#leverage-filters","title":"Leverage Filters","text":"

When using a default EKS Fluentbit configuration to send container logs to Cloudwatch, FluentBit captures and send ALL application container logs enriched with Kubernetes metadata to Cloudwatch as shown in the [INPUT] configuration block below.

    [INPUT]\n        Name                tail\n        Tag                 application.*\n        Exclude_Path        /var/log/containers/cloudwatch-agent*, /var/log/containers/fluent-bit*, /var/log/containers/aws-node*, /var/log/containers/kube-proxy*\n        Path                /var/log/containers/*.log\n        Docker_Mode         On\n        Docker_Mode_Flush   5\n        Docker_Mode_Parser  container_firstline\n        Parser              docker\n        DB                  /var/fluent-bit/state/flb_container.db\n        Mem_Buf_Limit       50MB\n        Skip_Long_Lines     On\n        Refresh_Interval    10\n        Rotate_Wait         30\n        storage.type        filesystem\n        Read_from_Head      ${READ_FROM_HEAD}\n

The [INPUT] section above is ingesting all the container logs. This can generate a large amount of data that might not be necessary. Filtering out this data can reduce the amount of log data sent to CloudWatch therefore reducing your cost. You can apply a filter to you logs before it outputs to CloudWatch. Fluentbit defines this in the [FILTER] section. For example, filtering out the Kubernetes metadata from being appended to log events can reduce your log volume.

    [FILTER]\n        Name                nest\n        Match               application.*\n        Operation           lift\n        Nested_under        kubernetes\n        Add_prefix          Kube.\n\n    [FILTER]\n        Name                modify\n        Match               application.*\n        Remove              Kube.<Metadata_1>\n        Remove              Kube.<Metadata_2>\n        Remove              Kube.<Metadata_3>\n\n    [FILTER]\n        Name                nest\n        Match               application.*\n        Operation           nest\n        Wildcard            Kube.*\n        Nested_under        kubernetes\n        Remove_prefix       Kube.\n
"},{"location":"cost_optimization/cost_opt_observability/#metrics","title":"Metrics","text":"

Metrics provide valuable information regarding the performance of your system. By consolidating all system-related or available resource metrics in a centralized location, you gain the capability to compare and analyze performance data. This centralized approach enables you to make more informed strategic decisions, such as scaling up or scaling down resources. Additionally, metrics play a crucial role in assessing the health of resources, allowing you to take proactive measures when necessary. Generally observability costs scale with telemetry data collection and retention. Below are a few strategies you can implement to reduce the cost of metric telemetry: collecting only metrics that matter, reducing the cardinality of your telemetry data, and fine tuning the granularity of your telemetry data collection.

"},{"location":"cost_optimization/cost_opt_observability/#monitor-what-matters-and-collect-only-what-you-need","title":"Monitor what matters and collect only what you need","text":"

The first cost reduction strategy is to reduce the number of metrics you are collecting and in turn, reduce retention costs.

  1. Begin by working backwards from your and/or your stakeholder\u2019s requirements to determine the metrics that are most important. Success metrics are different for everyone! Know what good looks like and measure for it.
  2. Consider diving deep into the workloads you are supporting and identifying its Key Performance Indicators (KPIs) a.k.a 'Golden Signals'. These should align to business and stake-holder requirements. Calculating SLIs, SLOs, and SLAs using Amazon CloudWatch and Metric Math is crucial for managing service reliability. Follow the best practices outlined in this guide to effectively monitor and maintain the performance of your EKS environment.
  3. Then continue through the different layers of infrastructure to connect and correlate EKS cluster, node and additional infrastructure metrics to your workload KPIs. Store your business metrics and operational metrics in a system where you can correlate them together and draw conclusions based on observed impacts to both.
  4. EKS exposes metrics from the control plane, cluster kube-state-metrics, pods, and nodes. The relevance of all these metrics is dependent on your needs, however it\u2019s likely that you will not need every single metric across the different layers. You can use this EKS essential metrics guide as a baseline for monitoring the overall health of an EKS cluster and your workloads.

Here is an example prometheus scrape config where we are using the relabel_config to keep only kubelet metrics and metric_relabel_config to drop all container metrics.

  kubernetes_sd_configs:\n  - role: endpoints\n    namespaces:\n      names:\n      - kube-system\n  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n  tls_config:\n    insecure_skip_verify: true\n  relabel_configs:\n  - source_labels: [__meta_kubernetes_service_label_k8s_app]\n    regex: kubelet\n    action: keep\n\n  metric_relabel_configs:\n  - source_labels: [__name__]\n    regex: container_(network_tcp_usage_total|network_udp_usage_total|tasks_state|cpu_load_average_10s)\n    action: drop\n
"},{"location":"cost_optimization/cost_opt_observability/#reduce-cardinality-where-applicable","title":"Reduce cardinality where applicable","text":"

Cardinality refers to the uniqueness of the data values in combination with its dimensions (eg. prometheus labels) for a specific metrics set. High cardinality metrics have many dimensions and each dimension metric combination has higher uniqueness. Higher cardinality results in larger metric telemetry data size and storage needs which increases cost.

In the high cardinality example below, we see that the Metric, Latency, has Dimensions, RequestID, CustomerID, and Service and each Dimension has many unique values. Cardinality is the measure of the combination of the number of possible values per Dimension. In Prometheus, each set of unique dimensions/labels are consider as a new metric, therefore high cardinality means more metrics.

In EKS environments with many metrics and dimensions/labels per metric (Cluster, Namespace, Service, Pod, Container, etc), the cardinality tends to grow. In order to optimize cost, consider the cardinality of the metrics you are collecting carefully. For example, if you are aggregating a specific metric for visualization at the cluster level, then you can drop additional labels that are at a lower layer such as the namespace label.

In order to identify high cardinality metrics in prometheus you can run the following PROMQL query to determine which scrape targets have the highest number of metrics (cardinality):

topk_max(5, max_over_time(scrape_samples_scraped[1h]))\n

and the following PROMQL query can help you determine which scrape targets have the highest metrics churn (how many new metrics series were created in a given scrape) rates :

topk_max(5, max_over_time(scrape_series_added[1h]))\n

If you are using grafana you can use Grafana Lab\u2019s Mimirtool to analyze your grafana dashboards and prometheus rules to identify unused high-cardinality metrics. Follow this guide on how to use the mimirtool analyze and mimirtool analyze prometheus commands to identify active metrics which are not referenced in your dashboards.

"},{"location":"cost_optimization/cost_opt_observability/#consider-metric-granularity","title":"Consider metric granularity","text":"

Collecting metrics at a higher granularity like every second vs every minute can have a big impact on how much telemetry is collected and stored which increases cost. Determine sensible scrape or metrics collection intervals that balance between enough granularity to see transient issues and low enough to be cost effective. Decrease granularity for metrics that are used for capacity planning and larger time window analysis.

Below is a snippet from the default AWS Distro for Opentelemetry (ADOT) EKS Addon Collector configuration.

Attention

the global prometheus scrape interval is set to 15s. This scrape interval can be increased resulting in a decrease in the amount of metric data collected in prometheus.

apiVersion: opentelemetry.io/v1alpha1\nkind: OpenTelemetryCollector\nmetadata:\n  name: my-collector-amp\n\n...\n\n  config: |\n    extensions:\n      sigv4auth:\n        region: \"<YOUR_AWS_REGION>\"\n        service: \"aps\"\n\n    receivers:\n      #\n      # Scrape configuration for the Prometheus Receiver\n      # This is the same configuration used when Prometheus is installed using the community Helm chart\n      # \n      prometheus:\n        config:\n          global:\n  scrape_interval: 15s\n            scrape_timeout: 10s\n
"},{"location":"cost_optimization/cost_opt_observability/#tracing","title":"Tracing","text":"

The primary cost associated with tracing stem from trace storage generation. With tracing, the aim is to gather sufficient data to diagnose and understand performance aspects. However, as X-Ray traces costs are based on data forwarded to to X-Ray, erasing traces after it has been forward will not reduce your costs. Let\u2019s review ways to lower your costs for tracing while maintaining data for you to perform proper analysis.

"},{"location":"cost_optimization/cost_opt_observability/#apply-sampling-rules","title":"Apply Sampling rules","text":"

The X-Ray sampling rate is conservative by default. Define sampling rules where you can control the amount of data that you gather. This will improve performance efficiency while reducing costs. By decreasing the sampling rate, you can collect traces from the request only what your workloads needs while maintaining a lower cost structure.

For example, you have java application that you want to debug the traces of all the requests for 1 problematic route.

Configure via the SDK to load sampling rules from a JSON document

{\n\"version\": 2,\n  \"rules\": [\n    {\n\"description\": \"debug-eks\",\n      \"host\": \"*\",\n      \"http_method\": \"PUT\",\n      \"url_path\": \"/history/*\",\n      \"fixed_target\": 0,\n      \"rate\": 1,\n      \"service_type\": \"debug-eks\"\n    }\n  ],\n  \"default\": {\n\"fixed_target\": 1,\n    \"rate\": 0.1\n  }\n}\n

Via the Console

"},{"location":"cost_optimization/cost_opt_observability/#apply-tail-sampling-with-aws-distro-for-opentelemetry-adot","title":"Apply Tail Sampling with AWS Distro for OpenTelemetry (ADOT)","text":"

ADOT Tail Sampling allows you to control the volume of traces ingested in the service. However, Tail Sampling allows you to define the sampling policies after all the spans in the request have been completed instead of at the beginning. This further limits the amount of raw data transferred to CloudWatch, hence reducing cost.

For example, if you\u2019re sampling 1% of traffic to a landing page and 10% of the requests to a payment page this might leave you with 300 traces for an 30 minute period. With an ADOT Tail Sampling rule of that filters specific errors, you could be left with 200 traces which decreases the number of traces stored.

processors:\n  groupbytrace:\n    wait_duration: 10s\n    num_traces: 300 \n    tail_sampling:\n    decision_wait: 1s # This value should be smaller than wait_duration\n    policies:\n      - ..... # Applicable policies**\n  batch/tracesampling:\n    timeout: 0s # No need to wait more since this will happen in previous processors\n    send_batch_max_size: 8196 # This will still allow us to limit the size of the batches sent to subsequent exporters\n\nservice:\n  pipelines:\n    traces/tailsampling:\n      receivers: [otlp]\n      processors: [groupbytrace, tail_sampling, batch/tracesampling]\n      exporters: [awsxray]\n
"},{"location":"cost_optimization/cost_opt_observability/#leverage-amazon-s3-storage-options","title":"Leverage Amazon S3 Storage options","text":"

You should leverage AWS S3 bucket and its different storage classes to store the traces. Export traces to S3 before the retention period expires. Use Amazon S3 Lifecycle rules to move the trace data to the storage class that meets your requirements.

For example, if you have traces that are 90 days old, Amazon S3 Intelligent-Tiering can automatically move the data to long-term storage based on your usage pattern. You can use Amazon Athena to query the data in Amazon S3 if you need to refer back to the traces at a later time. This can further reduce your cost for distributed tracing.

"},{"location":"cost_optimization/cost_opt_observability/#additional-resources","title":"Additional Resources:","text":"
  • Observability Best Practices Guide
  • Best Practices Metrics Collection
  • AWS re:Invent 2022 - Observability best practices at Amazon (COP343)
  • AWS re:Invent 2022 - Observability: Best practices for modern applications (COP344)
"},{"location":"cost_optimization/cost_opt_storage/","title":"Cost Optimization - Storage","text":""},{"location":"cost_optimization/cost_opt_storage/#overview","title":"Overview","text":"

There are scenarios where you may want to run applications that need to preserve data for a short or long term basis. For such use cases, volumes can be defined and mounted by Pods so that their containers can tap into different storage mechanisms. Kubernetes supports different types of volumes for ephemeral and persistent storage. The choice of storage largely depends on application requirements. For each approach, there are cost implications, and the practices detailed below which will help you accomplish cost efficiency for workloads needing some form of storage in your EKS environments.

"},{"location":"cost_optimization/cost_opt_storage/#ephemeral-volumes","title":"Ephemeral Volumes","text":"

Ephemeral volumes are for applications that require transient local volumes but don't require data to be persisted after restarts. Examples of this include requirements for scratch space, caching, and read-only input data like configuration data and secrets. You can find more details of Kubernetes ephemeral volumes here. Most of ephemeral volumes (e.g. emptyDir, configMap, downwardAPI, secret, hostpath) are backed by locally-attached writable devices (usually the root disk) or RAM, so it's important to choose the most cost efficient and performant host volume.

"},{"location":"cost_optimization/cost_opt_storage/#using-ebs-volumes","title":"Using EBS Volumes","text":"

We recommend starting with gp3 as the host root volume. It is the latest general purpose SSD volume offered by Amazon EBS and also offers a lower price (up to 20%) per GB compared to gp2 volumes.

"},{"location":"cost_optimization/cost_opt_storage/#using-amazon-ec2-instance-stores","title":"Using Amazon EC2 Instance Stores","text":"

Amazon EC2 instance stores provide temporary block-level storage for your EC2 instances. The storage provided by EC2 instance stores is accessible through disks that are physically attached to the hosts. Unlike Amazon EBS, you can only attach instance store volumes when the instance is launched, and these volumes only exist during the lifetime of the instance. They cannot be detached and re-attached to other instances. You can learn more about Amazon EC2 instance stores here. There are no additional fees associated with an instance store volume. This makes them (instance store volumes) more cost efficient than the general EC2 instances with large EBS volumes.

To use local store volumes in Kubernetes, you should partition, configure, and format the disks using the Amazon EC2 user-data so that volumes can be mounted as a HostPath in the pod spec. Alternatively, you can leverage the Local Persistent Volume Static Provisioner to simplify local storage management. The Local Persistent Volume static provisioner allows you to access local instance store volumes through the standard Kubernetes PersistentVolumeClaim (PVC) interface. Furthermore, it will provision PersistentVolumes (PVs) that contains node affinity information to schedule Pods to the correct nodes. Although it uses Kubernetes PersistentVolumes, EC2 instance store volumes are ephemeral in nature. Data written to ephemeral disks is only available during the instance\u2019s lifetime. When the instance is terminated, so is the data. Please refer to this blog for more details.

Keep in mind that when using Amazon EC2 instance store volumes, the total IOPS limit is shared with the host and it binds Pods to a specific host. You should thoroughly review your workload requirements before adopting Amazon EC2 instance store volumes.

"},{"location":"cost_optimization/cost_opt_storage/#persistent-volumes","title":"Persistent Volumes","text":"

Kubernetes is typically associated with running stateless applications. However, there are scenarios where you may want to run microservices that need to preserve persistent data or information from one request to the next. Databases are a common example for such use cases. However, Pods, and the containers or processes inside them, are ephemeral in nature. To persist data beyond the lifetime of a Pod, you can use PVs to define access to storage at a specific location that is independent from the Pod. The costs associated with PVs is highly dependent on the type of storage being used and how applications are consuming it.

There are different types of storage options that support Kubernetes PVs on Amazon EKS listed here. The storage options covered below are Amazon EBS, Amazon EFS, Amazon FSx for Lustre, Amazon FSx for NetApp ONTAP.

"},{"location":"cost_optimization/cost_opt_storage/#amazon-elastic-block-store-ebs-volumes","title":"Amazon Elastic Block Store (EBS) Volumes","text":"

Amazon EBS volumes can be consumed as Kubernetes PVs to provide block-level storage volumes. These are well suited for databases that rely on random reads & writes and throughput-intensive applications that perform long, continuous reads and writes. The Amazon Elastic Block Store Container Storage Interface (CSI) driver allows Amazon EKS clusters to manage the lifecycle of Amazon EBS volumes for persistent volumes. The Container Storage Interface enables and facilitates interaction between Kubernetes and a storage system. When a CSI driver is deployed to your EKS cluster, you can access it\u2019s capabilities through the native Kubernetes storage resources such as Persistent Volumes (PVs), Persistent Volume Claims (PVCs) and Storage Classes (SCs). This link provides practical examples of how to interact with Amazon EBS volumes with Amazon EBS CSI driver.

"},{"location":"cost_optimization/cost_opt_storage/#choosing-the-right-volume","title":"Choosing the right volume","text":"

We recommend using the latest generation of block storage (gp3) as it provides the right balance between price and performance. It also allows you to scale volume IOPS and throughput independently of volume size without needing to provision additional block storage capacity. If you\u2019re currently using gp2 volumes, we highly recommend migrating to gp3 volumes. This blog explains how to migrate from gp2 on gp3 on Amazon EKS clusters.

When you have applications that require higher performance and need volumes larger than what a single gp3 volume can support, you should consider using io2 block express. This type of storage is ideal for your largest, most I/O intensive, and mission critical deployment such as SAP HANA or other large databases with low latency requirements. Keep in mind that an instance's EBS performance is bounded by the instance's performance limits, so not all the instances support io2 block express volumes. You can check the supported instance types and other considerations in this doc.

A single gp3 volume can support up to up to 16,000 max IOPS, 1,000 MiB/s max throughput, max 16TiB. The latest generation of Provisioned IOPS SSD volume that provides up to 256,000 IOPS, 4,000 MiB/s, throughput, and 64TiB.

Among these options, you should best tailor your storage performance and cost to the needs of your applications.

"},{"location":"cost_optimization/cost_opt_storage/#monitor-and-optimize-over-time","title":"Monitor and optimize over time","text":"

It's important to understand your application's baseline performance and monitor it for selected volumes to check if it's meeting your requirements/expectations or if it's over-provisioned (e.g. a scenario where provisioned IOPS are not being fully utilized).

Instead of allocating a large volume from the beginning, you can gradually increase the size of the volume as you accumulate data. You can dynamically re-size volumes using the volume resizing feature in the Amazon Elastic Block Store CSI driver (aws-ebs-csi-driver). Keep in mind that you can only increase the EBS volume size.

To identify and remove any dangling EBS volumes, you can use AWS trusted advisor\u2019s cost optimization category. This feature helps you identify unattached volumes or volumes with very low write activity for a period of time. There is a cloud-native open-source, read-only tool called Popeye that scans live Kubernetes clusters and reports potential issues with deployed resources and configurations. For example, it can scan for unused PVs and PVCs and check whether they are bound or whether there is any volume mount error.

For a deep dive on monitoring, please refer to the EKS cost optimization observability guide.

One other option you can consider is the AWS Compute Optimizer Amazon EBS volume recommendations. This tool automatically identifies the optimal volume configuration and correct level of performance needed. For example, it can be used for optimal settings pertaining to provisioned IOPS, volume sizes, and types of EBS volumes based on the maximum utilization during the past 14 days. It also quantifies the potential monthly cost savings derived from its recommendations. You can review this blog for more details.

"},{"location":"cost_optimization/cost_opt_storage/#backup-retention-policy","title":"Backup retention policy","text":"

You can back up the data on your Amazon EBS volumes by taking point-in-time snapshots. The Amazon EBS CSI driver supports volume snapshots. You can learn how to create a snapshot and restore an EBS PV using the steps outlined here.

Subsequent snapshots are incremental backups, meaning that only the blocks on the device that have changed after your most recent snapshot are saved. This minimizes the time required to create the snapshot and saves on storage costs by not duplicating data. However, growing the number of old EBS snapshots without a proper retention policy can cause unexpected costs when operating at scale. If you\u2019re directly backing up Amazon EBS volumes through AWS API, you can leverage Amazon Data Lifecycle Manager (DLM) that provides an automated, policy-based lifecycle management solution for Amazon Elastic Block Store (EBS) Snapshots and EBS-backed Amazon Machine Images (AMIs). The console makes it easier to automate the creation, retention, and deletion of EBS Snapshots and AMIs.

Note

There is currently no way to make use of Amazon DLM via the Amazon EBS CSI driver.

In a Kubernetes environment, you can leverage an open-source tool called Velero to backup your EBS Persistent Volumes. You can set a TTL flag when scheduling the job to expire backups. Here is a guide from Velero as an example.

"},{"location":"cost_optimization/cost_opt_storage/#amazon-elastic-file-system-efs","title":"Amazon Elastic File System (EFS)","text":"

Amazon Elastic File System (EFS) is a serverless, fully elastic file system that lets you share file data using standard file system interface and file system semantics for a broad spectrum of workloads and applications. Examples of workloads and applications include Wordpress and Drupal, developer tools like JIRA and Git, and shared notebook system such as Jupyter as well as home directories.

One of main benefits of Amazon EFS is that it can be mounted by multiple containers spread across multiple nodes and multiple availability zones. Another benefit is that you only pay for the storage you use. EFS file systems will automatically grow and shrink as you add and remove files which eliminates the need for capacity planning.

To use Amazon EFS in Kubernetes, you need to use the Amazon Elastic File System Container Storage Interface (CSI) Driver, aws-efs-csi-driver. Currently, the driver can dynamically create access points. However, the Amazon EFS file system has to be provisioned first and provided as an input to the Kubernetes storage class parameter.

"},{"location":"cost_optimization/cost_opt_storage/#choosing-the-right-efs-storage-class","title":"Choosing the right EFS storage class","text":"

Amazon EFS offers four storage classes.

Two standard storage classes:

  • Amazon EFS Standard
  • Amazon EFS Standard-Infrequent Access (EFS Standard-IA)

Two one-zone storage classes:

  • Amazon EFS One Zone
  • Amazon EFS One Zone-Infrequent Access (EFS One Zone-IA)

The Infrequent Access (IA) storage classes are cost-optimized for files that are not accessed every day. With Amazon EFS lifecycle management, you can move files that have not been accessed for the duration of the lifecycle policy (7, 14, 30, 60, or 90 days) to the IA storage classes which can reduce the storage cost by up to 92 percent compared to EFS Standard and EFS One Zone storage classes respectively.

With EFS Intelligent-Tiering, lifecycle management monitors the access patterns of your file system and automatically move files to the most optimal storage class.

Note

aws-efs-csi-driver currently doesn\u2019t have a control on changing storage classes, lifecycle management or Intelligent-Tiering. Those should be setup manually in the AWS console or through the EFS APIs.

Note

aws-efs-csi-driver isn\u2019t compatible with Window-based container images.

Note

There is a known memory issue when vol-metrics-opt-in (to emit volume metrics) is enabled due to the DiskUsage function that consumes an amount of memory that is proportional to the size of your filesystem. Currently, we recommend to disable the --vol-metrics-opt-in option on large filesystems to avoid consuming too much memory. Here is a github issue link for more details.

"},{"location":"cost_optimization/cost_opt_storage/#amazon-fsx-for-lustre","title":"Amazon FSx for Lustre","text":"

Lustre is a high-performance parallel file system commonly used in workloads requiring throughput up to hundreds of GB/s and sub-millisecond per-operation latencies. It\u2019s used for scenarios such as machine learning training, financial modeling, HPC, and video processing. Amazon FSx for Lustre provides a fully managed shared storage with the scalability and performance, seamlessly integrated with Amazon S3.

You can use Kubernetes persistent storage volumes backed by FSx for Lustre using the FSx for Lustre CSI driver from Amazon EKS or your self-managed Kubernetes cluster on AWS. See the Amazon EKS documentation for more details and examples.

"},{"location":"cost_optimization/cost_opt_storage/#link-to-amazon-s3","title":"Link to Amazon S3","text":"

It's recommended to link a highly durable long-term data repository residing on Amazon S3 with your FSx for Lustre file system. Once linked, large datasets are lazy-loaded as needed from Amazon S3 to FSx for Lustre file systems. You can also run your analyses and your results back to S3, and then delete your [Lustre] file system.

"},{"location":"cost_optimization/cost_opt_storage/#choosing-the-right-deployment-and-storage-options","title":"Choosing the right deployment and storage options","text":"

FSx for Lustre provides different deployment options. The first option is called scratch and it doesn\u2019t replicate data, while the second option is called persistent which, as the name implies, persists data.

The first option (scratch) can be used to reduce the cost of temporary shorter-term data processing. The persistent deployment option is designed for longer-term storage that automatically replicates data within an AWS Availability Zone. It also supports both SSD and HDD storage.

You can configure the desired deployment type under parameters in the FSx for lustre filesystem\u2019s Kubernetes StorageClass. Here is an link that provides sample templates.

Note

For latency-sensitive workloads or workloads requiring the highest levels of IOPS/throughput, you should choose SSD storage. For throughput-focused workloads that aren\u2019t latency-sensitive, you should choose HDD storage.

"},{"location":"cost_optimization/cost_opt_storage/#enable-data-compression","title":"Enable data compression","text":"

You can also enable data compression on your file system by specifying \u201cLZ4\u201d as the Data Compression Type. Once it\u2019s enabled, all newly-written files will be automatically compressed on FSx for Lustre before they are written to disk and uncompressed when they are read. LZ4 data compression algorithm is lossless so the original data can be fully reconstructed from the compressed data.

You can configure the data compression type as LZ4 under parameters in the FSx for lustre filesystem\u2019s Kubernetes StorageClass. Compression is disabled when the value is set to NONE, which is default. This link provides sample templates.

Note

Amazon FSx for Lustre isn\u2019t compatible with Window-based container images.

"},{"location":"cost_optimization/cost_opt_storage/#amazon-fsx-for-netapp-ontap","title":"Amazon FSx for NetApp ONTAP","text":"

Amazon FSx for NetApp ONTAP is a fully managed shared storage built on NetApp\u2019s ONTAP file system. FSx for ONTAP provides feature-rich, fast, and flexible shared file storage that\u2019s broadly accessible from Linux, Windows, and macOS compute instances running in AWS or on premises.

Amazon FSx for NetApp ONTAP supports two tiers of storage: 1/primary tier and 2/capacity pool tier.

The primary tier is a provisioned, high-performance SSD-based tier for active, latency-sensitive data. The fully elastic capacity pool tier is cost-optimized for infrequently accessed data, automatically scales as data is tiered to it, and offers virtually unlimited petabytes of capacity. You can enable data compression and deduplication on capacity pool storage and further reduce the amount of storage capacity your data consumes. NetApp\u2019s native, policy-based FabricPool feature continually monitors data access patterns, automatically transferring data bidirectionally between storage tiers to optimize performance and cost.

NetApp's Astra Trident provides dynamic storage orchestration using a CSI driver which allows Amazon EKS clusters to manage the lifecycle of persistent volumes PVs backed by Amazon FSx for NetApp ONTAP file systems. To get started, see Use Astra Trident with Amazon FSx for NetApp ONTAP in the Astra Trident documentation.

"},{"location":"cost_optimization/cost_opt_storage/#other-considerations","title":"Other considerations","text":""},{"location":"cost_optimization/cost_opt_storage/#minimize-the-size-of-container-image","title":"Minimize the size of container image","text":"

Once containers are deployed, container images are cached on the host as multiple layers. By reducing the size of images, the amount of storage required on the host can be reduced.

By using slimmed-down base images such as scratch images or distroless container images (that contain only your application and its runtime dependencies) from the beginning, you can reduce storage cost in addition to other ancillary benefits such as a reducing the attack surface area and shorter image pull times.

You should also consider using open source tools, such as Slim.ai that provides an easy, secure way to create minimal images.

Multiple layers of packages, tools, application dependencies, libraries can easily bloat the container image size. By using multi-stage builds, you can selectively copy artifacts from one stage to another, excluding everything that isn\u2019t necessary from the final image. You can check more image-building best practices here.

Another thing to consider is how long to persist cached images. You may want to clean up the stale images from the image cache when a certain amount of disk is utilized. Doing so will help make sure you have enough space for the host\u2019s operation. By default, the kubelet performs garbage collection on unused images every five minutes and on unused containers every minute.

To configure options for unused container and image garbage collection, tune the kubelet using a configuration file and change the parameters related to garbage collection using the KubeletConfiguration resource type.

You can learn more about it in the Kubernetes documentation.

"},{"location":"cost_optimization/cost_optimization_index/","title":"Amazon EKS Best Practices Guide for Cost Optimization","text":"

Cost Optimization is achieving your business outcomes at the lowest price point. By following the documentation in this guide you will optimize your Amazon EKS workloads.

"},{"location":"cost_optimization/cost_optimization_index/#general-guidelines","title":"General Guidelines","text":"

In the cloud, there are a number of general guidelines that can help you achieve cost optimization of your microservices: + Ensure that workloads running on Amazon EKS are independent of specific infrastructure types for running your containers, this will give greater flexibility with regards to running them on the least expensive types of infrastructure. While using Amazon EKS with EC2, there can be exceptions when we have workloads that require specific type of EC2 Instance types like requiring a GPU or other instance types, due to the nature of the workload. + Select optimally profiled container instances \u2014 profile your production or pre-production environments and monitor critical metrics like CPU and memory, using services like Amazon CloudWatch Container Insights for Amazon EKS or third party tools that are available in the Kubernetes ecosystem. This will ensure that we can allocate the right amount of resources and avoid wastage of resources. + Take advantage of the different purchasing options that are available in AWS for running EKS with EC2, e.g. On-Demand, Spot and Savings Plan.

"},{"location":"cost_optimization/cost_optimization_index/#eks-cost-optimization-best-practices","title":"EKS Cost Optimization Best Practices","text":"

There are three general best practice areas for cost optimization in the cloud:

  • Cost-effective resources (Auto Scaling, Down Scaling, Policies and Purchasing Options)
  • Expenditure awareness (Using AWS and third party tools)
  • Optimizing over time (Right Sizing)

As with any guidance there are trade-offs. Ensure you work with your organization to understand the priorities for this workload and which best practices are most important.

"},{"location":"cost_optimization/cost_optimization_index/#how-to-use-this-guide","title":"How to use this guide","text":"

This guide is meant for devops teams who are responsible for implementing and managing the EKS clusters and the workloads they support. The guide is organized into different best practice areas for easier consumption. Each topic has a list of recommendations, tools to use and best practices for cost optimization of your EKS clusters. The topics do not need to read in a particular order.

"},{"location":"cost_optimization/cost_optimization_index/#key-aws-services-and-kubernetes-features","title":"Key AWS Services and Kubernetes features","text":"

Cost optimization is supported by the following AWS services and features: + EC2 Instance types, Savings Plan (and Reserved Instances) and Spot Instances, at different prices. + Auto Scaling along with Kubernetes native Auto Scaling policies. Consider Savings Plan (Previously Reserved Instances) for predictable workloads. Use managed data stores like EBS and EFS, for elasticity and durability of the application data. + The Billing and Cost Management console dashboard along with AWS Cost Explorer provides an overview of your AWS usage. Use AWS Organizations for granular billing details. Details of several third party tools have also been shared. + Amazon CloudWatch Container Metrics provides metrics around usage of resources by the EKS cluster. In addition to the Kubernetes dashboard, there are several tools in the Kubernetes ecosystem that can be used to reduce wastage.

This guide includes a set of recommendations that you can use to improve the cost optimization of your Amazon EKS cluster.

"},{"location":"cost_optimization/cost_optimization_index/#feedback","title":"Feedback","text":"

This guide is being released on GitHub so as to collect direct feedback and suggestions from the broader EKS/Kubernetes community. If you have a best practice that you feel we ought to include in the guide, please file an issue or submit a PR in the GitHub repository. Our intention is to update the guide periodically as new features are added to the service or when a new best practice evolves.

"},{"location":"cost_optimization/optimizing_WIP/","title":"Optimizing over time (Right Sizing)","text":"

Right Sizing as per the AWS Well-Architected Framework, is \u201c\u2026 using the lowest cost resource that still meets the technical specifications of a specific workload\u201d.

When you specify the resource requests for the Containers in a Pod, the scheduler uses this information to decide which node to place the Pod on. When you specify a resource limits for a Container, the kubelet enforces those limits so that the running container is not allowed to use more of that resource than the limit you set. The details of how Kubernetes manages resources for containers are given in the documentation.

In Kubernetes, this means setting the right compute resources (CPU and memory are collectively referred to as compute resources) - setting the resource requests that align as close as possible to the actual utilization. The tools for getting the actual resource usags of Pods are given in the section on Rexommendations below.

Amazon EKS on AWS Fargate: When pods are scheduled on Fargate, the vCPU and memory reservations within the pod specification determine how much CPU and memory to provision for the pod. If you do not specify a vCPU and memory combination, then the smallest available combination is used (.25 vCPU and 0.5 GB memory). The list of vCPU and memory combinations that are available for pods running on Fargate are listed in the Amazon EKS User Guide.

Amazon EKS on EC2: When you create a Pod, you can specify how much of each resource like CPU and Memory, a Container needs. It is important we do not over-provision (which will lead to wastage) or under-provision (will lead to throttling) the resources allocated to the containers.

"},{"location":"cost_optimization/optimizing_WIP/#recommendations","title":"Recommendations","text":"

FairwindsOps Goldilocks: The FairwindsOps Goldilocks is a tool that creates a Vertical Pod Autoscaler (VPA) for each deployment in a namespace and then queries them for information. Once the VPAs are in place, we see recommendations appear in the Goldilocks dashboard.

Deploy the Vertical Pod Autoscaler as per the documentation.

Enable Namespace - Pick an application namespace and label it like so in order to see some data, in the following example we are specifying the default namespace:

$ kubectl label ns default goldilocks.fairwinds.com/enabled=true\n

Viewing the Dashboard - The default installation creates a ClusterIP service for the dashboard. You can access via port forward:

$ kubectl -n goldilocks port-forward svc/goldilocks-dashboard 8080:80\n

Then open your browser to http://localhost:8080

"},{"location":"cost_optimization/optimizing_WIP/#use-application-profiling-tools-like-cloudwatch-container-insights-and-prometheus-metrics-in-amazon-cloudwatch","title":"Use Application Profiling tools like CloudWatch Container Insights and Prometheus Metrics in Amazon CloudWatch","text":"

Use CloudWatch Container Insights to see how you can use native CloudWatch features to monitor your EKS Cluster performance. You can use CloudWatch Container Insights to collect, aggregate, and summarize metrics and logs from your containerized applications and microservices running on Amazon Elastic Kubernetes Service. The metrics include utilization for resources such as CPU, memory, disk, and network - which can help with right-sizing Pods and save costs.

Container Insights Prometheus Metrics Monitoring At present, support for Prometheus metrics is still in beta. CloudWatch Container Insights monitoring for Prometheus automates the discovery of Prometheus metrics from containerized systems and workloads. Prometheus is an open-source systems monitoring and alerting toolkit. All Prometheus metrics are collected in the ContainerInsights/Prometheus namespace.

The Metrics provided by cAdvisor and kube-state-metrics can be used for monitoring pods on Amazon EKS on AWS Fargate using Prometheus and Grafana, which can then be used to implement requests in your containers. Please refer to this blog for more details.

Right Size Guide: The right size guide (rsg) is a simple CLI tool that provides you with memory and CPU recommendations for your application. This tool works across container orchestrators, including Kubernetes and easy to deploy.

By using tools like CloudWatch Container Insights, Kube Resource Report, Goldilocks and others, applications running in the Kubernetes cluster can be right sized and potentially lower your costs.

"},{"location":"cost_optimization/optimizing_WIP/#resources","title":"Resources","text":"

Refer to the following resources to learn more about best practices for cost optimization.

"},{"location":"cost_optimization/optimizing_WIP/#documentation-and-blogs","title":"Documentation and Blogs","text":"
  • Amazon EKS Workshop - Setting up EKS CloudWatch Container Insights
  • Using Prometheus Metrics in Amazon CloudWatch
  • Monitoring Amazon EKS on AWS Fargate using Prometheus and Grafana
"},{"location":"cost_optimization/optimizing_WIP/#tools","title":"Tools","text":"
  • Right size guide
  • Fargate count
  • FairwindsOps Goldilocks
  • Choose Right Node Size
"},{"location":"karpenter/","title":"Karpenter Best Practices","text":""},{"location":"karpenter/#karpenter","title":"Karpenter","text":"

Karpenter is an open-source project that provides node lifecycle management for Kubernetes clusters. It automates provisioning and deprovisioning of nodes based on the scheduling needs of pods, allowing efficient scaling and cost optimization. Its main functions are:

  • Monitor pods that the Kubernetes scheduler cannot schedule due to resource constraints.
  • Evaluate the scheduling requirements (resource requests, node selectors, affinities, tolerations, etc.) of the unschedulable pods.
  • Provision new nodes that meet the requirements of those pods.
  • Remove nodes when they are no longer needed.

With Karpenter, you can define NodePools with constraints on node provisioning like taints, labels, requirements (instance types, zones, etc.), and limits on total provisioned resources. When deploying workloads, you can specify scheduling constraints in the pod spec like resource requests/limits, node selectors, node/pod affinities, tolerations, and topology spread constraints. Karpenter will then provision right sized nodes for those pods.

Reasons to use Karpenter

Before the launch of Karpenter, Kubernetes users relied primarily on Amazon EC2 Auto Scaling groups and the Kubernetes Cluster Autoscaler (CAS) to dynamically adjust the compute capacity of their clusters. With Karpenter, you don\u2019t need to create dozens of node groups to achieve the flexibility and diversity you get with Karpenter. Moreover, Karpenter is not as tightly coupled to Kubernetes versions (as CAS is) and doesn\u2019t require you to jump between AWS and Kubernetes APIs.

Karpenter consolidates instance orchestration responsibilities within a single system, which is simpler, more stable and cluster-aware. Karpenter was designed to overcome some of the challenges presented by Cluster Autoscaler by providing simplified ways to:

  • Provision nodes based on workload requirements.
  • Create diverse node configurations by instance type, using flexible NodePool options. Instead of managing many specific custom node groups, Karpenter could let you manage diverse workload capacity with a single, flexible NodePool.
  • Achieve improved pod scheduling at scale by quickly launching nodes and scheduling pods.

For information and documentation on using Karpenter, visit the karpenter.sh site.

"},{"location":"karpenter/#recommendations","title":"Recommendations","text":"

Best practices are divided into sections on Karpenter itself, NodePools, and pod scheduling.

"},{"location":"karpenter/#karpenter-best-practices_1","title":"Karpenter best practices","text":"

The following best practices cover topics related to Karpenter itself.

"},{"location":"karpenter/#use-karpenter-for-workloads-with-changing-capacity-needs","title":"Use Karpenter for workloads with changing capacity needs","text":"

Karpenter brings scaling management closer to Kubernetes native APIs than do Autoscaling Groups (ASGs) and Managed Node Groups (MNGs). ASGs and MNGs are AWS-native abstractions where scaling is triggered based on AWS level metrics, such as EC2 CPU load. Cluster Autoscaler bridges the Kubernetes abstractions into AWS abstractions, but loses some flexibility because of that, such as scheduling for a specific availability zone.

Karpenter removes a layer of AWS abstraction to bring some of the flexibility directly into Kubernetes. Karpenter is best used for clusters with workloads that encounter periods of high, spiky demand or have diverse compute requirements. MNGs and ASGs are good for clusters running workloads that tend to be more static and consistent. You can use a mix of dynamically and statically managed nodes, depending on your requirements.

"},{"location":"karpenter/#consider-other-autoscaling-projects-when","title":"Consider other autoscaling projects when...","text":"

You need features that are still being developed in Karpenter. Because Karpenter is a relatively new project, consider other autoscaling projects for the time being if you have a need for features that are not yet part of Karpenter.

"},{"location":"karpenter/#run-the-karpenter-controller-on-eks-fargate-or-on-a-worker-node-that-belongs-to-a-node-group","title":"Run the Karpenter controller on EKS Fargate or on a worker node that belongs to a node group","text":"

Karpenter is installed using a Helm chart. The Helm chart installs the Karpenter controller and a webhook pod as a Deployment that needs to run before the controller can be used for scaling your cluster. We recommend a minimum of one small node group with at least one worker node. As an alternative, you can run these pods on EKS Fargate by creating a Fargate profile for the karpenter namespace. Doing so will cause all pods deployed into this namespace to run on EKS Fargate. Do not run Karpenter on a node that is managed by Karpenter.

"},{"location":"karpenter/#no-custom-launch-templates-support-with-karpenter","title":"No custom launch templates support with Karpenter","text":"

There is no custom launch template support with v1beta1 APIs (v0.32+). You can use custom user data and/or directly specifying custom AMIs in the EC2NodeClass. More information on how to do this is available at NodeClasses.

"},{"location":"karpenter/#exclude-instance-types-that-do-not-fit-your-workload","title":"Exclude instance types that do not fit your workload","text":"

Consider excluding specific instances types with the node.kubernetes.io/instance-type key if they are not required by workloads running in your cluster.

The following example shows how to avoid provisioning large Graviton instances.

- key: node.kubernetes.io/instance-type\n  operator: NotIn\n  values:\n  - m6g.16xlarge\n  - m6gd.16xlarge\n  - r6g.16xlarge\n  - r6gd.16xlarge\n  - c6g.16xlarge\n
"},{"location":"karpenter/#enable-interruption-handling-when-using-spot","title":"Enable Interruption Handling when using Spot","text":"

Karpenter supports native interruption handling and can handle involuntary interruption events like Spot Instance interruptions, scheduled maintenance events, instance termination/stopping events that could disrupt your workloads. When Karpenter detects such events for nodes, it automatically taints, drains and terminates the affected nodes ahead of time to start graceful cleanup of workloads before disruption. For Spot interruptions with 2 minute notice, Karpenter quickly starts a new node so pods can be moved before the instance is reclaimed. To enable interruption handling, you configure the --interruption-queue CLI argument with the name of the SQS queue provisioned for this purpose. It is not advised to use Karpenter interruption handling alongside Node Termination Handler as explained here.

Pods that require checkpointing or other forms of graceful draining, requiring the 2-mins before shutdown should enable Karpenter interruption handling in their clusters.

"},{"location":"karpenter/#amazon-eks-private-cluster-without-outbound-internet-access","title":"Amazon EKS private cluster without outbound internet access","text":"

When provisioning an EKS Cluster into a VPC with no route to the internet, you have to make sure you\u2019ve configured your environment in accordance with the private cluster requirements that appear in EKS documentation. In addition, you need to make sure you\u2019ve created an STS VPC regional endpoint in your VPC. If not, you will see errors similar to those that appear below.

{\"level\":\"FATAL\",\"time\":\"2024-02-29T14:28:34.392Z\",\"logger\":\"controller\",\"message\":\"Checking EC2 API connectivity, WebIdentityErr: failed to retrieve credentials\\ncaused by: RequestError: send request failed\\ncaused by: Post \\\"https://sts.<region>.amazonaws.com/\\\": dial tcp 54.239.32.126:443: i/o timeout\",\"commit\":\"596ea97\"}\n

These changes are necessary in a private cluster because the Karpenter Controller uses IAM Roles for Service Accounts (IRSA). Pods configured with IRSA acquire credentials by calling the AWS Security Token Service (AWS STS) API. If there is no outbound internet access, you must create and use an AWS STS VPC endpoint in your VPC.

Private clusters also require you to create a VPC endpoint for SSM. When Karpenter tries to provision a new node, it queries the Launch template configs and an SSM parameter. If you do not have a SSM VPC endpoint in your VPC, it will cause the following error:

{\"level\":\"ERROR\",\"time\":\"2024-02-29T14:28:12.889Z\",\"logger\":\"controller\",\"message\":\"Unable to hydrate the AWS launch template cache, RequestCanceled: request context canceled\\ncaused by: context canceled\",\"commit\":\"596ea97\",\"tag-key\":\"karpenter.k8s.aws/cluster\",\"tag-value\":\"eks-workshop\"}\n...\n{\"level\":\"ERROR\",\"time\":\"2024-02-29T15:08:58.869Z\",\"logger\":\"controller.nodeclass\",\"message\":\"discovering amis from ssm, getting ssm parameter \\\"/aws/service/eks/optimized-ami/1.27/amazon-linux-2/recommended/image_id\\\", RequestError: send request failed\\ncaused by: Post \\\"https://ssm.<region>.amazonaws.com/\\\": dial tcp 67.220.228.252:443: i/o timeout\",\"commit\":\"596ea97\",\"ec2nodeclass\":\"default\",\"query\":\"/aws/service/eks/optimized-ami/1.27/amazon-linux-2/recommended/image_id\"}\n

There is no VPC endpoint for the Price List Query API. As a result, pricing data will go stale over time. Karpenter gets around this by including on-demand pricing data in its binary, but only updates that data when Karpenter is upgraded. Failed requests for pricing data will result in the following error messages:

{\"level\":\"ERROR\",\"time\":\"2024-02-29T15:08:58.522Z\",\"logger\":\"controller.pricing\",\"message\":\"retreiving on-demand pricing data, RequestError: send request failed\\ncaused by: Post \\\"https://api.pricing.<region>.amazonaws.com/\\\": dial tcp 18.196.224.8:443: i/o timeout; RequestError: send request failed\\ncaused by: Post \\\"https://api.pricing.<region>.amazonaws.com/\\\": dial tcp 18.185.143.117:443: i/o timeout\",\"commit\":\"596ea97\"}\n

Refer to this documentation to use Karpenter in a completely Private EKS Clusters and to know which VPC endpoints to be created.

"},{"location":"karpenter/#creating-nodepools","title":"Creating NodePools","text":"

The following best practices cover topics related to creating NodePools.

"},{"location":"karpenter/#create-multiple-nodepools-when","title":"Create multiple NodePools when...","text":"

When different teams are sharing a cluster and need to run their workloads on different worker nodes, or have different OS or instance type requirements, create multiple NodePools. For example, one team may want to use Bottlerocket, while another may want to use Amazon Linux. Likewise, one team might have access to expensive GPU hardware that wouldn\u2019t be needed by another team. Using multiple NodePools makes sure that the most appropriate assets are available to each team.

"},{"location":"karpenter/#create-nodepools-that-are-mutually-exclusive-or-weighted","title":"Create NodePools that are mutually exclusive or weighted","text":"

It is recommended to create NodePools that are either mutually exclusive or weighted to provide consistent scheduling behavior. If they are not and multiple NodePools are matched, Karpenter will randomly choose which to use, causing unexpected results. Useful examples for creating multiple NodePools include the following:

Creating a NodePool with GPU and only allowing special workloads to run on these (expensive) nodes:

# NodePool for GPU Instances with Taints\napiVersion: karpenter.sh/v1beta1\nkind: NodePool\nmetadata:\n  name: gpu\nspec:\n  disruption:\n    consolidateAfter: 1m0s\n    consolidationPolicy: WhenEmpty\n    expireAfter: Never\n  template:\n    metadata: {}\n    spec:\n      nodeClassRef:\n        name: default\n      requirements:\n      - key: node.kubernetes.io/instance-type\n        operator: In\n        values:\n        - p3.8xlarge\n        - p3.16xlarge\n      - key: kubernetes.io/os\n        operator: In\n        values:\n        - linux\n      - key: kubernetes.io/arch\n        operator: In\n        values:\n        - amd64\n      - key: karpenter.sh/capacity-type\n        operator: In\n        values:\n        - on-demand\n      taints:\n      - effect: NoSchedule\n        key: nvidia.com/gpu\n        value: \"true\"\n

Deployment with toleration for the taint:

# Deployment of GPU Workload will have tolerations defined\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: inflate-gpu\nspec:\n  ...\n    spec:\n      tolerations:\n      - key: \"nvidia.com/gpu\"\n        operator: \"Exists\"\n        effect: \"NoSchedule\"\n

For a general deployment for another team, the NodePool spec could include nodeAffinity. A Deployment could then use nodeSelectorTerms to match billing-team.

# NodePool for regular EC2 instances\napiVersion: karpenter.sh/v1beta1\nkind: NodePool\nmetadata:\n  name: generalcompute\nspec:\n  disruption:\n    expireAfter: Never\n  template:\n    metadata:\n      labels:\n        billing-team: my-team\n    spec:\n      nodeClassRef:\n        name: default\n      requirements:\n      - key: node.kubernetes.io/instance-type\n        operator: In\n        values:\n        - m5.large\n        - m5.xlarge\n        - m5.2xlarge\n        - c5.large\n        - c5.xlarge\n        - c5a.large\n        - c5a.xlarge\n        - r5.large\n        - r5.xlarge\n      - key: kubernetes.io/os\n        operator: In\n        values:\n        - linux\n      - key: kubernetes.io/arch\n        operator: In\n        values:\n        - amd64\n      - key: karpenter.sh/capacity-type\n        operator: In\n        values:\n        - on-demand\n

Deployment using nodeAffinity:

# Deployment will have spec.affinity.nodeAffinity defined\nkind: Deployment\nmetadata:\n  name: workload-my-team\nspec:\n  replicas: 200\n  ...\n    spec:\n      affinity:\n        nodeAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n            nodeSelectorTerms:\n              - matchExpressions:\n                - key: \"billing-team\"\n                  operator: \"In\"\n                  values: [\"my-team\"]\n
"},{"location":"karpenter/#use-timers-ttl-to-automatically-delete-nodes-from-the-cluster","title":"Use timers (TTL) to automatically delete nodes from the cluster","text":"

You can use timers on provisioned nodes to set when to delete nodes that are devoid of workload pods or have reached an expiration time. Node expiry can be used as a means of upgrading, so that nodes are retired and replaced with updated versions. See Expiration in the Karpenter documentation for information on using spec.disruption.expireAfter to configure node expiry.

"},{"location":"karpenter/#avoid-overly-constraining-the-instance-types-that-karpenter-can-provision-especially-when-utilizing-spot","title":"Avoid overly constraining the Instance Types that Karpenter can provision, especially when utilizing Spot","text":"

When using Spot, Karpenter uses the Price Capacity Optimized allocation strategy to provision EC2 instances. This strategy instructs EC2 to provision instances from the deepest pools for the number of instances that you are launching and have the lowest risk of interruption. EC2 Fleet then requests Spot instances from the lowest priced of these pools. The more instance types you allow Karpenter to utilize, the better EC2 can optimize your spot instance\u2019s runtime. By default, Karpenter will use all Instance Types EC2 offers in the region and availability zones your cluster is deployed in. Karpenter intelligently chooses from the set of all instance types based on pending pods to make sure your pods are scheduled onto appropriately sized and equipped instances. For example, if your pod does not require a GPU, Karpenter will not schedule your pod to an EC2 instance type supporting a GPU. When you're unsure about which instance types to use, you can run the Amazon ec2-instance-selector to generate a list of instance types that match your compute requirements. For example, the CLI takes memory vCPU, architecture, and region as input parameters and provides you with a list of EC2 instances that satisfy those constraints.

$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r ap-southeast-1\nc5.large\nc5a.large\nc5ad.large\nc5d.large\nc6i.large\nt2.medium\nt3.medium\nt3a.medium\n

You shouldn\u2019t place too many constraints on Karpenter when using Spot instances because doing so can affect the availability of your applications. Say, for example, all of the instances of a particular type are reclaimed and there are no suitable alternatives available to replace them. Your pods will remain in a pending state until the spot capacity for the configured instance types is replenished. You can reduce the risk of insufficient capacity errors by spreading your instances across different availability zones, because spot pools are different across AZs. That said, the general best practice is to allow Karpenter to use a diverse set of instance types when using Spot.

"},{"location":"karpenter/#scheduling-pods","title":"Scheduling Pods","text":"

The following best practices relate to deploying pods In a cluster using Karpenter for node provisioning.

"},{"location":"karpenter/#follow-eks-best-practices-for-high-availability","title":"Follow EKS best practices for high availability","text":"

If you need to run highly available applications, follow general EKS best practice recommendations. See Topology Spread in Karpenter documentation for details on how to spread pods across nodes and zones. Use Disruption Budgets to set the minimum available pods that need to be maintained, in case there are attempts to evict or delete pods.

"},{"location":"karpenter/#use-layered-constraints-to-constrain-the-compute-features-available-from-your-cloud-provider","title":"Use layered Constraints to constrain the compute features available from your cloud provider","text":"

Karpenter\u2019s model of layered constraints allows you to create a complex set of NodePool and pod deployment constraints to get the best possible matches for pod scheduling. Examples of constraints that a pod spec can request include the following:

  • Needing to run in availability zones where only particular applications are available. Say, for example, you have pod that has to communicate with another application that runs on an EC2 instance residing in a particular availability zone. If your aim is to reduce cross-AZ traffic in your VPC, you may want to co-locate the pods in the AZ where the EC2 instance is located. This sort of targeting is often accomplished using node selectors. For additional information on Node selectors, please refer to the Kubernetes documentation.
  • Requiring certain kinds of processors or other hardware. See the Accelerators section of the Karpenter docs for a podspec example that requires the pod to run on a GPU.
"},{"location":"karpenter/#create-billing-alarms-to-monitor-your-compute-spend","title":"Create billing alarms to monitor your compute spend","text":"

When you configure your cluster to automatically scale, you should create billing alarms to warn you when your spend has exceeded a threshold and add resource limits to your Karpenter configuration. Setting resource limits with Karpenter is similar to setting an AWS autoscaling group\u2019s maximum capacity in that it represents the maximum amount of compute resources that can be instantiated by a Karpenter NodePool.

Note

It is not possible to set a global limit for the whole cluster. Limits apply to specific NodePools.

The snippet below tells Karpenter to only provision a maximum of 1000 CPU cores and 1000Gi of memory. Karpenter will stop adding capacity only when the limit is met or exceeded. When a limit is exceeded the Karpenter controller will write memory resource usage of 1001 exceeds limit of 1000 or a similar looking message to the controller\u2019s logs. If you are routing your container logs to CloudWatch logs, you can create a metrics filter to look for specific patterns or terms in your logs and then create a CloudWatch alarm to alert you when your configured metrics threshold is breached.

For further information using limits with Karpenter, see Setting Resource Limits in the Karpenter documentation.

spec:\n  limits:\n    cpu: 1000\n    memory: 1000Gi\n

If you don\u2019t use limits or constrain the instance types that Karpenter can provision, Karpenter will continue adding compute capacity to your cluster as needed. While configuring Karpenter in this way allows your cluster to scale freely, it can also have significant cost implications. It is for this reason that we recommend that configuring billing alarms. Billing alarms allow you to be alerted and proactively notified when the calculated estimated charges in your account(s) exceed a defined threshold. See Setting up an Amazon CloudWatch Billing Alarm to Proactively Monitor Estimated Charges for additional information.

You may also want to enable Cost Anomaly Detection which is an AWS Cost Management feature that uses machine learning to continuously monitor your cost and usage to detect unusual spends. Further information can be found in the AWS Cost Anomaly Detection Getting Started guide. If you\u2019ve gone so far as to create a budget in AWS Budgets, you can also configure an action to notify you when a specific threshold has been breached. With budget actions you can send an email, post a message to an SNS topic, or send a message to a chatbot like Slack. For further information see Configuring AWS Budgets actions.

"},{"location":"karpenter/#use-the-karpentershdo-not-disrupt-annotation-to-prevent-karpenter-from-deprovisioning-a-node","title":"Use the karpenter.sh/do-not-disrupt annotation to prevent Karpenter from deprovisioning a node","text":"

If you are running a critical application on a Karpenter-provisioned node, such as a long running batch job or stateful application, and the node\u2019s TTL has expired, the application will be interrupted when the instance is terminated. By adding a karpenter.sh/do-not-disrupt annotation to the pod, you are instructing Karpenter to preserve the node until the Pod is terminated or the karpenter.sh/do-not-disrupt annotation is removed. See Distruption documentation for further information.

If the only non-daemonset pods left on a node are those associated with jobs, Karpenter is able to target and terminate those nodes so long as the job status is succeed or failed.

"},{"location":"karpenter/#configure-requestslimits-for-all-non-cpu-resources-when-using-consolidation","title":"Configure requests=limits for all non-CPU resources when using consolidation","text":"

Consolidation and scheduling in general work by comparing the pods resource requests vs the amount of allocatable resources on a node. The resource limits are not considered. As an example, pods that have a memory limit that is larger than the memory request can burst above the request. If several pods on the same node burst at the same time, this can cause some of the pods to be terminated due to an out of memory (OOM) condition. Consolidation can make this more likely to occur as it works to pack pods onto nodes only considering their requests.

"},{"location":"karpenter/#use-limitranges-to-configure-defaults-for-resource-requests-and-limits","title":"Use LimitRanges to configure defaults for resource requests and limits","text":"

Because Kubernetes doesn\u2019t set default requests or limits, a container\u2019s consumption of resources from the underlying host, CPU, and memory is unbound. The Kubernetes scheduler looks at a pod\u2019s total requests (the higher of the total requests from the pod\u2019s containers or the total resources from the pod\u2019s Init containers) to determine which worker node to schedule the pod onto. Similarly, Karpenter considers a pod\u2019s requests to determine which type of instance it provisions. You can use a limit range to apply a sensible default for a namespace, in case resource requests are not specified by some pods.

See Configure Default Memory Requests and Limits for a Namespace

"},{"location":"karpenter/#apply-accurate-resource-requests-to-all-workloads","title":"Apply accurate resource requests to all workloads","text":"

Karpenter is able to launch nodes that best fit your workloads when its information about your workloads requirements is accurate. This is particularly important if using Karpenter's consolidation feature.

See Configure and Size Resource Requests/Limits for all Workloads

"},{"location":"karpenter/#coredns-recommendations","title":"CoreDNS recommendations","text":""},{"location":"karpenter/#update-the-configuration-of-coredns-to-maintain-reliability","title":"Update the configuration of CoreDNS to maintain reliability","text":"

When deploying CoreDNS pods on nodes managed by Karpenter, given Karpenter's dynamic nature in rapidly terminating/creating new nodes to align with demand, it is advisable to adhere to the following best practices:

CoreDNS lameduck duration

CoreDNS readiness probe

This will ensure that DNS queries are not directed to a CoreDNS Pod that is not yet ready or has been terminated.

"},{"location":"karpenter/#karpenter-blueprints","title":"Karpenter Blueprints","text":"

As Karpenter takes an application-first approach to provision compute capacity for to the Kubernetes data plane, there are common workload scenarios that you might be wondering how to configure them properly. Karpenter Blueprints is a repository that includes a list of common workload scenarios following the best practices described here. You'll have all the resources you need to even create an EKS cluster with Karpenter configured, and test each of the blueprints included in the repository. You can combine different blueprints to finally create the one you need for your workload(s).

"},{"location":"karpenter/#additional-resources","title":"Additional Resources","text":"
  • Karpenter/Spot Workshop
  • Karpenter Node Provisioner
  • TGIK Karpenter
  • Karpenter vs. Cluster Autoscaler
  • Groupless Autoscaling with Karpenter
  • Tutorial: Run Kubernetes Clusters for Less with Amazon EC2 Spot and Karpenter
"},{"location":"networking/custom-networking/","title":"Custom Networking","text":"

By default, Amazon VPC CNI will assign Pods an IP address selected from the primary subnet. The primary subnet is the subnet CIDR that the primary ENI is attached to, usually the subnet of the node/host.

If the subnet CIDR is too small, the CNI may not be able to acquire enough secondary IP addresses to assign to your Pods. This is a common challenge for EKS IPv4 clusters.

Custom networking is one solution to this problem.

Custom networking addresses the IP exhaustion issue by assigning the node and Pod IPs from secondary VPC address spaces (CIDR). Custom networking support supports ENIConfig custom resource. The ENIConfig includes an alternate subnet CIDR range (carved from a secondary VPC CIDR), along with the security group(s) that the Pods will belong to. When custom networking is enabled, the VPC CNI creates secondary ENIs in the subnet defined under ENIConfig. The CNI assigns Pods an IP addresses from a CIDR range defined in a ENIConfig CRD.

Since the primary ENI is not used by custom networking, the maximum number of Pods you can run on a node is lower. The host network Pods continue to use IP address assigned to the primary ENI. Additionally, the primary ENI is used to handle source network translation and route Pods traffic outside the node.

"},{"location":"networking/custom-networking/#example-configuration","title":"Example Configuration","text":"

While custom networking will accept valid VPC range for secondary CIDR range, we recommend that you use CIDRs (/16) from the CG-NAT space, i.e. 100.64.0.0/10 or 198.19.0.0/16 as those are less likely to be used in a corporate setting than other RFC1918 ranges. For additional information about the permitted and restricted CIDR block associations you can use with your VPC, see IPv4 CIDR block association restrictions in the VPC and subnet sizing section of the VPC documentation.

As shown in the diagram below, the primary Elastic Network Interface (ENI) of the worker node still uses the primary VPC CIDR range (in this case 10.0.0.0/16) but the secondary ENIs use the secondary VPC CIDR Range (in this case 100.64.0.0/16). Now, in order to have the Pods use the 100.64.0.0/16 CIDR range, you must configure the CNI plugin to use custom networking. You can follow through the steps as documented here.

If you want the CNI to use custom networking, set the AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG environment variable to true.

kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true\n

When AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true, the CNI will assign Pod IP address from a subnet defined in ENIConfig. The ENIConfig custom resource is used to define the subnet in which Pods will be scheduled.

apiVersion : crd.k8s.amazonaws.com/v1alpha1\nkind : ENIConfig\nmetadata:\n  name: us-west-2a\nspec: \n  securityGroups:\n    - sg-0dff111a1d11c1c11\n  subnet: subnet-011b111c1f11fdf11\n

Upon creating the ENIconfig custom resources, you will need to create new worker nodes and drain the existing nodes. The existing worker nodes and Pods will remain unaffected.

"},{"location":"networking/custom-networking/#recommendations","title":"Recommendations","text":""},{"location":"networking/custom-networking/#use-custom-networking-when","title":"Use Custom Networking When","text":"

We recommend you to consider custom networking if you are dealing with IPv4 exhaustion and can\u2019t use IPv6 yet. Amazon EKS support for RFC6598 space enables you to scale Pods beyond RFC1918 address exhaustion challenges. Please consider using prefix delegation with custom networking to increase the Pods density on a node.

You might consider custom networking if you have a security requirement to run Pods on a different network with different security group requirements. When custom networking enabled, the pods use different subnet or security groups as defined in the ENIConfig than the node's primary network interface.

Custom networking is indeed an ideal option for deploying multiple EKS clusters and applications to connect on-premise datacenter services. You can increase the number of private addresses (RFC1918) accessible to EKS in your VPC for services such as Amazon Elastic Load Balancing and NAT-GW, while using non-routable CG-NAT space for your Pods across multiple clusters. Custom networking with the transit gateway and a Shared Services VPC (including NAT gateways across several Availability Zones for high availability) enables you to deliver scalable and predictable traffic flows. This blog post describes an architectural pattern that is one of the most recommended ways to connect EKS Pods to a datacenter network using custom networking.

"},{"location":"networking/custom-networking/#avoid-custom-networking-when","title":"Avoid Custom Networking When","text":""},{"location":"networking/custom-networking/#ready-to-implement-ipv6","title":"Ready to Implement IPv6","text":"

Custom networking can mitigate IP exhaustion issues, but it requires additional operational overhead. If you are currently deploying a dual-stack (IPv4/IPv6) VPC or if your plan includes IPv6 support, we recommend implementing IPv6 clusters instead. You can set up IPv6 EKS clusters and migrate your apps. In an IPv6 EKS cluster, both Kubernetes and Pods get an IPv6 address and can communicate in and out to both IPv4 and IPv6 endpoints. Please review best practices for Running IPv6 EKS Clusters.

"},{"location":"networking/custom-networking/#exhausted-cg-nat-space","title":"Exhausted CG-NAT Space","text":"

Furthermore, if you're currently utilizing CIDRs from the CG-NAT space or are unable to link a secondary CIDR with your cluster VPC, you may need to explore other options, such as using an alternative CNI. We strongly recommend that you either obtain commercial support or possess the in-house knowledge to debug and submit patches to the open source CNI plugin project. Refer Alternate CNI Plugins user guide for more details.

"},{"location":"networking/custom-networking/#use-private-nat-gateway","title":"Use Private NAT Gateway","text":"

Amazon VPC now offers private NAT gateway capabilities. Amazon's private NAT Gateway enables instances in private subnets to connect to other VPCs and on-premises networks with overlapping CIDRs. Consider utilizing the method described on this blog post to employ a private NAT gateway to overcome communication issues for the EKS workloads caused by overlapping CIDRs, a significant complaint expressed by our clients. Custom networking cannot address the overlapping CIDR difficulties on its own, and it adds to the configuration challenges.

The network architecture used in this blog post implementation follows the recommendations under Enable communication between overlapping networks in Amazon VPC documentation. As demonstrated in this blog post, you may expand the usage of private NAT Gateway in conjunction with RFC6598 addresses to manage customers' private IP exhaustion issues. The EKS clusters, worker nodes are deployed in the non-routable 100.64.0.0/16 VPC secondary CIDR range, whereas the private NAT gateway, NAT gateway are deployed to the routable RFC1918 CIDR ranges. The blog explains how a transit gateway is used to connect VPCs in order to facilitate communication across VPCs with overlapping non-routable CIDR ranges. For use cases in which EKS resources in a VPC's non-routable address range need to communicate with other VPCs that do not have overlapping address ranges, customers have the option of using VPC Peering to interconnect such VPCs. This method could provide potential cost savings as all data transit within an Availability Zone via a VPC peering connection is now free.

"},{"location":"networking/custom-networking/#unique-network-for-nodes-and-pods","title":"Unique network for nodes and Pods","text":"

If you need to isolate your nodes and Pods to a specific network for security reasons, we recommend that you deploy nodes and Pods to a subnet from a larger secondary CIDR block (e.g. 100.64.0.0/8). Following the installation of the new CIDR in your VPC, you can deploy another node group using the secondary CIDR and drain the original nodes to automatically redeploy the pods to the new worker nodes. For more information on how to implement this, see this blog post.

Custom networking is not used in the setup represented in the diagram below. Rather, Kubernetes worker nodes are deployed on subnets from your VPC's secondary VPC CIDR range, such as 100.64.0.0/10. You can keep the EKS cluster running (the control plane will remain on the original subnet/s), but the nodes and Pods will be moved to a secondary subnet/s. This is yet another, albeit unconventional, technique to mitigate the danger of IP exhaustion in a VPC. We propose draining the old nodes before redeploying the pods to the new worker nodes.

"},{"location":"networking/custom-networking/#automate-configuration-with-availability-zone-labels","title":"Automate Configuration with Availability Zone Labels","text":"

You can enable Kubernetes to automatically apply the corresponding ENIConfig for the worker node Availability Zone (AZ).

Kubernetes automatically adds the tag topology.kubernetes.io/zone to your worker nodes. Amazon EKS recommends using the availability zone as your ENI config name when you only have one secondary subnet (alternate CIDR) per AZ. Note that tag failure-domain.beta.kubernetes.io/zone is deprecated and replaced with the tag topology.kubernetes.io/zone.

  1. Set name field to the Availability Zone of your VPC.
  2. Enable automatic configuration with this command:
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true\n

if you have multiple secondary subnets per availability zone, you need create a specific ENI_CONFIG_LABEL_DEF. You might consider configuring ENI_CONFIG_LABEL_DEF as k8s.amazonaws.com/eniConfig and label nodes with custom eniConfig names, such as k8s.amazonaws.com/eniConfig=us-west-2a-subnet-1 and k8s.amazonaws.com/eniConfig=us-west-2a-subnet-2.

"},{"location":"networking/custom-networking/#replace-pods-when-configuring-secondary-networking","title":"Replace Pods when Configuring Secondary Networking","text":"

Enabling custom networking does not modify existing nodes. Custom networking is a disruptive action. Rather than doing a rolling replacement of all the worker nodes in your cluster after enabling custom networking, we suggest updating the AWS CloudFormation template in the EKS Getting Started Guide with a custom resource that calls a Lambda function to update the aws-node Daemonset with the environment variable to enable custom networking before the worker nodes are provisioned.

If you had any nodes in your cluster with running Pods before you switched to the custom CNI networking feature, you should cordon and drain the nodes to gracefully shutdown the Pods and then terminate the nodes. Only new nodes matching the ENIConfig label or annotations use custom networking, and hence the Pods scheduled on these new nodes can be assigned an IP from secondary CIDR.

"},{"location":"networking/custom-networking/#calculate-max-pods-per-node","title":"Calculate Max Pods per Node","text":"

Since the node\u2019s primary ENI is no longer used to assign Pod IP addresses, there is a decrease in the number of Pods you can run on a given EC2 instance type. To work around this limitation you can use prefix assignment with custom networking. With prefix assignment, each secondary IP is replaced with a /28 prefix on secondary ENIs.

Consider the maximum number of Pods for an m5.large instance with custom networking.

The maximum number of Pods you can run without prefix assignment is 29

  • ((3 ENIs - 1) * (10 secondary IPs per ENI - 1)) + 2 = 20

Enabling prefix attachments increases the number of Pods to 290.

  • (((3 ENIs - 1) * ((10 secondary IPs per ENI - 1) * 16)) + 2 = 290

However, we suggest setting max-pods to 110 rather than 290 because the instance has a rather small number of virtual CPUs. On bigger instances, EKS recommends a max pods value of 250. When utilizing prefix attachments with smaller instance types (e.g. m5.large), it is possible that you will exhaust the instance's CPU and memory resources well before its IP addresses.

Info

When the CNI prefix allocates a /28 prefix to an ENI, it has to be a contiguous block of IP addresses. If the subnet that the prefix is generated from is highly fragmented, the prefix attachment may fail. You can mitigate this from happening by creating a new dedicated VPC for the cluster or by reserving subnet a set of CIDR exclusively for prefix attachments. Visit Subnet CIDR reservations for more information on this topic.

"},{"location":"networking/custom-networking/#identify-existing-usage-of-cg-nat-space","title":"Identify Existing Usage of CG-NAT Space","text":"

Custom networking allows you to mitigate IP exhaustion issue, however it can\u2019t solve all the challenges. If you already using CG-NAT space for your cluster, or simply don\u2019t have the ability to associate a secondary CIDR with your cluster VPC, we suggest you to explore other options, like using an alternate CNI or moving to IPv6 clusters.

"},{"location":"networking/index/","title":"Amazon EKS Best Practices Guide for Networking","text":"

It is critical to understand Kubernetes networking to operate your cluster and applications efficiently. Pod networking, also called the cluster networking, is the center of Kubernetes networking. Kubernetes supports Container Network Interface (CNI) plugins for cluster networking.

Amazon EKS officially supports Amazon Virtual Private Cloud (VPC) CNI plugin to implement Kubernetes Pod networking. The VPC CNI provides native integration with AWS VPC and works in underlay mode. In underlay mode, Pods and hosts are located at the same network layer and share the network namespace. The IP address of the Pod is consistent from the cluster and VPC perspective.

This guide introduces the Amazon VPC Container Network Interface(VPC CNI) in the context of Kubernetes cluster networking. The VPC CNI is the default networking plugin supported by EKS and hence is the focus of the guide. The VPC CNI is highly configurable to support different use cases. This guide further includes dedicated sections on different VPC CNI use cases, operating modes, sub-components, followed by the recommendations.

Amazon EKS runs upstream Kubernetes and is certified Kubernetes conformant. Although you can use alternate CNI plugins, this guide does not provide recommendations for managing alternate CNIs. Check the EKS Alternate CNI documentation for a list of partners and resources for managing alternate CNIs effectively.

"},{"location":"networking/index/#kubernetes-networking-model","title":"Kubernetes Networking Model","text":"

Kubernetes sets the following requirements on cluster networking:

  • Pods scheduled on the same node must be able to communicate with other Pods without using NAT (Network Address Translation).
  • All system daemons (background processes, for example, kubelet) running on a particular node can communicate with the Pods running on the same node.
  • Pods that use the host network must be able to contact all other Pods on all other nodes without using NAT.

See the Kubernetes network model for details on what Kubernetes expects from compatible networking implementations. The following figure illustrates the relationship between Pod network namespaces and the host network namespace.

"},{"location":"networking/index/#container-networking-interface-cni","title":"Container Networking Interface (CNI)","text":"

Kubernetes supports CNI specifications and plugins to implement Kubernetes network model. A CNI consists of a specification (current version 1.0.0) and libraries for writing plugins to configure network interfaces in containers, along with a number of supported plugins. CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted.

The CNI plugin is enabled by passing kubelet the --network-plugin=cni command-line option. Kubelet reads a file from --cni-conf-dir (default /etc/cni/net.d) and uses the CNI configuration from that file to set up each Pod\u2019s network. The CNI configuration file must match the CNI specification (minimum v0.4.0) and any required CNI plugins referenced by the configuration must be present in the --cni-bin-dir directory (default /opt/cni/bin). If there are multiple CNI configuration files in the directory, the kubelet uses the configuration file that comes first by name in lexicographic order.

"},{"location":"networking/index/#amazon-virtual-private-cloud-vpc-cni","title":"Amazon Virtual Private Cloud (VPC) CNI","text":"

The AWS-provided VPC CNI is the default networking add-on for EKS clusters. VPC CNI add-on is installed by default when you provision EKS clusters. VPC CNI runs on Kubernetes worker nodes. The VPC CNI add-on consists of the CNI binary and the IP Address Management (ipamd) plugin. The CNI assigns an IP address from the VPC network to a Pod. The ipamd manages AWS Elastic Networking Interfaces (ENIs) to each Kubernetes node and maintains the warm pool of IPs. The VPC CNI provides configuration options for pre-allocation of ENIs and IP addresses for fast Pod startup times. Refer to Amazon VPC CNI for recommended plugin management best practices.

Amazon EKS recommends you specify subnets in at least two availability zones when you create a cluster. Amazon VPC CNI allocates IP addresses to Pods from the node subnets. We strongly recommend checking the subnets for available IP addresses. Please consider VPC and Subnet recommendations before deploying EKS clusters.

Amazon VPC CNI allocates a warm pool of ENIs and secondary IP addresses from the subnet attached to the node\u2019s primary ENI. This mode of VPC CNI is called the \"secondary IP mode.\" The number of IP addresses and hence the number of Pods (Pod density) is defined by the number of ENIs and the IP address per ENI (limits) as defined by the instance type. The secondary mode is the default and works well for small clusters with smaller instance types. Please consider using prefix mode if you are experiencing pod density challenges. You can also increase the available IP addresses on node for Pods by assigning prefixes to ENIs.

Amazon VPC CNI natively integrates with AWS VPC and allows users to apply existing AWS VPC networking and security best practices for building Kubernetes clusters. This includes the ability to use VPC flow logs, VPC routing policies, and security groups for network traffic isolation. By default, the Amazon VPC CNI applies security group associated with the primary ENI on the node to the Pods. Consider enabling security groups for Pods when you would like to assign different network rules for a Pod.

By default, VPC CNI assigns IP addresses to Pods from the subnet assigned to the primary ENI of a node. It is common to experience a shortage of IPv4 addresses when running large clusters with thousands of workloads. AWS VPC allows you to extend available IPs by assigning a secondary CIDRs to work around exhaustion of IPv4 CIDR blocks. AWS VPC CNI allows you to use a different subnet CIDR range for Pods. This feature of VPC CNI is called custom networking. You might consider using custom networking to use 100.64.0.0/10 and 198.19.0.0/16 CIDRs (CG-NAT) with EKS. This effectively allows you to create an environment where Pods no longer consume any RFC1918 IP addresses from your VPC.

Custom networking is one option to address the IPv4 address exhaustion problem, but it requires operational overhead. We recommend IPv6 clusters over custom networking to resolve this problem. Specifically, we recommend migrating to IPv6 clusters if you have completely exhausted all available IPv4 address space for your VPC. Evaluate your organization\u2019s plans to support IPv6, and consider if investing in IPv6 may have more long-term value.

EKS\u2019s support for IPv6 is focused on solving the IP exhaustion problem caused by a limited IPv4 address space. In response to customer issues with IPv4 exhaustion, EKS has prioritized IPv6-only Pods over dual-stack Pods. That is, Pods may be able to access IPv4 resources, but they are not assigned an IPv4 address from VPC CIDR range. The VPC CNI assigns IPv6 addresses to Pods from the AWS managed VPC IPv6 CIDR block.

"},{"location":"networking/index/#subnet-calculator","title":"Subnet Calculator","text":"

This project includes a Subnet Calculator Excel Document. This calculator document simulates the IP address consumption of a specified workload under different ENI configuration options, such as WARM_IP_TARGET and WARM_ENI_TARGET. The document includes two sheets, a first for Warm ENI mode, and a second for Warm IP mode. Review the VPC CNI guidance for more information on these modes.

Inputs: - Subnet CIDR Size - Warm ENI Target or Warm IP Target - List of instances - type, number, and number of workload pods scheduled per instance

Outputs: - Total number of pods hosted - Number of Subnet IPs consumed - Number of Subnet IPs remaining - Instance Level Details - Number of Warm IPs/ENIs per instance - Number of Active IPs/ENIs per instance

"},{"location":"networking/ip-optimization-strategies/","title":"Optimizing IP Address Utilization","text":"

Containerized environments are growing in scale at a rapid pace, thanks to application modernization. This means that more and more worker nodes and pods are being deployed.

The Amazon VPC CNI plugin assigns each pod an IP address from the VPC's CIDR(s). This approach provides full visibility of the Pod addresses with tools such as VPC Flow Logs and other monitoring solutions. Depending on your workload type this can cause a substantial number of IP addresses to be consumed by the pods.

When designing your AWS networking architecture, it is important to optimize Amazon EKS IP consumption at the VPC and at the node level. This will help you mitigate IP exhaustion issues and increase the pod density per node.

In this section, we will discuss techniques that can help you achieve these goals.

"},{"location":"networking/ip-optimization-strategies/#optimize-node-level-ip-consumption","title":"Optimize node-level IP consumption","text":"

Prefix delegation is a feature of Amazon Virtual Private Cloud (Amazon VPC) that allows you to assign IPv4 or IPv6 prefixes to your Amazon Elastic Compute Cloud (Amazon EC2) instances. It increases the IP addresses per network interface (ENI), which increases the pod density per node and improves your compute efficiency. Prefix delegation is also supported with Custom Networking.

For detailed information please see Prefix Delegation with Linux nodes and Prefix Delegation with Windows nodes sections.

"},{"location":"networking/ip-optimization-strategies/#mitigate-ip-exhaustion","title":"Mitigate IP exhaustion","text":"

To prevent your clusters from consuming all available IP addresses, we strongly recommend sizing your VPCs and subnets with growth in mind.

Adopting IPv6 is a great way to avoid these problems from the very beginning. However, for organizations whose scalability needs exceed the initial planning and cannot adopt IPv6, improving the VPC design is the recommended response to IP address exhaustion. The most commonly used technique among Amazon EKS customers is adding non-routable Secondary CIDRs to the VPC and configuring the VPC CNI to use this additional IP space when allocating IP addresses to Pods. This is commonly referred to as Custom Networking.

We will cover which variables of the Amazon VPC CNI you can use to optimize the warm pool of IPs assigned to your nodes. We will close this section with some other architectural patterns that are not intrinsic to Amazon EKS but can help mitigate IP exhaustion.

"},{"location":"networking/ip-optimization-strategies/#use-ipv6-recommended","title":"Use IPv6 (recommended)","text":"

Adopting IPv6 is the easiest way to work around the RFC1918 limitations; we strongly recommend you consider adopting IPv6 as your first option when choosing a network architecture. IPv6 provides a significantly larger total IP address space, and cluster administrators can focus on migrating and scaling applications without devoting effort towards working around IPv4 limits.

Amazon EKS clusters support both IPv4 and IPv6. By default, EKS clusters use IPv4 address space. Specifying an IPv6 based address space at cluster creation time will enable the use of IPv6. In an IPv6 EKS cluster, pods and services receive IPv6 addresses while maintaining the ability for legacy IPv4 endpoints to connect to services running on IPv6 clusters and vice versa. All the pod-to-pod communication within a cluster always occurs over IPv6. Within a VPC (/56), the IPv6 CIDR block size for IPv6 subnets is fixed at /64. This provides 2^64 (approximately 18 quintillion) IPv6 addresses allowing to scale your deployments on EKS.

For detailed information please see the Running IPv6 EKS Clusters section and for hands-on experience please see the Understanding IPv6 on Amazon EKS section of the Get hands-on with IPv6 workshop.

"},{"location":"networking/ip-optimization-strategies/#optimize-ip-consumption-in-ipv4-clusters","title":"Optimize IP consumption in IPv4 clusters","text":"

This section is dedicated to customers that are running legacy applications, and/or are not ready to migrate to IPv6. While we encourage all organizations to migrate to IPv6 as soon as possible, we recognize that some may still need to look into alternative approaches to scale their container workloads with IPv4. For this reason, we will also walk you through the architectural patterns to optimize IPv4 (RFC1918) address space consumption with Amazon EKS clusters.

"},{"location":"networking/ip-optimization-strategies/#plan-for-growth","title":"Plan for Growth","text":"

As a first line of defense against IP exhaustion, we strongly recommend to size your IPv4 VPCs and subnets with growth in mind, to prevent your clusters to consume all the available IP addresses. You will not be able to create new Pods or nodes if the subnets don\u2019t have enough available IP addresses.

Before building VPC and subnets, it is advised to work backwards from the required workload scale. For example, when clusters are built using eksctl (a simple CLI tool for creating and managing clusters on EKS) /19 subnets are created by default. A netmask of /19 is suitable for the majority of workload types allowing more than 8000 addresses to be allocated.

Attention

When you size VPCs and subnets, there might be a number of elements (other than pods and nodes) which can consume IP addresses, for example Load Balancers, RDS Databases and other in-vpc services.

Additionally, Amazon EKS, can create up to 4 elastic network interfaces (X-ENI) that are required to allow communication towards the control plane (more info here). During cluster upgrades, Amazon EKS creates new X-ENIs and deletes the old ones when the upgrade is successful. For this reason we recommend a netmask of at least /28 (16 IP addresses) for subnets associated with an EKS cluster.

You can use the sample EKS Subnet Calculator spreadsheet to plan for your network. The spreadsheet calculates IP usage based on workloads and VPC ENI configuration. The IP usage is compared to an IPv4 subnet to determine if the configuration and subnet size is sufficient for your workload. Keep in mind that, if subnets in your VPC run out of available IP addresses, we suggest creating a new subnet using the VPC\u2019s original CIDR blocks. Notice that now Amazon EKS now allows modification of cluster subnets and security groups.

"},{"location":"networking/ip-optimization-strategies/#expand-the-ip-space","title":"Expand the IP space","text":"

If you are about to exhaust the RFC1918 IP space, you can use the Custom Networking pattern to conserve routable IPs by scheduling Pods inside dedicated additional subnets. While custom networking will accept valid VPC range for secondary CIDR range, we recommend that you use CIDRs (/16) from the CG-NAT space, i.e. 100.64.0.0/10 or 198.19.0.0/16 as those are less likely to be used in a corporate setting than RFC1918 ranges.

For detailed information please see the dedicated section for Custom Networking.

"},{"location":"networking/ip-optimization-strategies/#optimize-the-ips-warm-pool","title":"Optimize the IPs warm pool","text":"

With the default configuration, the VPC CNI keeps an entire ENI (and associated IPs) in the warm pool. This may consume a large number of IPs, especially on larger instance types.

If your cluster subnet has a limited number of IP addresses available, scrutinize these VPC CNI configuration environment variables:

  • WARM_IP_TARGET
  • MINIMUM_IP_TARGET
  • WARM_ENI_TARGET

You can configure the value of MINIMUM_IP_TARGET to closely match the number of Pods you expect to run on your nodes. Doing so will ensure that as Pods get created, and the CNI can assign IP addresses from the warm pool without calling the EC2 API.

Please be mindful that setting the value of WARM_IP_TARGET too low, will cause additional calls to the EC2 API, and that might cause throttling of the requests. For large clusters use along with MINIMUM_IP_TARGET to avoid throttling of the requests.

To configure these options, you can download the aws-k8s-cni.yaml manifest and set the environment variables. At the time of writing, the latest release is located here. Check the version of the configuration value matches the installed VPC CNI version.

Warning

These settings will be reset to defaults when you update the CNI. Please take a backup of the CNI, before you update it. Review the configuration settings to determine if you need to reapply them after update is successful.

You can adjust the CNI parameters on the fly without downtime for your existing applications, but you should choose values that will support your scalability needs. For example, if you're working with batch workloads, we recommend updating the default WARM_ENI_TARGET to match the Pod scale needs. Setting WARM_ENI_TARGET to a high value always maintains the warm IP pool required to run large batch workloads and hence avoid data processing delays.

Warning

Improving your VPC design is the recommended response to IP address exhaustion. Consider solutions like IPv6 and Secondary CIDRs. Adjusting these values to minimize the number of Warm IPs should be a temporary solution after other options are excluded. Misconfiguring these values may interfere with cluster operation.

Before making any changes to a production system, be sure to review the considerations on this page.

"},{"location":"networking/ip-optimization-strategies/#monitor-ip-address-inventory","title":"Monitor IP Address Inventory","text":"

In addition to the solutions described above, it is also important to have visibility over IP utilization. You can monitor the IP addresses inventory of subnets using CNI Metrics Helper. Some of the metrics available are:

  • maximum number of ENIs the cluster can support
  • number of ENIs already allocated
  • number of IP addresses currently assigned to Pods
  • total and maximum number of IP address available

You can also set CloudWatch alarms to get notified if a subnet is running out of IP addresses.

Warning

Make sure DISABLE_METRICS variable for VPC CNI is set to false.

"},{"location":"networking/ip-optimization-strategies/#further-considerations","title":"Further considerations","text":"

There are other architectural patterns not intrinsic to Amazon EKS that can help with IP exhaustion. For example, you can optimize communication across VPCs or share a VPC across multiple accounts to limit the IPv4 address allocation.

Learn more about these patterns here:

  • Designing hyperscale Amazon VPC networks,
  • Build secure multi-account multi-VPC connectivity with Amazon VPC Lattice.
"},{"location":"networking/ipv6/","title":"Running IPv6 EKS Clusters","text":"

EKS in IPv6 mode solves the IPv4 exhaustion challenge often manifested in large scale EKS clusters. EKS\u2019s support for IPv6 is focused on resolving the IPv4 exhaustion problem, which stems from the limited size of the IPv4 address space. This is a significant concern raised by a number of our customers and is distinct from Kubernetes\u2019 \u201cIPv4/IPv6 dual-stack\u201d feature. EKS/IPv6 will also provide the flexability to inter-connect network boundaries using IPv6 CIDRs hence minimizing the chances to suffer from CIDR overlap, therefor solving a 2-Fold problem (In-Cluster, Cross-Cluster). When deploying EKS clusters in IPv6 mode (--ip-family ipv6), the action is not a reversible. In simple words EKS IPv6 support is enabled for the entire lifetime of your cluster.

In an IPv6 EKS cluster, Pods and Services will receive IPv6 addresses while maintaining compatibility with legacy IPv4 Endpoints. This includes the ability for external IPv4 endpoints to access in-cluster services, and Pods to access external IPv4 endpoints.

Amazon EKS IPv6 support leverages the native VPC IPv6 capabilities. Each VPC is allocated with an IPv4 address prefix (CIDR block size can be from /16 to /28) and a unique /56 IPv6 address prefix (fixed) from within Amazon\u2019s GUA (Global Unicast Address); you can assign a /64 address prefix to each subnet in your VPC. IPv4 features, like Route Tables, Network Access Control Lists, Peering, and DNS resolution, work the same way in an IPv6 enabled VPC. The VPC is then referred as dual-stack VPC, following dual-stack subnets, the following diagram depict the IPV4&IPv6 VPC foundation pattern that support EKS/IPv6 based clusters:

In the IPv6 world, every address is internet routable. By default, VPC allocates IPv6 CIDR from the public GUA range. VPCs do not support assigning private IPv6 addresses from the Unique Local Address (ULA) range as defined by RFC 4193 (fd00::/8 or fc00::/8). This is true even when you would like to assign an IPv6 CIDR owned by you. Egressing to the internet from Private Subnets is supported by implementing an egress-only internet gateway (EIGW) in a VPC, allowing outbound traffic while blocking all incoming traffic. The following diagram depict a Pod IPv6 Internet egress flow inside an EKS/IPv6 cluster:

Best practices for implementing IPv6 subnets can be found in the VPC user guide.

In an IPv6 EKS cluster, nodes and Pods receive public IPv6 addresses. EKS assigns IPv6 addresses to services based on Unique Local IPv6 Unicast Addresses (ULA). The ULA Service CIDR for an IPv6 cluster is automatically assigned during the cluster creation stage and cannot be specified, unlike IPv4. The following diagram depict an EKS/IPv6 based cluster control-plane & data-plan foundation pattern:

"},{"location":"networking/ipv6/#overview","title":"Overview","text":"

EKS/IPv6 is only supported in prefix mode (VPC-CNI Plug-in ENI IP assign mode). Learn more on Prefix Mode.

Prefix assignment only works on Nitro-based EC2 instances, hence EKS/IPv6 is only supported when the cluster data-plane uses EC2 Nitro-based instances.

In simple words an IPv6 prefix of /80 (Per worker-node) will yield ~10^14 IPv6 addresses, the limiting factor will no longer be IPs but Pod density (Resources wise).

IPv6 prefix assignment only occurs at the EKS worker-node bootstrap time. This behaviour is known to mitigate scenarios where high Pod churn EKS/IPv4 clusters are often delayed in Pod scheduling due to throttled API calls generated by the VPC CNI plug-in (ipamd) aimed to allocate Private IPv4 addresses in a timely fashion. It is also known to make the VPC-CNI plug-in advanced knobs tuning WARM_IP/ENI, MINIMUM_IP unnecessarily.

The following diagram zooms into an IPv6 worker-node Elastic Network Interface (ENI):

Every EKS worker-node is assigned with IPv4 and IPv6 addresses, along with corresponding DNS entries. For a given worker-node, only a single IPv4 address from the dual-stack subnet is consumed. EKS support for IPv6 enables you to communicate with IPv4 endpoints (AWS, on-premise, internet) through a highly opinionated egress-only IPv4 model. EKS implements a host-local CNI plugin, secondary to the VPC CNI plugin, which allocates and configures an IPv4 address for a Pod. The CNI plugin configures a host-specific non-routable IPv4 address for a Pod from the 169.254.172.0/22 range. The IPv4 address assigned to the Pod is unique to the worker-node and is not advertised beyond the worker-node. 169.254.172.0/22 provides up to 1024 unique IPv4 addresses which can support large instance types.

The following diagram depict the flow of an IPv6 Pod connecting to an IPv4 endpoint outside the cluster boundary (non-internet):

In the above diagram Pods will perform a DNS lookup for the endpoint and, upon receiving an IPv4 \u201cA\u201d response, Pod\u2019s node-only unique IPv4 address is translated through source network address translation (SNAT) to the Private IPv4 (VPC) address of the primary network interface attached to the EC2 Worker-node.

EKS/IPv6 Pods will also need to connect to IPv4 endpoints over the internet using public IPv4 Addresses, to achieve that a similar flow exists. The following diagram depict the flow of an IPv6 Pod connecting to an IPv4 endpoint outside the cluster boundary (internet routable):

In the above diagram Pods will perform a DNS lookup for the endpoint and, upon receiving an IPv4 \u201cA\u201d response, Pod\u2019s node-only unique IPv4 address is translated through source network address translation (SNAT) to the Private IPv4 (VPC) address of the primary network interface attached to the EC2 Worker-node. The Pod IPv4 Address (Source IPv4: EC2 Primary IP) is then routed to the IPv4 NAT Gateway where the EC2 Primary IP is translated (SNAT) into a valid internet routable IPv4 Public IP Address (NAT Gateway Assigned Public IP).

Any Pod-to-Pod communication across the nodes always uses an IPv6 address. VPC CNI configures iptables to handle IPv6 while blocking any IPv4 connections.

Kubernetes services will receive only IPv6 addresses (ClusterIP) from Unique Local IPv6 Unicast Addresses (ULA). The ULA Service CIDR for an IPv6 cluster is automatically assigned during EKS cluster creation stage and cannot be modified. The following diagram depict the Pod to Kubernetes Service flow:

Services are exposed to the internet using an AWS load balancer. The load balancer receives public IPv4 and IPv6 addresses, a.k.a dual-stack load balancer. For IPv4 clients accessing IPv6 cluster kubernetes services, the load balancer does IPv4 to IPv6 translation.

Amazon EKS recommends running worker nodes and Pods in private subnets. You can create public load balancers in the public subnets that load balance traffic to Pods running on nodes that are in private subnets. The following diagram depict an internet IPv4 user accessing an EKS/IPv6 Ingress based service:

Note: The above pattern requires to deploy the most recent version of the AWS load balancer controller

"},{"location":"networking/ipv6/#eks-control-plane-data-plane-communication","title":"EKS Control Plane <-> Data Plane communication","text":"

EKS will provision Cross-Account ENIs (X-ENIs) in dual stack mode (IPv4/IPv6). Kubernetes node components such as kubelet and kube-proxy are configured to support dual stack. Kubelet and kube-proxy run in a hostNetwork mode and bind to both IPv4 and IPv6 addresses attached to the primary network interface of a node. The Kubernetes api-server communicates to Pods and node components via the X-ENIs is IPv6 based. Pods communicate with the api-servers via the X-ENIs, and Pod to api-server communication always uses IPv6 mode.

"},{"location":"networking/ipv6/#recommendations","title":"Recommendations","text":""},{"location":"networking/ipv6/#maintain-access-to-ipv4-eks-apis","title":"Maintain Access to IPv4 EKS APIs","text":"

EKS APIs are accessible by IPv4 only. This also includes the Cluster API Endpoint. You will not be able to access cluster endpoints and APIs from an IPv6 only network. It is required that your network supports (1) an IPv6 transition mechanism such as NAT64/DNS64 that facilitates communication between IPv6 and IPv4 hosts and (2) a DNS service that supports translations of IPv4 endpoints.

"},{"location":"networking/ipv6/#schedule-based-on-compute-resources","title":"Schedule Based on Compute Resources","text":"

A single IPv6 prefix is sufficient to run many Pods on a single node. This also effectively removes ENI and IP limitations on the maximum number of Pods on a node. Although IPv6 removes direct dependency on max-Pods, when using prefix attachments with smaller instance types like the m5.large, you\u2019re likely to exhaust the instance\u2019s CPU and memory resources long before you exhaust its IP addresses. You must set the EKS recommended maximum Pod value by hand if you are using self-managed node groups or a managed node group with a custom AMI ID.

You can use the following formula to determine the maximum number of Pods you can deploy on a node for a IPv6 EKS cluster.

  • ((Number of network interfaces for instance type (number of prefixes per network interface-1)* 16) + 2

  • ((3 ENIs)((10 secondary IPs per ENI-1) 16)) + 2 = 460 (real)

Managed node groups automatically calculate the maximum number of Pods for you. Avoid changing EKS\u2019s recommended value for the maximum number of Pods to avoid Pod scheduling failures due to resource limitations.

"},{"location":"networking/ipv6/#evaluate-purpose-of-existing-custom-networking","title":"Evaluate Purpose of Existing Custom Networking","text":"

If custom networking is currently enabled, Amazon EKS recommends re-evaluating your need for it with IPv6. If you chose to use custom networking to address the IPv4 exhaustion issue, it is no longer necessary with IPv6. If you are utilizing custom networking to satisfy a security requirement, such as a separate network for nodes and Pods, you are encouraged to submit an EKS roadmap request.

"},{"location":"networking/ipv6/#fargate-pods-in-eksipv6-cluster","title":"Fargate Pods in EKS/IPv6 Cluster","text":"

EKS supports IPv6 for Pods running on Fargate. Pods running on Fargate will consume IPv6 and VPC Routable Private IPv4 addresses carved from the VPC CIDR ranges (IPv4&IPv6). In simple words your EKS/Fargate Pods cluster wide density will be limited to the available IPv4 and IPv6 addresses. It is recommended to size your dual-stack subnets/VPC CIDRs for future growth. You will not be able to schedule new Fargate Pods if the underlying subnet does not contain an available IPv4 address, irrespective of IPv6 available addresses.

"},{"location":"networking/ipv6/#deploy-the-aws-load-balancer-controller-lbc","title":"Deploy the AWS Load Balancer Controller (LBC)","text":"

The upstream in-tree Kubernetes service controller does not support IPv6. We recommend using the most recent version of the AWS Load Balancer Controller add-on. The LBC will only deploy a dual-stack NLB or a dual-stack ALB upon consuming corresponding kubernetes service/ingress definition annotated with: \"alb.ingress.kubernetes.io/ip-address-type: dualstack\" and \"alb.ingress.kubernetes.io/target-type: ip\"

AWS Network Load Balancer does not support dual-stack UDP protocol address types. If you have strong requirements for low-latency, real-time streaming, online gaming, and IoT, we recommend running IPv4 clusters. To learn more about managing health checks for UDP services, please refer to \u201cHow to route UDP traffic into Kubernetes\u201d.

"},{"location":"networking/ipvs/","title":"Running kube-proxy in IPVS Mode","text":"

EKS in IP Virtual Server (IPVS) mode solves the network latency issue often seen when running large clusters with over 1,000 services with kube-proxy running in legacy iptables mode. This performance issue is the result of sequential processing of iptables packet filtering rules for each packet. This latency issue has been addressed in nftables, the successor to iptables. However, as of the time of this writing, kube-proxy is still under development to make use of nftables. To get around this issue, you can configure your cluster to run kube-proxy in IPVS mode.

"},{"location":"networking/ipvs/#overview","title":"Overview","text":"

IPVS, which has been GA since Kubernetes version 1.11, uses hash tables rather than linear searching to process packets, providing efficiency for clusters with thousands of nodes and services. IPVS was designed for load balancing, making it a suitable solution for Kubernetes networking performance issues.

IPVS offers several options for distributing traffic to backend pods. Detailed information for each option can be found in the official Kubernetes documentation, but a simple list is shown below. Round Robin and Least Connections are among the most popular choices for IPVS load balancing options in Kubernetes.

- rr (Round Robin)\n- wrr (Weighted Round Robin)\n- lc (Least Connections)\n- wlc (Weighted Least Connections)\n- lblc (Locality Based Least Connections)\n- lblcr (Locality Based Least Connections with Replication)\n- sh (Source Hashing)\n- dh (Destination Hashing)\n- sed (Shortest Expected Delay)\n- nq (Never Queue)\n

"},{"location":"networking/ipvs/#implementation","title":"Implementation","text":"

Only a few steps are required to enable IPVS in your EKS cluster. The first thing you need to do is ensure your EKS worker node images have the Linux Virtual Server administration ipvsadm package installed. To install this package on a Fedora based image, such as Amazon Linux 2023, you can run the following command on the worker node instance.

sudo dnf install -y ipvsadm\n
On a Debian based image, such as Ubuntu, the installation command would look like this.
sudo apt-get install ipvsadm\n

Next, you need to load the kernel modules for the IPVS configuration options listed above. We recommend writing these modules to a file inside of the /etc/modules-load.d/ directory so that they survive a reboot.

sudo sh -c 'cat << EOF > /etc/modules-load.d/ipvs.conf\nip_vs\nip_vs_rr\nip_vs_wrr\nip_vs_lc\nip_vs_wlc\nip_vs_lblc\nip_vs_lblcr\nip_vs_sh\nip_vs_dh\nip_vs_sed\nip_vs_nq\nnf_conntrack\nEOF'\n
You can run the following command to load these modules on a machine that is already running.
sudo modprobe ip_vs \nsudo modprobe ip_vs_rr\nsudo modprobe ip_vs_wrr\nsudo modprobe ip_vs_lc\nsudo modprobe ip_vs_wlc\nsudo modprobe ip_vs_lblc\nsudo modprobe ip_vs_lblcr\nsudo modprobe ip_vs_sh\nsudo modprobe ip_vs_dh\nsudo modprobe ip_vs_sed\nsudo modprobe ip_vs_nq\nsudo modprobe nf_conntrack\n

Note

It is highly recommended to execute these worker node steps as part of you worker node's bootstrapping process via user data script or in any build scripts executed to build a custom worker node AMI.

Next, you will configure your cluster's kube-proxy DaemonSet to run in IPVS mode. This is done by setting the kube-proxy mode to ipvs and the ipvs scheduler to one of the load balancing options listed above, for example: rr for Round Robin.

Warning

This is a disruptive change and should be performed in off-hours. We recommend making these changes during initial EKS cluster creation to minimize impacts.

You can issue an AWS CLI command to enable IPVS by updating the kube-proxy EKS Add-on.

aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name kube-proxy \\\n  --configuration-values '{\"ipvs\": {\"scheduler\": \"rr\"}, \"mode\": \"ipvs\"}' \\\n  --resolve-conflicts OVERWRITE\n
Or you can do this by modifying the kube-proxy-config ConfigMap in your cluster.
kubectl -n kube-system edit cm kube-proxy-config\n
Find the scheduler setting under ipvs and set the value to one of the IPVS load balancing options listed above, for example: rr for Round Robin. Find the mode setting, which defaults to iptables, and change the value to ipvs. The result of either option should look similar to the configuration below.
  iptables:\n    masqueradeAll: false\n    masqueradeBit: 14\n    minSyncPeriod: 0s\n    syncPeriod: 30s\n  ipvs:\n    excludeCIDRs: null\n    minSyncPeriod: 0s\n    scheduler: \"rr\"\n    syncPeriod: 30s\n  kind: KubeProxyConfiguration\n  metricsBindAddress: 0.0.0.0:10249\n  mode: \"ipvs\"\n  nodePortAddresses: null\n  oomScoreAdj: -998\n  portRange: \"\"\n  udpIdleTimeout: 250ms\n

If your worker nodes were joined to your cluster prior to making these changes, you will need to restart the kube-proxy DaemonSet.

kubectl -n kube-system rollout restart ds kube-proxy\n

"},{"location":"networking/ipvs/#validation","title":"Validation","text":"

You can validate that your cluster and worker nodes are running in IPVS mode by issuing the following command on one of your worker nodes.

sudo ipvsadm -L\n

At a minimum, you should see a result similar to the one below, showing entries for the Kubernetes API Server service at 10.100.0.1 and the CoreDNS service at 10.100.0.10.

IP Virtual Server version 1.2.1 (size=4096)\nProt LocalAddress:Port Scheduler Flags\n  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn\nTCP  ip-10-100-0-1.us-east-1. rr\n  -> ip-192-168-113-81.us-eas Masq        1      0          0\n  -> ip-192-168-162-166.us-ea Masq        1      1          0\nTCP  ip-10-100-0-10.us-east-1 rr\n  -> ip-192-168-104-215.us-ea Masq        1      0          0\n  -> ip-192-168-123-227.us-ea Masq        1      0          0\nUDP  ip-10-100-0-10.us-east-1 rr\n  -> ip-192-168-104-215.us-ea Masq        1      0          0\n  -> ip-192-168-123-227.us-ea Masq        1      0          0\n

Note

This example output comes from an EKS cluster with a service IP address range of 10.100.0.0/16.

"},{"location":"networking/loadbalancing/loadbalancing/","title":"Load Balancing","text":"

Load Balancers receive incoming traffic and distribute it across targets of the intended application hosted in an EKS Cluster. This improves the resilience of the application. When deployed in an EKS Cluster the AWS Load Balancer controller will create and manage AWS Elastic Load Balancers for that cluster. When a Kubernetes Service of type LoadBalancer is created, the AWS Load Balancer controller creates a Network Load Balancer (NLB) which load balances received traffic at Layer 4 of the OSI model. While when a Kubernetes Ingress object is created, the AWS Load Balancer Controller creates an Application Load Balancer (ALB) which load balances traffic at Layer 7 of the OSI model.

"},{"location":"networking/loadbalancing/loadbalancing/#choosing-load-balancer-type","title":"Choosing Load Balancer Type","text":"

The AWS Elastic Load Balancing portfolio supports the following load balancers: Application Load Balancers (ALB), Network Load Balancers (NLB), Gateway Load Balancers (GWLB), and Classic Load Balancers (CLB). This best practices section will focus on the ALB and NLB which are the two which are most relevant for EKS Clusters.

The main consideration in choosing the type of load balancer is the workload requirements.

For more detailed information and as a reference for all AWS Load balancers, see Product Comparisons

"},{"location":"networking/loadbalancing/loadbalancing/#choose-the-application-load-balancer-alb-if-your-workload-is-httphttps","title":"Choose the Application Load Balancer (ALB) if your workload is HTTP/HTTPS","text":"

If a workloads requires load balancing at Layer 7 of the OSI Model, the AWS Load Balancer Controller can be used to provision an ALB; we cover the provisioning in the following section. The ALB is controlled and configured by the Ingress resource mentioned earlier and routes HTTP or HTTPS traffic to different Pods within the cluster. The ALB provides customers with the flexibility to change the application traffic routing algorithm; the default routing algorithm is round robin with the least outstanding requests routing algorithm also an alternative.

"},{"location":"networking/loadbalancing/loadbalancing/#choose-the-network-load-balancer-nlb-if-your-workload-is-tcp-or-if-your-workload-requires-source-ip-preservation-of-clients","title":"Choose the Network Load Balancer (NLB) if your workload is TCP, or if your workload requires Source IP Preservation of Clients","text":"

A Network Load Balancer functions at the fourth layer (Transport) of the Open Systems Interconnection (OSI) model. It is suited for TCP & UDP based workloads. Network Load Balancer also by default preserves the Source IP of address of the clients when presenting the traffic to the pod.

"},{"location":"networking/loadbalancing/loadbalancing/#choose-the-network-load-balancer-nlb-if-your-workload-cannot-utilize-dns","title":"Choose the Network Load Balancer (NLB) if your workload cannot utilize DNS","text":"

Another key reason to use the NLB is if your clients cannot utilize DNS. In this case, the NLB may be a better fit for your workload as the IPs on a Network Load Balancer are static. While clients are recommended to use DNS when resolving Domain Names to IP Addresses when connecting to Load Balancers, if a client's application doesn't support DNS resolution and only accepts hard-coded IPs then an NLB is a better fit as the IPs are static and remain same for the life of the NLB.

"},{"location":"networking/loadbalancing/loadbalancing/#provisioning-load-balancers","title":"Provisioning Load Balancers","text":"

After determining the Load Balancer best suited for your workloads, customers have a number of options for provisioning a load balancer.

"},{"location":"networking/loadbalancing/loadbalancing/#provision-load-balancers-by-deploying-the-aws-load-balancer-controller","title":"Provision Load Balancers by deploying the AWS Load Balancer Controller","text":"

There are two key methods of provisioning load balancers within an EKS Cluster.

  • Leveraging the AWS Cloud Provider Load balancer Controller (legacy)
  • Leveraging the AWS Load Balancer Controller (recommended)

By default, Kubernetes Service resources of type LoadBalancer get reconciled by the Kubernetes Service Controller that is built into the CloudProvider component of the kube-controller-manager or the cloud-controller-manager (also known as the in-tree controller).

The configuration of the provisioned load balancer is controlled by annotations that are added to the manifest for the Service or Ingress object and are different when using the AWS Load Balancer Controller than they are when using the AWS cloud provider load balancer controller.

The AWS Cloud Provider Load balancer Controller is legacy and is currently only receiving critical bug fixes. When you create a Kubernetes Service of type LoadBalancer, the AWS cloud provider load balancer controller creates AWS Classic Load Balancers by default, but can also create AWS Network Load Balancers with the correct annotation.

The AWS Load Balancer Controller (LBC) has to be installed in the EKS clusters and provisions AWS load balancers that point to cluster Service or Ingress resources.

In order for the LBC to manage the reconciliation of Kubernetes Service resources of type LoadBalancer, you need to offload the reconciliation from the in-tree controller to the LBC, explicitly. With LoadBalancerClassWith service.beta.kubernetes.io/aws-load-balancer-type annotation

"},{"location":"networking/loadbalancing/loadbalancing/#choosing-load-balancer-target-type","title":"Choosing Load Balancer Target-Type","text":""},{"location":"networking/loadbalancing/loadbalancing/#register-pods-as-targets-using-ip-target-type","title":"Register Pods as targets using IP Target-Type","text":"

An AWS Elastic Load Balancer: Network & Application, sends received traffic to registered targets in a target group. For an EKS Cluster there are 2 types of targets you can register in the target group: Instance & IP, which target type is used has implications on what gets registered and how traffic is routed from the Load Balancer to the pod. By default the AWS Load Balancer controller will register targets using \u2018Instance\u2019 type and this target will be the Worker Node\u2019s IP and NodePort, implication of this include:

  • Traffic from the Load Balancer will be forwarded to the Worker Node on the NodePort, this gets processed by iptables rules (configured by kube-proxy running on the node), and gets forwarded to the Service on its ClusterIP (still on the node), finally the Service randomly selects a pod registered to it and forwards the traffic to it. This flow involves multiple hops and extra latency can be incurred especially because the Service will sometimes select a pod running on another worker node which might also be in another AZ.
  • Because the Load Balancer registers the Worker Node as its target this means its health check which gets sent to the target will not be directly received by the pod but by the Worker Node on its NodePort and health check traffic will follow the same path described above.
  • Monitoring and Troubleshooting is more complex since traffic forwarded by the Load Balancer isn\u2019t directly sent to the pods and you\u2019d have to carefully correlate the packet received on the Worker Node to to the Service ClusterIP and eventually the pod to have full end-to-end visibility into the packet\u2019s path for proper troubleshooting.

By contrast if you configure the target type as \u2018IP\u2019 as we recommend the implication will be the following:

  • Traffic from the Load Balancer will be forwarded directly to the pod, this simplifies the network path as it bypasses the previous extra hops of Worker Nodes and Service Cluster IP, it reduces latency that would otherwise have been incurred if the Service forwarded traffic to a pod in another AZ and lastly it removes the iptables rules overhead processing on the Worker Nodes.
  • The Load Balancer\u2019s health check is directly received and responded to by the pod, this means the target status \u2018healthy\u2019 or \u2018unhealthy\u2019 are a direct representation of the pod\u2019s health status.
  • Monitoring and Troubleshooting is easier and any tool used that captures packet IP address will directly reveal the bi-directional traffic between the Load Balancer and the pod in its source and destination fields.

To create an AWS Elastic Load Balancing that uses IP Targets you add:

  • alb.ingress.kubernetes.io/target-type: ip annotation to your Ingress\u2019 manifest when configuring your Kubernetes Ingress (Application Load Balancer)
  • service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip annotation to your Service\u2019s Manifest when configuring your Kubernetes Service of type LoadBalancer (Network Load Balancer).
"},{"location":"networking/loadbalancing/loadbalancing/#availability-and-pod-lifecycle","title":"Availability and Pod Lifecycle","text":"

During an application upgrade you must make sure that your application is always available to process requests so users do not experience any downtime. One common challenge in this scenario is syncing the availability status of your workloads between the Kubernetes layer, and the infrastructure, for instance external Load Balancers. The next few sections highlight the best practices to address such scenarios.

Note : The explanations below are based on the EndpointSlices as it is the recommended replacement for the Endpoints in Kubernetes. The differences between the two are negligible in the context of the scenarios covered below. AWS Load Balancer Controller by default consumes Endpoints, you can enable EndpointSlices by enabling the enable-endpoint-sliceflag on the controller.

"},{"location":"networking/loadbalancing/loadbalancing/#use-health-checks","title":"Use health checks","text":"

Kubernetes by default runs the process health check where the kubelet process on the node verifies whether or not the main process of the container is running. If not then by default it restarts that container. However you can also configure Kubernetes probes to identify when a container process is running but in a deadlock state, or whether an application has started successfully or not. Probes can be based on exec, grpc, httpGet and tcpSocket mechanisms. Based on the type and result of the probe the container can be restarted.

Please see the [Pod Creation] in the Appendix section below to revisit the sequence of events in Pod creation process.

"},{"location":"networking/loadbalancing/loadbalancing/#use-readiness-probes","title":"Use readiness probes","text":"

By default when all the containers within a Pod are running the Pod condition is considered to be \u201cReady\u201d. However the application may still not be able to process client requests. For example the application may need to pull some data or configuration from an external resource to be able to process requests. In such a state you would neither want to kill the application nor forward any requests to it. Readiness probe enables you to make sure that the Pod is not considered to be \u201cReady\u201d, meaning that it will not be added to the EndpointSlice object, until the probe result is success. On the other hand if the probe fails further down the line then the Pod is removed from the EndpointSlice object. You can configure a readiness probe in the Pod manifest for each container. kubelet process on each node runs the readiness probe against the containers on that node.

"},{"location":"networking/loadbalancing/loadbalancing/#utilize-pod-readiness-gates","title":"Utilize Pod readiness gates","text":"

One aspect of the readiness probe is the fact that there is no external feedback/influence mechanism in it, kubelet process on the node executes the probe and defines the state of the probe. This does not have any impact on the requests between microservices themselves in the Kubernetes layer (east west traffic) since the EndpointSlice Controller keeps the list of endpoints (Pods) always up to date. Why and when would you need an external mechanism then ?

When you expose your applications using Kubernetes Service type of Load Balancer or Kubernetes Ingress (for north - south traffic) then the list of Pod IPs for the respective Kubernetes Service must be propagated to the external infrastructure load balancer so that the load balancer also has an up to date list targets. AWS Load Balancer Controller bridges the gap here. When you use AWS Load Balancer Controller and leverage target group: IP , just like kube-proxy the AWS Load Balancer Controller also receives an update (via watch) and then it communicates with the ELB API to configure and start registering the Pod IP as a target on the ELB.

When you perform a rolling update of a Deployment, new Pods get created, and as soon as a new Pod\u2019s condition is \u201cReady\u201d an old/existing Pod gets terminated. During this process, the Kubernetes EndpointSlice object is updated faster than the time it takes the ELB to register the new Pods as targets, see target registration. For a brief time you could have a state mismatch between the Kubernetes layer and the infrastructure layer where client requests could be dropped. During this period within the Kubernetes layer new Pods would be ready to process requests but from ELB point of view they are not.

Pod Readiness Gates enables you to define additional requirements that must be met before the Pod condition is considered to be \u201cReady\u201d. In the case of AWS ELB, the AWS Load Balancer Controller monitors the status of the target (the Pod) on the AWS ELB and once the target registration completes and its status turns \u201cHealthy\u201d then the controller updates the Pod\u2019 s condition to \u201cReady\u201d. With this approach you influence the Pod condition based on the state of the external network, which is the target status on the AWS ELB. Pod Readiness Gates is crucial in rolling update scenarios as it enables you to prevent the rolling update of a deployment from terminating old pods until the newly created Pods target status turn \u201cHealthy\u201d on the AWS ELB.

"},{"location":"networking/loadbalancing/loadbalancing/#gracefully-shutdown-applications","title":"Gracefully shutdown applications","text":"

Your application should respond to a SIGTERM signal by starting its graceful shutdown so that clients do not experience any downtime. What this means is your application should run cleanup procedures such as saving data, closing file descriptors, closing database connections, completing in-flight requests gracefully and exit in a timely manner to fulfill the Pod termination request. You should set the grace period to long enough so that cleanup can finish. To learn how to respond to the SIGTERM signal you can refer to the resources of the respective programming language that you use for your application.

If your application is unable to shutdown gracefully upon receipt of a SIGTERM signal or if it ignores/does not receive the signal, then you can instead leverage PreStop hook to initiate a graceful shutdown of the application. Prestop hook is executed immediately before the SIGTERM signal is sent and it can perform arbitrary operations without having to implement those operations in the application code itself.

The overall sequence of events is shown in the diagram below. Note: regardless of the result of graceful shutdown procedure of the application, or the result of the PreStop hook, the application containers are eventually terminated at the end of the grace period via SIGKILL.

Please see the [Pod Deletion] in the Appendix section below to revisit the sequence of events in Pod deletion process.

"},{"location":"networking/loadbalancing/loadbalancing/#gracefully-handle-the-client-requests","title":"Gracefully handle the client requests","text":"

The sequence of events in Pod deletion is different than Pod creation. When a Pod is created kubelet updates the Pod IP in Kubernetes API and only then the EndpointSlice object is updated. On the other hand when a Pod is being terminated Kubernetes API notifies both the kubelet and EndpointSlice controller at the same time. Carefully inspect the following diagram which shows the sequence of events.

The way the state propagates all the way from API server down to the iptables rules on the nodes explained above creates an interesting race condition. Because there is a high chance that the container receives the SIGKILL signal much earlier than the kube-proxy on each node updates the local iptables rules. In such an event two scenarios worth mentioning are :

  • If your application immediately and bluntly drops the in-flight requests and connections upon receipt of SIGTERM which means the clients would see 50x errors all over the place.
  • Even if your application ensures that all in-flight requests and connections are processed completely upon receipt of SIGTERM, during the grace period, new client requests would still be sent to the application container because iptables rules may still not be updated yet. Until the cleanup procedure closes the server socket on the container those new requests will result in new connections. When the grace period ends those connections, which are established after the SIGTERM, at that time are dropped unconditionally since SIGKILL is sent.

Setting the grace period in Pod spec long enough may address this challenge but depending on the propagation delay and the number of actual client requests it is hard to anticipate the time it takes for the application to close out the connections gracefully. Hence the not so perfect but most feasible approach here is to use a PreStop hook to delay the SIGTERM signal until the iptables rules are updated to make sure that no new client requests are sent to the application rather, only existing connections carry on. PreStop hook can be a simple Exec handler such as sleep 10.

The behavior and the recommendation mentioned above would be equally applicable when you expose your applications using Kubernetes Service type of Load Balancer or Kubernetes Ingress (for north - south traffic) using AWS Load Balancer Controller and leverage target group: IP . Because just like kube-proxy the AWS Load Balancer Controller also receives an update (via watch) on the EndpointSlice object and then it communicates with the ELB API to start deregistering the Pod IP from the ELB. However depending on the load on Kubernetes API or the ELB API this can also take time and the SIGTERM may have already been sent to the application long ago. Once the ELB starts deregistering the target it stops sending requests to that target so the application will not receive any new requests and the ELB also starts a Deregistration delay which is 300 seconds by default. During the deregistration process the target is draining where basically the ELB waits for the in-flight requests/existing connections to that target to drain. Once the deregistration delay expires then the target is unused and any in-flight requests to that target is forcibly dropped.

"},{"location":"networking/loadbalancing/loadbalancing/#use-pod-disruption-budget","title":"Use Pod disruption budget","text":"

Configure a Pod Disruption Budget (PDB) for your applications. PDBlimits the number of Pods of a replicated application that are down simultaneously from voluntary disruptions. It ensures that a minimum number or percentage of pods remain available in a StatefulSet or Deployment. For example, a quorum-based application needs to ensure that the number of replicas running is never brought below the number needed for a quorum. Or a web front end might ensure that the number of replicas serving load never falls below a certain percentage of the total. PDB will protect the application against actions such as nodes being drained, or new versions of Deployments being rolled out. Keep in mind that PDB\u2019s will not protect the application against involuntary disruptions such as a failure of the node operating system or loss of network connectivity. For more information please refer to the Specifying a Disruption Budget for your Application in Kubernetes documentation.

"},{"location":"networking/loadbalancing/loadbalancing/#references","title":"References","text":"
  • KubeCon Europe 2019 Session - Ready? A Deep Dive into Pod Readiness Gates for Service Health
  • Book - Kubernetes in Action
  • AWS Blog - How to rapidly scale your application with ALB on EKS (without losing traffic)
"},{"location":"networking/loadbalancing/loadbalancing/#appendix","title":"Appendix","text":""},{"location":"networking/loadbalancing/loadbalancing/#pod-creation","title":"Pod Creation","text":"

It is imperative to understand what is the sequence of events in a scenario where a Pod is deployed and then it becomes healthy/ready to receive and process client requests. Let\u2019s talk about the sequence of events.

  1. A Pod is created on the Kubernetes control plane (i.e. by a kubectl command, or Deployment update, or scaling action).
  2. kube-scheduler assigns the Pod to a node in the cluster.
  3. The kubelet process running on the assigned node receives the update (via watch) and communicates with the container runtime to start the containers defined in the Pod spec.
  4. When the containers starts running, the kubelet updates the Pod condition as Ready in the Pod object in the Kubernetes API.
  5. The EndpointSlice Controller receives the Pod condition update (via watch) and adds the Pod IP/Port as a new endpoint to the EndpointSlice object (list of Pod IPs) of the respective Kubernetes Service.
  6. kube-proxy process on each node receives the update (via watch) on the EndpointSlice object and then updates the iptables rules on each node, with the new Pod IP/port.
"},{"location":"networking/loadbalancing/loadbalancing/#pod-deletion","title":"Pod Deletion","text":"

Just like Pod creation, it is imperative to understand what is the sequence of events during Pod deletion. Let\u2019 s talk about the sequence of events.

  1. A Pod deletion request is sent to the Kubernetes API server (i.e. by a kubectl command, or Deployment update, or scaling action).
  2. Kubernetes API server starts a grace period, which is 30 seconds by default, by setting the deletionTimestamp field in the Pod object. (Grace period can be configured in Pod spec through terminationGracePeriodSeconds)
  3. The kubelet process running on the node receives the update (via watch) on the Pod object and sends a SIGTERM signal to process identifier 1 (PID 1) inside each container in that Pod. It then watches the terminationGracePeriodSeconds.
  4. The EndpointSlice Controller also receives the update (via watch) from Step 2 and sets the endpoint condition to \u201cterminating\u201d in the EndpointSlice object (list of Pod IPs) of the respective Kubernetes Service.
  5. kube-proxy process on each node receives the update (via watch) on the EndpointSlice object then iptables rules on each node get updated by the kube-proxy to stop forwarding clients requests to the Pod.
  6. When the terminationGracePeriodSeconds expires then the kubelet sends SIGKILL signal to the parent process of each container in the Pod and forcibly terminates them.
  7. TheEndpointSlice Controller removes the endpoint from the EndpointSlice object.
  8. API server deletes the Pod object.
"},{"location":"networking/monitoring/","title":"Monitoring EKS workloads for Network performance issues","text":""},{"location":"networking/monitoring/#monitoring-coredns-traffic-for-dns-throttling-issues","title":"Monitoring CoreDNS traffic for DNS throttling issues","text":"

Running DNS intensive workloads can sometimes experience intermittent CoreDNS failures due to DNS throttling, and this can impact applications where you may encounter occasional UnknownHostException errors.

The Deployment for CoreDNS has an anti-affinity policy that instructs the Kubernetes scheduler to run instances of CoreDNS on separate worker nodes in the cluster, i.e. it should avoid co-locating replicas on the same worker node. This effectively reduces the number of DNS queries per network interface because traffic from each replica is routed through a different ENI. If you notice that DNS queries are being throttled because of the 1024 packets per second limit, you can 1) try increasing the number of CoreDNS replicas or 2) implement NodeLocal DNSCache. See Monitor CoreDNS Metrics for further information.

"},{"location":"networking/monitoring/#challenge","title":"Challenge","text":"
  • Packet drop happens in seconds and it can be tricky for us to properly monitor these patterns to determine if DNS throttling is actually happening.
  • DNS queries are throttled at the elastic network interface level. So, throttled queries don't appear in the query logging.
  • Flow logs do not capture all IP traffic. E.g. Traffic generated by instances when they contact the Amazon DNS server. If you use your own DNS server, then all traffic to that DNS server is logged
"},{"location":"networking/monitoring/#solution","title":"Solution","text":"

An easy way to identify the DNS throttling issues in worker nodes is by capturing linklocal_allowance_exceeded metric. The linklocal_allowance_exceeded is number of packets dropped because the PPS of the traffic to local proxy services exceeded the maximum for the network interface. This impacts traffic to the DNS service, the Instance Metadata Service, and the Amazon Time Sync Service. Instead of tracking this event real-time, we can stream this metric to Amazon Managed Service for Prometheus as well and can have them visualized in Amazon Managed Grafana

"},{"location":"networking/monitoring/#monitoring-dns-query-delays-using-conntrack-metrics","title":"Monitoring DNS query delays using Conntrack metrics","text":"

Another metric that can help in monitoring the CoreDNS throttling / query delay are conntrack_allowance_available and conntrack_allowance_exceeded. Connectivity failures caused by exceeding Connections Tracked allowances can have a larger impact than those resulting from exceeding other allowances. When relying on TCP to transfer data, packets that are queued or dropped due to exceeding EC2 instance network allowances, such as Bandwidth, PPS, etc., are typically handled gracefully thanks to TCP\u2019s congestion control capabilities. Impacted flows will be slowed down, and lost packets will be retransmitted. However, when an instance exceeds its Connections Tracked allowance, no new connections can be established until some of the existing ones are closed to make room for new connections.

conntrack_allowance_available and conntrack_allowance_exceeded helps customers in monitoring the connections tracked allowance which varies for every instance. These network performance metrics give customers visibility into the number of packets queued or dropped when an instance\u2019s networking allowances, such as Network Bandwidth, Packets-Per-Second (PPS), Connections Tracked, and Link-local service access (Amazon DNS, Instance Meta Data Service, Amazon Time Sync) are exceeded

conntrack_allowance_available is the number of tracked connections that can be established by the instance before hitting the Connections Tracked allowance of that instance type (supported for nitro-based instance only). conntrack_allowance_exceeded is the number of packets dropped because connection tracking exceeded the maximum for the instance and new connections could not be established.

"},{"location":"networking/monitoring/#other-important-network-performance-metrics","title":"Other important Network performance metrics","text":"

Other important network performance metrics include:

bw_in_allowance_exceeded (ideal value of the metric should be zero) is the number of packets queued and/or dropped because the inbound aggregate bandwidth exceeded the maximum for the instance

bw_out_allowance_exceeded (ideal value of the metric should be zero) is the number of packets queued and/or dropped because the outbound aggregate bandwidth exceeded the maximum for the instance

pps_allowance_exceeded (ideal value of the metric should be zero) is the number of packets queued and/or dropped because the bidirectional PPS exceeded the maximum for the instance

"},{"location":"networking/monitoring/#capturing-the-metrics-to-monitor-workloads-for-network-performance-issues","title":"Capturing the metrics to monitor workloads for network performance issues","text":"

The Elastic Network Adapter (ENA ) driver publishes network performance metrics discussed above from the instances where they are enabled. All the network performance metrics can be published to CloudWatch using the CloudWatch agent. Please refer to the blog for more information.

Let's now capture the metrics discussed above, store them in Amazon Managed Service for Prometheus and visualize using Amazon Managed Grafana

"},{"location":"networking/monitoring/#prerequisites","title":"Prerequisites","text":"
  • ethtool - Ensure the worker nodes have ethtool installed
  • An AMP workspace configured in your AWS account. For instructions, see Create a workspace in the AMP User Guide.
  • Amazon Managed Grafana Workspace
"},{"location":"networking/monitoring/#deploying-prometheus-ethtool-exporter","title":"Deploying Prometheus ethtool exporter","text":"

The deployment contains a python script that pulls information from ethtool and publishes it in prometheus format.

kubectl apply -f https://raw.githubusercontent.com/Showmax/prometheus-ethtool-exporter/master/deploy/k8s-daemonset.yaml\n
"},{"location":"networking/monitoring/#deploy-the-adot-collector-to-scrape-the-ethtool-metrics-and-store-in-amazon-managed-service-for-prometheus-workspace","title":"Deploy the ADOT collector to scrape the ethtool metrics and store in Amazon Managed Service for Prometheus workspace","text":"

Each cluster where you install AWS Distro for OpenTelemetry (ADOT) must have this role to grant your AWS service account permissions to store metrics into Amazon Managed Service for Prometheus. Follow these steps to create and associate your IAM role to your Amazon EKS service account using IRSA:

eksctl create iamserviceaccount --name adot-collector --namespace default --cluster <CLUSTER_NAME> --attach-policy-arn arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess --attach-policy-arn arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess --attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy --region <REGION> --approve  --override-existing-serviceaccounts\n

Let's deploy the ADOT collector to scrape the metrcis from the prometheus ethtool exporter and store it in Amazon Managed Service for Prometheus

The following procedure uses an example YAML file with deployment as the mode value. This is the default mode and deploys the ADOT Collector similarly to a standalone application. This configuration receives OTLP metrics from the sample application and Amazon Managed Service for Prometheus metrics scraped from pods on the cluster

curl -o collector-config-amp.yaml https://raw.githubusercontent.com/aws-observability/aws-otel-community/master/sample-configs/operator/collector-config-amp.yaml\n

In collector-config-amp.yaml, replace the following with your own values: * mode: deployment * serviceAccount: adot-collector * endpoint: \"\" * region: \"\" * name: adot-collector

kubectl apply -f collector-config-amp.yaml \n

Once the adot collector is deployed, the metrics will be stored successfully in Amazon Prometheus

"},{"location":"networking/monitoring/#configure-alert-manager-in-amazon-managed-service-for-prometheus-to-send-notifications","title":"Configure alert manager in Amazon Managed Service for Prometheus to send notifications","text":"

You can use alert manager in Amazon Managed Service for Prometheus to set up alerting rules for critical alerts then you can send notifications to an Amazon SNS topic. Let's configure recording rules and alerting rules to check for the metrics discussed so far.

We will use the ACK Controller for Amazon Managed Service for Prometheus to provision the alerting and recording rules.

Let's deploy the ACL controller for the Amazon Managed Service for Prometheus service:

export SERVICE=prometheusservice\nexport RELEASE_VERSION=`curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '\"tag_name\":' | cut -d'\"' -f4`\nexport ACK_SYSTEM_NAMESPACE=ack-system\nexport AWS_REGION=us-east-1\naws ecr-public get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin public.ecr.aws\nhelm install --create-namespace -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller \\\noci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION --set=aws.region=$AWS_REGION\n

Run the command and after a few moments you should see the following message:

You are now able to create Amazon Managed Service for Prometheus (AMP) resources!\n\nThe controller is running in \"cluster\" mode.\n\nThe controller is configured to manage AWS resources in region: \"us-east-1\"\n\nThe ACK controller has been successfully installed and ACK can now be used to provision an Amazon Managed Service for Prometheus workspace.\n

Let's now create a yaml file for provisioning the alert manager defnition and rule groups. Save the below file as rulegroup.yaml

apiVersion: prometheusservice.services.k8s.aws/v1alpha1\nkind: RuleGroupsNamespace\nmetadata:\n   name: default-rule\nspec:\n   workspaceID: <Your WORKSPACE-ID>\n   name: default-rule\n   configuration: |\n     groups:\n     - name: ppsallowance\n       rules:\n       - record: metric:pps_allowance_exceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"pps_allowance_exceeded\"}[30s])\n       - alert: PPSAllowanceExceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"pps_allowance_exceeded\"} [30s]) > 0\n         labels:\n           severity: critical\n\n         annotations:\n           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})\n           description: \"PPSAllowanceExceeded is greater than 0\"\n     - name: bw_in\n       rules:\n       - record: metric:bw_in_allowance_exceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"bw_in_allowance_exceeded\"}[30s])\n       - alert: BWINAllowanceExceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"bw_in_allowance_exceeded\"} [30s]) > 0\n         labels:\n           severity: critical\n\n         annotations:\n           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})\n           description: \"BWInAllowanceExceeded is greater than 0\"\n     - name: bw_out\n       rules:\n       - record: metric:bw_out_allowance_exceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"bw_out_allowance_exceeded\"}[30s])\n       - alert: BWOutAllowanceExceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"bw_out_allowance_exceeded\"} [30s]) > 0\n         labels:\n           severity: critical\n\n         annotations:\n           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})\n           description: \"BWoutAllowanceExceeded is greater than 0\"            \n     - name: conntrack\n       rules:\n       - record: metric:conntrack_allowance_exceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"conntrack_allowance_exceeded\"}[30s])\n       - alert: ConntrackAllowanceExceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"conntrack_allowance_exceeded\"} [30s]) > 0\n         labels:\n           severity: critical\n\n         annotations:\n           summary: Connections dropped due to total allowance exceeding for the  (instance {{ $labels.instance }})\n           description: \"ConnTrackAllowanceExceeded is greater than 0\"\n     - name: linklocal\n       rules:\n       - record: metric:linklocal_allowance_exceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"linklocal_allowance_exceeded\"}[30s])\n       - alert: LinkLocalAllowanceExceeded\n         expr: rate(node_net_ethtool{device=\"eth0\",type=\"linklocal_allowance_exceeded\"} [30s]) > 0\n         labels:\n           severity: critical\n\n         annotations:\n           summary: Packets dropped due to PPS rate allowance exceeded for local services  (instance {{ $labels.instance }})\n           description: \"LinkLocalAllowanceExceeded is greater than 0\"\n

Replace Your WORKSPACE-ID with the Workspace ID of the workspace you are using.

Let's now configure the alert manager definition. Save the below fie as alertmanager.yaml

apiVersion: prometheusservice.services.k8s.aws/v1alpha1  \nkind: AlertManagerDefinition\nmetadata:\n  name: alert-manager\nspec:\n  workspaceID: <Your WORKSPACE-ID >\n  configuration: |\n    alertmanager_config: |\n      route:\n         receiver: default_receiver\n       receivers:\n       - name: default_receiver\n          sns_configs:\n          - topic_arn: TOPIC-ARN\n            sigv4:\n              region: REGION\n            message: |\n              alert_type: {{ .CommonLabels.alertname }}\n              event_type: {{ .CommonLabels.event_type }}     \n

Replace You WORKSPACE-ID with the Workspace ID of the new workspace, TOPIC-ARN with the ARN of an Amazon Simple Notification Service topic where you want to send the alerts, and REGION with the current region of the workload. Make sure that your workspace has permissions to send messages to Amazon SNS.

"},{"location":"networking/monitoring/#visualize-ethtool-metrics-in-amazon-managed-grafana","title":"Visualize ethtool metrics in Amazon Managed Grafana","text":"

Let's visualize the metrics within the Amazon Managed Grafana and build a dashboard. Configure the Amazon Managed Service for Prometheus as a datasource inside the Amazon Managed Grafana console. For instructions, see Add Amazon Prometheus as a datasource

Let's explore the metrics in Amazon Managed Grafana now: Click the explore button, and search for ethtool:

Let's build a dashboard for the linklocal_allowance_exceeded metric by using the query rate(node_net_ethtool{device=\"eth0\",type=\"linklocal_allowance_exceeded\"}[30s]). It will result in the below dashboard.

We can clearly see that there were no packets dropped as the value is zero.

Let's build a dashboard for the conntrack_allowance_exceeded metric by using the query rate(node_net_ethtool{device=\"eth0\",type=\"conntrack_allowance_exceeded\"}[30s]). It will result in the below dashboard.

The metric conntrack_allowance_exceeded can be visualized in CloudWatch, provided you run a cloudwatch agent as described here. The resulting dashboard in CloudWatch will look like below:

We can clearly see that there were no packets dropped as the value is zero. If you are using Nitro-based instances, you can create a similar dashboard for conntrack_allowance_available and pro-actively monitor the connections in your EC2 instance. You can further extend this by configuring alerts in Amazon Managed Grafana to send notifications to Slack, SNS, Pagerduty etc.

"},{"location":"networking/prefix-mode/index_linux/","title":"Prefix Mode for Linux","text":"

Amazon VPC CNI assigns network prefixes to Amazon EC2 network interfaces to increase the number of IP addresses available to nodes and increase pod density per node. You can configure version 1.9.0 or later of the Amazon VPC CNI add-on to assign IPv4 and IPv6 CIDRs instead of assigning individual secondary IP addresses to network interfaces.

Prefix mode is enabled by default on IPv6 clusters and is the only option supported. The VPC CNI assigns a /80 IPv6 prefix to a slot on an ENI. Please refer to the IPv6 section of this guide for further information.

With prefix assignment mode, the maximum number of elastic network interfaces per instance type remains the same, but you can now configure Amazon VPC CNI to assign /28 (16 IP addresses) IPv4 address prefixes, instead of assigning individual IPv4 addresses to the slots on network interfaces. When ENABLE_PREFIX_DELEGATION is set to true VPC CNI allocates an IP address to a Pod from the prefix assigned to an ENI. Please follow the instructions mentioned in the EKS user guide to enable Prefix IP mode.

The maximum number of IP addresses that you can assign to a network interface depends on the instance type. Each prefix that you assign to a network interface counts as one IP address. For example, a c5.large instance has a limit of 10 IPv4 addresses per network interface. Each network interface for this instance has a primary IPv4 address. If a network interface has no secondary IPv4 addresses, you can assign up to 9 prefixes to the network interface. For each additional IPv4 address that you assign to a network interface, you can assign one less prefix to the network interface. Review the AWS EC2 documentation on IP addresses per network interface per instance type and assigning prefixes to network interfaces.

During worker node initialization, the VPC CNI assigns one or more prefixes to the primary ENI. The CNI pre-allocates a prefix for faster pod startup by maintaining a warm pool. The number of prefixes to be held in warm pool can be controlled by setting environment variables.

  • WARM_PREFIX_TARGET, the number of prefixes to be allocated in excess of current need.
  • WARM_IP_TARGET, the number of IP addresses to be allocated in excess of current need.
  • MINIMUM_IP_TARGET, the minimum number of IP addresses to be available at any time.
  • WARM_IP_TARGET and MINIMUM_IP_TARGET if set will override WARM_PREFIX_TARGET.

As more Pods scheduled additional prefixes will be requested for the existing ENI. First, the VPC CNI attempts to allocate a new prefix to an existing ENI. If the ENI is at capacity, the VPC CNI attempts to allocate a new ENI to the node. New ENIs will be attached until the maximum ENI limit (defined by the instance type) is reached. When a new ENI is attached, ipamd will allocate one or more prefixes needed to maintain the WARM_PREFIX_TARGET, WARM_IP_TARGET, and MINIMUM_IP_TARGET setting.

"},{"location":"networking/prefix-mode/index_linux/#recommendations","title":"Recommendations","text":""},{"location":"networking/prefix-mode/index_linux/#use-prefix-mode-when","title":"Use Prefix Mode when","text":"

Use prefix mode if you are experiencing Pod density issue on the worker nodes. To avoid VPC CNI errors, we recommend examining the subnets for contiguous block of addresses for /28 prefix before migrate to prefix mode. Please refer \u201cUse Subnet Reservations to Avoid Subnet Fragmentation (IPv4)\u201d section for Subnet reservation details.

For backward compatibility, the max-pods limit is set to support secondary IP mode. To increase the pod density, please specify the max-pods value to Kubelet and --use-max-pods=false as the user data for the nodes. You may consider using the max-pod-calculator.sh script to calculate EKS\u2019s recommended maximum number of pods for a given instance type. Refer to the EKS user guide for example user data.

./max-pods-calculator.sh --instance-type m5.large --cni-version ``1.9``.0 --cni-prefix-delegation-enabled\n

Prefix assignment mode is especially relevant for users of CNI custom networking where the primary ENI is not used for pods. With prefix assignment, you can still attach more IPs on nearly every Nitro instance type, even without the primary ENI used for pods.

"},{"location":"networking/prefix-mode/index_linux/#avoid-prefix-mode-when","title":"Avoid Prefix Mode when","text":"

If your subnet is very fragmented and has insufficient available IP addresses to create /28 prefixes, avoid using prefix mode. The prefix attachment may fail if the subnet from which the prefix is produced is fragmented (a heavily used subnet with scattered secondary IP addresses). This problem may be avoided by creating a new subnet and reserving a prefix.

In prefix mode, the security group assigned to the worker nodes is shared by the Pods. Consider using Security groups for Podsif you have a security requirement to achieve compliance by running applications with varying network security requirements on shared compute resources.

"},{"location":"networking/prefix-mode/index_linux/#use-similar-instance-types-in-the-same-node-group","title":"Use Similar Instance Types in the same Node Group","text":"

Your node group may contain instances of many types. If an instance has a low maximum pod count, that value is applied to all nodes in the node group. Consider using similar instance types in a node group to maximize node use. We recommend configuring node.kubernetes.io/instance-type in the requirements part of the provisioner API if you are using Karpenter for automated node scaling.

Warning

The maximum pod count for all nodes in a particular node group is defined by the lowest maximum pod count of any single instance type in the node group.

"},{"location":"networking/prefix-mode/index_linux/#configure-warm_prefix_target-to-conserve-ipv4-addresses","title":"Configure WARM_PREFIX_TARGET to conserve IPv4 addresses","text":"

The installation manifest\u2019s default value for WARM_PREFIX_TARGET is 1. In most cases, the recommended value of 1 for WARM_PREFIX_TARGET will provide a good mix of fast pod launch times while minimizing unused IP addresses assigned to the instance.

If you have a need to further conserve IPv4 addresses per node use WARM_IP_TARGET and MINIMUM_IP_TARGET settings, which override WARM_PREFIX_TARGET when configured. By setting WARM_IP_TARGET to a value less than 16, you can prevent the CNI from keeping an entire excess prefix attached.

"},{"location":"networking/prefix-mode/index_linux/#prefer-allocating-new-prefixes-over-attaching-a-new-eni","title":"Prefer allocating new prefixes over attaching a new ENI","text":"

Allocating an additional prefix to an existing ENI is a faster EC2 API operation than creating and attaching a new ENI to the instance. Using prefixes improves performance while being frugal with IPv4 address allocation. Attaching a prefix typically completes in under a second, whereas attaching a new ENI can take up to 10 seconds. For most use cases, the CNI will only need a single ENI per worker node when running in prefix mode. If you can afford (in the worst case) up to 15 unused IPs per node, we strongly recommend using the newer prefix assignment networking mode, and realizing the performance and efficiency gains that come with it.

"},{"location":"networking/prefix-mode/index_linux/#use-subnet-reservations-to-avoid-subnet-fragmentation-ipv4","title":"Use Subnet Reservations to Avoid Subnet Fragmentation (IPv4)","text":"

When EC2 allocates a /28 IPv4 prefix to an ENI, it has to be a contiguous block of IP addresses from your subnet. If the subnet that the prefix is generated from is fragmented (a highly used subnet with scattered secondary IP addresses), the prefix attachment may fail, and you will see the following error message in the VPC CNI logs:

failed to allocate a private IP/Prefix address: InsufficientCidrBlocks: There are not enough free cidr blocks in the specified subnet to satisfy the request.\n

To avoid fragmentation and have sufficient contiguous space for creating prefixes, you may use VPC Subnet CIDR reservations to reserve IP space within a subnet for exclusive use by prefixes. Once you create a reservation, the VPC CNI plugin will call EC2 APIs to assign prefixes that are automatically allocated from the reserved space.

It is recommended to create a new subnet, reserve space for prefixes, and enable prefix assignment with VPC CNI for worker nodes running in that subnet. If the new subnet is dedicated only to Pods running in your EKS cluster with VPC CNI prefix assignment enabled, then you can skip the prefix reservation step.

"},{"location":"networking/prefix-mode/index_linux/#avoid-downgrading-vpc-cni","title":"Avoid downgrading VPC CNI","text":"

Prefix mode works with VPC CNI version 1.9.0 and later. Downgrading of the Amazon VPC CNI add-on to a version lower than 1.9.0 must be avoided once the prefix mode is enabled and prefixes are assigned to ENIs. You must delete and recreate nodes if you decide to downgrade the VPC CNI.

"},{"location":"networking/prefix-mode/index_linux/#replace-all-nodes-during-the-transition-to-prefix-delegation","title":"Replace all nodes during the transition to Prefix Delegation","text":"

It is highly recommended that you create new node groups to increase the number of available IP addresses rather than doing rolling replacement of existing worker nodes. Cordon and drain all the existing nodes to safely evict all of your existing Pods. To prevent service disruptions, we suggest implementing Pod Disruption Budgets on your production clusters for critical workloads. Pods on new nodes will be assigned an IP from a prefix assigned to an ENI. After you confirm the Pods are running, you can delete the old nodes and node groups. If you are using managed node groups, please follow steps mentioned here to safely delete a node group.

"},{"location":"networking/prefix-mode/index_windows/","title":"Prefix Mode for Windows","text":"

In Amazon EKS, each Pod that runs on a Windows host is assigned a secondary IP address by the VPC resource controller by default. This IP address is a VPC-routable address that is allocated from the host's subnet. On Linux, each ENI attached to the instance has multiple slots that can be populated by a secondary IP address or a /28 CIDR (a prefix). Windows hosts, however, only support a single ENI and its available slots. Using only secondary IP addresses can artifically limit the number of pods you can run on a Windows host, even when there is an abundance of IP addresses available for assignment.

In order to increase the pod density on Windows hosts, especially when using smaller instance types, you can enable Prefix Delegation for Windows nodes. When prefix delegation is enabled, /28 IPv4 prefixes are assigned to ENI slots rather than secondary IP addresses. Prefix delegation can be enabled by adding the enable-windows-prefix-delegation: \"true\" entry to the amazon-vpc-cni config map. This is the same config map where you need to set enable-windows-ipam: \"true\" entry for enabling Windows support.

Please follow the instructions mentioned in the EKS user guide to enable Prefix Delegation mode for Windows nodes.

Figure: Comparison of Secondary IP mode with Prefix Delegation mode

The maximum number of IP addresses you can assign to a network interface depends on the instance type and its size. Each prefix assigned to a network interface consumes an available slot. For example, a c5.large instance has a limit of 10 slots per network interface. The first slot on a network interface is always consumed by the interface's primary IP address, leaving you with 9 slots for prefixes and/or secondary IP addresses. If these slots are assigned prefixes, the node can support (9 * 16) 144 IP address whereas if they're assigned secondary IP addresses it can only support 9 IP addresses. See the documentation on IP addresses per network interface per instance type and assigning prefixes to network interfaces for further information.

During worker node initialization, the VPC Resource Controller assigns one or more prefixes to the primary ENI for faster pod startup by maintaining a warm pool of the IP addresses. The number of prefixes to be held in warm pool can be controlled by setting the following configuration parameters in amazon-vpc-cni config map.

  • warm-prefix-target, the number of prefixes to be allocated in excess of current need.
  • warm-ip-target, the number of IP addresses to be allocated in excess of current need.
  • minimum-ip-target, the minimum number of IP addresses to be available at any time.
  • warm-ip-target and/or minimum-ip-target if set will override warm-prefix-target.

As more Pods are scheduled on the node, additional prefixes will be requested for the existing ENI. When a Pod is scheduled on the node, VPC Resource Controller would first try to assign an IPv4 address from the existing prefixes on the node. If that is not possible, then a new IPv4 prefix will be requested as long as the subnet has the required capacity.

Figure: Workflow during assignment of IPv4 address to the Pod

"},{"location":"networking/prefix-mode/index_windows/#recommendations","title":"Recommendations","text":""},{"location":"networking/prefix-mode/index_windows/#use-prefix-delegation-when","title":"Use Prefix Delegation when","text":"

Use prefix delegation if you are experiencing Pod density issues on the worker nodes. To avoid errors, we recommend examining the subnets for contiguous block of addresses for /28 prefix before migrating to prefix mode. Please refer \u201cUse Subnet Reservations to Avoid Subnet Fragmentation (IPv4)\u201d section for Subnet reservation details.

By default, the max-pods on Windows nodes is set to 110. For the vast majority of instance types, this should be sufficient. If you want to increase or decrease this limit, then add the following to the bootstrap command in your user data:

-KubeletExtraArgs '--max-pods=example-value'\n
For more details about the bootstrap configuration parameters for Windows nodes, please visit the documentation here.

"},{"location":"networking/prefix-mode/index_windows/#avoid-prefix-delegation-when","title":"Avoid Prefix Delegation when","text":"

If your subnet is very fragmented and has insufficient available IP addresses to create /28 prefixes, avoid using prefix mode. The prefix attachment may fail if the subnet from which the prefix is produced is fragmented (a heavily used subnet with scattered secondary IP addresses). This problem may be avoided by creating a new subnet and reserving a prefix.

"},{"location":"networking/prefix-mode/index_windows/#configure-parameters-for-prefix-delegation-to-conserve-ipv4-addresses","title":"Configure parameters for prefix delegation to conserve IPv4 addresses","text":"

warm-prefix-target, warm-ip-target, and minimum-ip-target can be used to fine tune the behaviour of pre-scaling and dynamic scaling with prefixes. By default, the following values are used:

warm-ip-target: \"1\"\nminimum-ip-target: \"3\"\n
By fine tuning these configuration parameters, you can achieve an optimal balance of conserving the IP addresses and ensuring decreased Pod latency due to assignment of IP address. For more information about these configuration parameters, visit the documentation here.

"},{"location":"networking/prefix-mode/index_windows/#use-subnet-reservations-to-avoid-subnet-fragmentation-ipv4","title":"Use Subnet Reservations to Avoid Subnet Fragmentation (IPv4)","text":"

When EC2 allocates a /28 IPv4 prefix to an ENI, it has to be a contiguous block of IP addresses from your subnet. If the subnet that the prefix is generated from is fragmented (a highly used subnet with scattered secondary IP addresses), the prefix attachment may fail, and you will see the following node event:

InsufficientCidrBlocks: The specified subnet does not have enough free cidr blocks to satisfy the request\n
To avoid fragmentation and have sufficient contiguous space for creating prefixes, use VPC Subnet CIDR reservations to reserve IP space within a subnet for exclusive use by prefixes. Once you create a reservation, the IP addresses from the reserved blocks will not be assigned to other resources. That way, VPC Resource Controller will be able to get available prefixes during the assignment call to the node ENI.

It is recommended to create a new subnet, reserve space for prefixes, and enable prefix assignment for worker nodes running in that subnet. If the new subnet is dedicated only to Pods running in your EKS cluster with prefix delegation enabled, then you can skip the prefix reservation step.

"},{"location":"networking/prefix-mode/index_windows/#replace-all-nodes-when-migrating-from-secondary-ip-mode-to-prefix-delegation-mode-or-vice-versa","title":"Replace all nodes when migrating from Secondary IP mode to Prefix Delegation mode or vice versa","text":"

It is highly recommended that you create new node groups to increase the number of available IP addresses rather than doing rolling replacement of existing worker nodes.

When using self-managed node groups, the steps for transition would be:

  • Increase the capacity in your cluster such that the new nodes would be able to accomodate your workloads
  • Enable/Disable the Prefix Delegation feature for Windows
  • Cordon and drain all the existing nodes to safely evict all of your existing Pods. To prevent service disruptions, we suggest implementing Pod Disruption Budgets on your production clusters for critical workloads.
  • After you confirm the Pods are running, you can delete the old nodes and node groups. Pods on new nodes will be assigned an IPv4 address from a prefix assigned to the node ENI.

When using managed node groups, the steps for transition would be:

  • Enable/Disable the Prefix Delegation feature for Windows
  • Update the node group using the steps mentioned here. This performs similar steps as above but are managed by EKS.

Warning

Run all Pods on a node in the same mode

For Windows, we recommend that you avoid running Pods in both secondary IP mode and prefix delegation mode at the same time. Such a situation can arise when you migrate from secondary IP mode to prefix delegation mode or vice versa with running Windows workloads.

While this will not impact your running Pods, there can be inconsistency with respect to the node's IP address capacity. For example, consider that a t3.xlarge node which has 14 slots for secondary IPv4 addresses. If you are running 10 Pods, then 10 slots on the ENI will be consumed by secondary IP addresses. After you enable prefix delegation the capacity advertised to the kube-api server would be (14 slots * 16 ip addresses per prefix) 244 but the actual capacity at that moment would be (4 remaining slots * 16 addresses per prefix) 64. This inconsistency between the amount of capacity advertised and the actual amount of capacity (remaining slots) can cause issues if you run more Pods than there are IP addresses available for assignment.

That being said, you can use the migration strategy as described above to safely transition your Pods from secondary IP address to addresses obtained from prefixes. When toggling between the modes, the Pods will continue running normally and:

  • When toggling from secondary IP mode to prefix delegation mode, the secondary IP addresses assigned to the running pods will not be released. Prefixes will be assigned to the free slots. Once a pod is terminated, the secondary IP and slot it was using will be released.
  • When toggling from prefix delegation mode to secondary IP mode, a prefix will be released when all the IPs within its range are no longer allocated to pods. If any IP from the prefix is assigned to a pod then that prefix will be kept until the pods are terminated.
"},{"location":"networking/prefix-mode/index_windows/#debugging-issues-with-prefix-delegation","title":"Debugging Issues with Prefix Delegation","text":"

You can use our debugging guide here to deep dive into the issue you are facing with prefix delegation on Windows.

"},{"location":"networking/sgpp/","title":"Security Groups Per Pod","text":"

An AWS security group acts as a virtual firewall for EC2 instances to control inbound and outbound traffic. By default, the Amazon VPC CNI will use security groups associated with the primary ENI on the node. More specifically, every ENI associated with the instance will have the same EC2 Security Groups. Thus, every Pod on a node shares the same security groups as the node it runs on.

As seen in the image below, all application Pods operating on worker nodes will have access to the RDS database service (considering RDS inbound allows node security group). Security groups are too coarse grained because they apply to all Pods running on a node. Security groups for Pods provides network segmentation for workloads which is an essential part a good defense in depth strategy.

With security groups for Pods, you can improve compute efficiency by running applications with varying network security requirements on shared compute resources. Multiple types of security rules, such as Pod-to-Pod and Pod-to-External AWS services, can be defined in a single place with EC2 security groups and applied to workloads with Kubernetes native APIs. The image below shows security groups applied at the Pod level and how they simplify your application deployment and node architecture. The Pod can now access Amazon RDS database.

You can enable security groups for Pods by setting ENABLE_POD_ENI=true for VPC CNI. Once enabled, the \u201cVPC Resource Controller\u201c running on the control plane (managed by EKS) creates and attaches a trunk interface called \u201caws-k8s-trunk-eni\u201c to the node. The trunk interface acts as a standard network interface attached to the instance. To manage trunk interfaces, you must add the AmazonEKSVPCResourceController managed policy to the cluster role that goes with your Amazon EKS cluster.

The controller also creates branch interfaces named \"aws-k8s-branch-eni\" and associates them with the trunk interface. Pods are assigned a security group using the SecurityGroupPolicy custom resource and are associated with a branch interface. Since security groups are specified with network interfaces, we are now able to schedule Pods requiring specific security groups on these additional network interfaces. Review the EKS User Guide Section on Security Groups for Pods, including deployment prerequisites.

Branch interface capacity is additive to existing instance type limits for secondary IP addresses. Pods that use security groups are not accounted for in the max-pods formula and when you use security group for pods you need to consider raising the max-pods value or be ok with running fewer pods than the node can actually support.

A m5.large can have up to 9 branch network interfaces and up to 27 secondary IP addresses assigned to its standard network interfaces. As shown in the example below, the default max-pods for a m5.large is 29, and EKS counts the Pods that use security groups towards the maximum Pods. Please see the EKS user guide for instructions on how to change the max-pods for nodes.

When security groups for Pods are used in combination with custom networking, the security group defined in security groups for Pods is used rather than the security group specified in the ENIConfig. As a result, when custom networking is enabled, carefully assess security group ordering while using security groups per Pod.

"},{"location":"networking/sgpp/#recommendations","title":"Recommendations","text":""},{"location":"networking/sgpp/#disable-tcp-early-demux-for-liveness-probe","title":"Disable TCP Early Demux for Liveness Probe","text":"

If are you using liveness or readiness probes, you also need to disable TCP early demux, so that the kubelet can connect to Pods on branch network interfaces via TCP. This is only required in strict mode. To do this run the following command:

kubectl edit daemonset aws-node -n kube-system\n

Under the initContainer section, change the value for DISABLE_TCP_EARLY_DEMUX to true.

"},{"location":"networking/sgpp/#use-security-group-for-pods-to-leverage-existing-aws-configuration-investment","title":"Use Security Group For Pods to leverage existing AWS configuration investment.","text":"

Security groups makes it easier to restrict network access to VPC resources, such as RDS databases or EC2 instances. One clear advantage of security groups per Pod is the opportunity to reuse existing AWS security group resources. If you are using security groups as a network firewall to limit access to your AWS services, we propose applying security groups to Pods using branch ENIs. Consider using security groups for Pods if you are transferring apps from EC2 instances to EKS and limit access to other AWS services with security groups.

"},{"location":"networking/sgpp/#configure-pod-security-group-enforcing-mode","title":"Configure Pod Security Group Enforcing Mode","text":"

Amazon VPC CNI plugin version 1.11 added a new setting named POD_SECURITY_GROUP_ENFORCING_MODE (\u201cenforcing mode\u201d). The enforcing mode controls both which security groups apply to the pod, and if source NAT is enabled. You may specify the enforcing mode as either strict or standard. Strict is the default, reflecting the previous behavior of the VPC CNI with ENABLE_POD_ENI set to true.

In Strict Mode, only the branch ENI security groups are enforced. The source NAT is also disabled.

In Standard Mode, the security groups associated with both the primary ENI and branch ENI (associated with the pod) are applied. Network traffic must comply with both security groups.

Warning

Any mode change will only impact newly launched Pods. Existing Pods will use the mode that was configured when the Pod was created. Customers will need to recycle existing Pods with security groups if they want to change the traffic behavior.

"},{"location":"networking/sgpp/#enforcing-mode-use-strict-mode-for-isolating-pod-and-node-traffic","title":"Enforcing Mode: Use Strict mode for isolating pod and node traffic:","text":"

By default, security groups for Pods is set to \"strict mode.\" Use this setting if you must completely separate Pod traffic from the rest of the node's traffic. In strict mode, the source NAT is turned off so the branch ENI outbound security groups can be used.

Warning

When strict mode is enabled, all outbound traffic from a pod will leave the node and enter the VPC network. Traffic between pods on the same node will go over the VPC. This increases VPC traffic and limits node-based features. The NodeLocal DNSCache is not supported with strict mode.

"},{"location":"networking/sgpp/#enforcing-mode-use-standard-mode-in-the-following-situations","title":"Enforcing Mode: Use Standard mode in the following situations","text":"

Client source IP visible to the containers in the Pod

If you need to keep the client source IP visible to the containers in the Pod, consider setting POD_SECURITY_GROUP_ENFORCING_MODE to standard. Kubernetes services support externalTrafficPolicy=local to support preservation of the client source IP (default type cluster). You can now run Kubernetes services of type NodePort and LoadBalancer using instance targets with an externalTrafficPolicy set to Local in the standard mode. Local preserves the client source IP and avoids a second hop for LoadBalancer and NodePort type Services.

Deploying NodeLocal DNSCache

When using security groups for pods, configure standard mode to support Pods that use NodeLocal DNSCache. NodeLocal DNSCache improves Cluster DNS performance by running a DNS caching agent on cluster nodes as a DaemonSet. This will help the pods that have the highest DNS QPS requirements to query local kube-dns/CoreDNS having a local cache, which will improve the latency.

NodeLocal DNSCache is not supported in strict mode as all network traffic, even to the node, enters the VPC.

Supporting Kubernetes Network Policy

We recommend using standard enforcing mode when using network policy with Pods that have associated security groups.

We strongly recommend to utilize security groups for Pods to limit network-level access to AWS services that are not part of a cluster. Consider network policies to restrict network traffic between Pods inside a cluster, often known as East/West traffic.

"},{"location":"networking/sgpp/#identify-incompatibilities-with-security-groups-per-pod","title":"Identify Incompatibilities with Security Groups per Pod","text":"

Windows-based and non-nitro instances do not support security groups for Pods. To utilize security groups with Pods, the instances must be tagged with isTrunkingEnabled. Use network policies to manage access between Pods rather than security groups if your Pods do not depend on any AWS services within or outside of your VPC.

"},{"location":"networking/sgpp/#use-security-groups-per-pod-to-efficiently-control-traffic-to-aws-services","title":"Use Security Groups per Pod to efficiently control traffic to AWS Services","text":"

If an application running within the EKS cluster has to communicate with another resource within the VPC, e.g. an RDS database, then consider using SGs for pods. While there are policy engines that allow you to specify an CIDR or a DNS name, they are a less optimal choice when communicating with AWS services that have endpoints that reside within a VPC.

In contrast, Kubernetes network policies provide a mechanism for controlling ingress and egress traffic both within and outside the cluster. Kubernetes network policies should be considered if your application has limited dependencies on other AWS services. You may configure network policies that specify egress rules based on CIDR ranges to limit access to AWS services as opposed to AWS native semantics like SGs. You may use Kubernetes network policies to control network traffic between Pods (often referred to as East/West traffic) and between Pods and external services. Kubernetes network policies are implemented at OSI levels 3 and 4.

Amazon EKS allows you to use network policy engines such as Calico and Cilium. By default, the network policy engines are not installed. Please check the respective install guides for instructions on how to set up. For more information on how to use network policy, see EKS Security best practices. The DNS hostnames feature is available in the enterprise versions of network policy engines, which could be useful for controlling traffic between Kubernetes Services/Pods and resources that run outside of AWS. Also, you can consider DNS hostname support for AWS services that don't support security groups by default.

"},{"location":"networking/sgpp/#tag-a-single-security-group-to-use-aws-loadbalancer-controller","title":"Tag a single Security Group to use AWS Loadbalancer Controller","text":"

When many security groups are allocated to a Pod, Amazon EKS recommends tagging a single security group with kubernetes.io/cluster/$name shared or owned. The tag allows the AWS Loadbalancer Controller to update the rules of security groups to route traffic to the Pods. If just one security group is given to a Pod, the assignment of a tag is optional. Permissions set in a security group are additive, therefore tagging a single security group is sufficient for the loadbalancer controller to locate and reconcile the rules. It also helps to adhere to the default quotas defined by security groups.

"},{"location":"networking/sgpp/#configure-nat-for-outbound-traffic","title":"Configure NAT for Outbound Traffic","text":"

Source NAT is disabled for outbound traffic from Pods that are assigned security groups. For Pods using security groups that require access the internet launch worker nodes on private subnets configured with a NAT gateway or instance and enable external SNAT in the CNI.

kubectl set env daemonset -n kube-system aws-node AWS_VPC_K8S_CNI_EXTERNALSNAT=true\n
"},{"location":"networking/sgpp/#deploy-pods-with-security-groups-to-private-subnets","title":"Deploy Pods with Security Groups to Private Subnets","text":"

Pods that are assigned security groups must be run on nodes that are deployed on to private subnets. Note that Pods with assigned security groups deployed to public subnets will not able to access the internet.

"},{"location":"networking/sgpp/#verify-terminationgraceperiodseconds-in-pod-specification-file","title":"Verify terminationGracePeriodSeconds in Pod Specification File","text":"

Ensure that terminationGracePeriodSeconds is non-zero in your Pod specification file (default 30 seconds). This is essential in order for Amazon VPC CNI to delete the Pod network from the worker node. When set to zero, the CNI plugin does not remove the Pod network from the host, and the branch ENI is not effectively cleaned up.

"},{"location":"networking/sgpp/#using-security-groups-for-pods-with-fargate","title":"Using Security Groups for Pods with Fargate","text":"

Security groups for Pods that run on Fargate work very similarly to Pods that run on EC2 worker nodes. For example, you have to create the security group before referencing it in the SecurityGroupPolicy you associate with your Fargate Pod. By default, the cluster security group is assiged to all Fargate Pods when you don't explicitly assign a SecurityGroupPolicy to a Fargate Pod. For simplicity's sake, you may want to add the cluster security group to a Fagate Pod's SecurityGroupPolicy otherwise you will have to add the minimum security group rules to your security group. You can find the cluster security group using the describe-cluster API.

 aws eks describe-cluster --name CLUSTER_NAME --query 'cluster.resourcesVpcConfig.clusterSecurityGroupId'\n
cat >my-fargate-sg-policy.yaml <<EOF\napiVersion: vpcresources.k8s.aws/v1beta1\nkind: SecurityGroupPolicy\nmetadata:\n  name: my-fargate-sg-policy\n  namespace: my-fargate-namespace\nspec:\n  podSelector: \n    matchLabels:\n      role: my-fargate-role\n  securityGroups:\n    groupIds:\n      - cluster_security_group_id\n      - my_fargate_pod_security_group_id\nEOF\n

The minimum security group rules are listed here. These rules allow Fargate Pods to communicate with in-cluster services like kube-apiserver, kubelet, and CoreDNS. You also need add rules to allow inbound and outbound connections to and from your Fargate Pod. This will allow your Pod to communicate with other Pods or resources in your VPC. Additionally, you have to include rules for Fargate to pull container images from Amazon ECR or other container registries such as DockerHub. For more information, see AWS IP address ranges in the AWS General Reference.

You can use the below commands to find the security groups applied to a Fargate Pod.

kubectl get pod FARGATE_POD -o jsonpath='{.metadata.annotations.vpc\\.amazonaws\\.com/pod-eni}{\"\\n\"}'\n

Note down the eniId from above command.

aws ec2 describe-network-interfaces --network-interface-ids ENI_ID --query 'NetworkInterfaces[*].Groups[*]'\n

Existing Fargate pods must be deleted and recreated in order for new security groups to be applied. For instance, the following command initiates the deployment of the example-app. To update specific pods, you can change the namespace and deployment name in the below command.

kubectl rollout restart -n example-ns deployment example-pod\n
"},{"location":"networking/subnets/","title":"VPC and Subnet Considerations","text":"

Operating an EKS cluster requires knowledge of AWS VPC networking, in addition to Kubernetes networking.

We recommend you understand the EKS control plane communication mechanisms before you start designing your VPC or deploying clusters into existing VPCs.

Refer to Cluster VPC considerations and Amazon EKS security group considerations when architecting a VPC and subnets to be used with EKS.

"},{"location":"networking/subnets/#overview","title":"Overview","text":""},{"location":"networking/subnets/#eks-cluster-architecture","title":"EKS Cluster Architecture","text":"

An EKS cluster consists of two VPCs:

  • An AWS-managed VPC that hosts the Kubernetes control plane. This VPC does not appear in the customer account.
  • A customer-managed VPC that hosts the Kubernetes nodes. This is where containers run, as well as other customer-managed AWS infrastructure such as load balancers used by the cluster. This VPC appears in the customer account. You need to create customer-managed VPC prior creating a cluster. The eksctl creates a VPC if you do not provide one.

The nodes in the customer VPC need the ability to connect to the managed API server endpoint in the AWS VPC. This allows the nodes to register with the Kubernetes control plane and receive requests to run application Pods.

The nodes connect to the EKS control plane through (a) an EKS public endpoint or (b) a Cross-Account elastic network interfaces (X-ENI) managed by EKS. When a cluster is created, you need to specify at least two VPC subnets. EKS places a X-ENI in each subnet specified during cluster create (also called cluster subnets). The Kubernetes API server uses these Cross-Account ENIs to communicate with nodes deployed on the customer-managed cluster VPC subnets.

As the node starts, the EKS bootstrap script is executed and Kubernetes node configuration files are installed. As part of the boot process on each instance, the container runtime agents, kubelet, and Kubernetes node agents are launched.

To register a node, Kubelet contacts the Kubernetes cluster endpoint. It establishes a connection with either the public endpoint outside of the VPC or the private endpoint within the VPC. Kubelet receives API instructions and provides status updates and heartbeats to the endpoint on a regular basis.

"},{"location":"networking/subnets/#eks-control-plane-communication","title":"EKS Control Plane Communication","text":"

EKS has two ways to control access to the cluster endpoint. Endpoint access control lets you choose whether the endpoint can be reached from the public internet or only through your VPC. You can turn on the public endpoint (which is the default), the private endpoint, or both at once.

The configuration of the cluster API endpoint determines the path that nodes take to communicate to the control plane. Note that these endpoint settings can be changed at any time through the EKS console or API.

"},{"location":"networking/subnets/#public-endpoint","title":"Public Endpoint","text":"

This is the default behavior for new Amazon EKS clusters. When only the public endpoint for the cluster is enabled, Kubernetes API requests that originate from within your cluster\u2019s VPC (such as worker node to control plane communication) leave the VPC, but not Amazon\u2019s network. In order for nodes to connect to the control plane, they must have a public IP address and a route to an internet gateway or a route to a NAT gateway where they can use the public IP address of the NAT gateway.

"},{"location":"networking/subnets/#public-and-private-endpoint","title":"Public and Private Endpoint","text":"

When both the public and private endpoints are enabled, Kubernetes API requests from within the VPC communicate to the control plane via the X-ENIs within your VPC. Your cluster API server is accessible from the internet.

"},{"location":"networking/subnets/#private-endpoint","title":"Private Endpoint","text":"

There is no public access to your API server from the internet when only private endpoint is enabled. All traffic to your cluster API server must come from within your cluster\u2019s VPC or a connected network. The nodes communicate to API server via X-ENIs within your VPC. Note that cluster management tools must have access to the private endpoint. Learn more about how to connect to a private Amazon EKS cluster endpoint from outside the Amazon VPC.

Note that the cluster's API server endpoint is resolved by public DNS servers to a private IP address from the VPC. In the past, the endpoint could only be resolved from within the VPC.

"},{"location":"networking/subnets/#vpc-configurations","title":"VPC configurations","text":"

Amazon VPC supports IPv4 and IPv6 addressing. Amazon EKS supports IPv4 by default. A VPC must have an IPv4 CIDR block associated with it. You can optionally associate multiple IPv4 Classless Inter-Domain Routing (CIDR) blocks and multiple IPv6 CIDR blocks to your VPC. When you create a VPC, you must specify an IPv4 CIDR block for the VPC from the private IPv4 address ranges as specified in RFC 1918. The allowed block size is between a /16 prefix (65,536 IP addresses) and /28 prefix (16 IP addresses).

When creating a new VPC, you can attach a single IPv6 CIDR block, and up to five when changing an existing VPC. The prefix length of the IPv6 CIDR block size can be between /44 and /60 and for the IPv6 subnets it can be betwen /44/ and /64. You can request an IPv6 CIDR block from the pool of IPv6 addresses maintained by Amazon. Please refer to VPC CIDR blocks section of the VPC User Guide for more information.

Amazon EKS clusters support both IPv4 and IPv6. By default, EKS clusters use IPv4 IP. Specifying IPv6 at cluster creation time will enable the use IPv6 clusters. IPv6 clusters require dual-stack VPCs and subnets.

Amazon EKS recommends you use at least two subnets that are in different Availability Zones during cluster creation. The subnets you pass in during cluster creation are known as cluster subnets. When you create a cluster, Amazon EKS creates up to 4 cross account (x-account or x-ENIs) ENIs in the subnets that you specify. The x-ENIs are always deployed and are used for cluster administration traffic such as log delivery, exec, and proxy. Please refer to the EKS user guide for complete VPC and subnet requirement details.

Kubernetes worker nodes can run in the cluster subnets, but it is not recommended. During cluster upgrades Amazon EKS provisions additional ENIs in the cluster subnets. When your cluster scales out, worker nodes and pods may consume the available IPs in the cluster subnet. Hence in order to make sure there are enough available IPs you might want to consider using dedicated cluster subnets with /28 netmask.

Kubernetes worker nodes can run in either a public or a private subnet. Whether a subnet is public or private refers to whether traffic within the subnet is routed through an internet gateway. Public subnets have a route table entry to the internet through the internet gateway, but private subnets don't.

The traffic that originates somewhere else and reaches your nodes is called ingress. Traffic that originates from the nodes and leaves the network is called egress. Nodes with public or elastic IP addresses (EIPs) within a subnet configured with an internet gateway allow ingress from outside of the VPC. Private subnets usually have a routing to a NAT gateway, which do not allow ingress traffic to the nodes in the subnets from outside of VPC while still allowing traffic from the nodes to leave the VPC (egress).

In the IPv6 world, every address is internet routable. The IPv6 addresses associated with the nodes and pods are public. Private subnets are supported by implementing an egress-only internet gateways (EIGW) in a VPC, allowing outbound traffic while blocking all incoming traffic. Best practices for implementing IPv6 subnets can be found in the VPC user guide.

"},{"location":"networking/subnets/#you-can-configure-vpc-and-subnets-in-three-different-ways","title":"You can configure VPC and Subnets in three different ways:","text":""},{"location":"networking/subnets/#using-only-public-subnets","title":"Using only public subnets","text":"

In the same public subnets, both nodes and ingress resources (such as load balancers) are created. Tag the public subnet with kubernetes.io/role/elb to construct load balancers that face the internet. In this configuration, the cluster endpoint can be configured to be public, private, or both (public and private).

"},{"location":"networking/subnets/#using-private-and-public-subnets","title":"Using private and public subnets","text":"

Nodes are created on private subnets, whereas Ingress resources are instantiated in public subnets. You can enable public, private, or both (public and private) access to the cluster endpoint. Depending on the configuration of the cluster endpoint, node traffic will enter via the NAT gateway or the ENI.

"},{"location":"networking/subnets/#using-only-private-subnets","title":"Using only private subnets","text":"

Both nodes and ingress are created in private subnets. Using the kubernetes.io/role/internal-elb subnet tag to construct internal load balancers. Accessing your cluster's endpoint will require a VPN connection. You must activate AWS PrivateLink for EC2 and all Amazon ECR and S3 repositories. Only the private endpoint of the cluster should be enabled. We suggest going through the EKS private cluster requirements before provisioning private clusters.

"},{"location":"networking/subnets/#communication-across-vpcs","title":"Communication across VPCs","text":"

There are many scenarios when you require multiple VPCs and separate EKS clusters deployed to these VPCs.

You can use Amazon VPC Lattice to consistently and securely connect services across multiple VPCs and accounts (without requiring additional connectivity to be provided by services like VPC peering, AWS PrivateLink or AWS Transit Gateway). Learn more here.

Amazon VPC Lattice operates in the link-local address space in IPv4 and IPv6, providing connectivity between services that may have overlapping IPv4 addresses. For operational efficiency, we strongly recommend deploying EKS clusters and nodes to IP ranges that do not overlap. In case your infrastructure includes VPCs with overlapping IP ranges, you need to architect your network accordingly. We suggest Private NAT Gateway, or VPC CNI in custom networking mode in conjunction with transit gateway to integrate workloads on EKS to solve overlapping CIDR challenges while preserving routable RFC1918 IP addresses.

Consider utilizing AWS PrivateLink, also known as an endpoint service, if you are the service provider and would want to share your Kubernetes service and ingress (either ALB or NLB) with your customer VPC in separate accounts.

"},{"location":"networking/subnets/#sharing-vpc-across-multiple-accounts","title":"Sharing VPC across multiple accounts","text":"

Many enterprises adopted shared Amazon VPCs as a means to streamline network administration, reduce costs and improve security across multiple AWS Accounts in an AWS Organization. They utilize AWS Resource Access Manager (RAM) to securely share supported AWS resources with individual AWS Accounts, organizational units (OUs) or entire AWS Organization.

You can deploy Amazon EKS clusters, managed node groups and other supporting AWS resources (like LoadBalancers, security groups, end points, etc.,) in shared VPC Subnets from an another AWS Account using AWS RAM. Below figure depicts an example highlevel architecture. This allows central networking teams control over the networking constructs like VPCs, Subnets, etc., while allowing application or platform teams to deploy Amazon EKS clusters in their respective AWS Accounts. A complete walkthrough of this scenario is available at this github repository.

"},{"location":"networking/subnets/#considerations-when-using-shared-subnets","title":"Considerations when using Shared Subnets","text":"
  • Amazon EKS clusters and worker nodes can be created within shared subnets that are all part of the same VPC. Amazon EKS does not support the creation of clusters across multiple VPCs.

  • Amazon EKS uses AWS VPC Security Groups (SGs) to control the traffic between the Kubernetes control plane and the cluster's worker nodes. Security groups are also used to control the traffic between worker nodes, and other VPC resources, and external IP addresses. You must create these security groups in the application/participant account. Ensure that the security groups you intend to use for your pods are also located in the participant account. You can configure the inbound and outbound rules within your security groups to permit the necessary traffic to and from security groups located in the Central VPC account.

  • Create IAM roles and associated policies within the participant account where your Amazon EKS cluster resides. These IAM roles and policies are essential for granting the necessary permissions to Kubernetes clusters managed by Amazon EKS, as well as to the nodes and pods running on Fargate. The permissions enable Amazon EKS to make calls to other AWS services on your behalf.

  • You can follow following approaches to allow cross Account access to AWS resources like Amazon S3 buckets, Dynamodb tables, etc., from k8s pods:

    • Resource based policy approach: If the AWS service supports resource policies, you can add appropriate resource based policy to allow cross account access to IAM Roles assigned to the kubernetes pods. In this scenario, OIDC provider, IAM Roles, and permission policies exist in the application account. To find AWS Services that support Resource based policies, refer AWS services that work with IAM and look for the services that have Yes in the Resource Based column.

    • OIDC Provider approach: IAM resources like OIDC Provider, IAM Roles, Permission, and Trust policies will be created in other participant AWS Account where the resources exists. These roles will be assigned to Kubernetes pods in application account, so that they can access cross account resources. Refer Cross account IAM roles for Kubernetes service accounts blog for a complete walkthrough of this approach.

  • You can deploy the Amazon Elastic Loadbalancer (ELB) resources (ALB or NLB) to route traffic to k8s pods either in application or central networking accounts. Refer to Expose Amazon EKS Pods Through Cross-Account Load Balancer walkthrough for detailed instructions on deploying the ELB resources in central networking account. This option offers enhanced flexibility, as it grants the Central Networking account full control over the security configuration of the Load Balancer resources.

  • When using custom networking feature of Amazon VPC CNI, you need to use the Availability Zone (AZ) ID mappings listed in the central networking account to create each ENIConfig. This is due to random mapping of physical AZs to the AZ names in each AWS account.

"},{"location":"networking/subnets/#security-groups","title":"Security Groups","text":"

A security group controls the traffic that is allowed to reach and leave the resources that it is associated with. Amazon EKS uses security groups to manage the communication between the control plane and nodes. When you create a cluster, Amazon EKS creates a security group that's named eks-cluster-sg-my-cluster-uniqueID. EKS associates these security groups to the managed ENIs and the nodes. The default rules allow all traffic to flow freely between your cluster and nodes, and allows all outbound traffic to any destination.

When you create a cluster, you can specify your own security groups. Please see recommendation for security groups when you specify own security groups.

"},{"location":"networking/subnets/#recommendations","title":"Recommendations","text":""},{"location":"networking/subnets/#consider-multi-az-deployment","title":"Consider Multi-AZ Deployment","text":"

AWS Regions provide multiple physically separated and isolated Availability Zones (AZ), which are connected with low-latency, high-throughput, and highly redundant networking. With Availability Zones, you can design and operate applications that automatically fail over between Availability Zones without interruption. Amazon EKS strongly recommends deploying EKS clusters to multiple availability zones. Please consider specifying subnets in at least two availability zones when you create the cluster.

Kubelet running on nodes automatically adds labels to the node object such as topology.kubernetes.io/region=us-west-2, and topology.kubernetes.io/zone=us-west-2d. We recommend to use node labels in conjunction with Pod topology spread constraints to control how Pods are spread across zones. These hints enable Kubernetes scheduler to place Pods for better expected availability, reducing the risk that a correlated failure affects your whole workload. Please refer Assigning nodes to Pods to see examples for node selector and AZ spread constraints.

You can define the subnets or availability zones when you create nodes. The nodes are placed in cluster subnets if no subnets are configured. EKS support for managed node groups automatically spreads the nodes across multiple availability zones on available capacity. Karpenter will honor the AZ spread placement by scaling nodes to specified AZs if workloads define topology spread limits.

AWS Elastic Load Balancers are managed by the AWS Load Balancer Controller for a Kubernetes cluster. It provisions an Application Load Balancer (ALB) for Kubernetes ingress resources and a Network Load Balancer (NLB) for Kubernetes services of type Loadbalancer. The Elastic Load Balancer controller uses tags to discover the subnets. ELB controller requires a minimum of two availability zones (AZs) to provision ingress resource successfully. Consider setting subnets in at least two AZs to take advantage of geographic redundancy's safety and reliability.

"},{"location":"networking/subnets/#deploy-nodes-to-private-subnets","title":"Deploy Nodes to Private Subnets","text":"

A VPC including both private and public subnets is the ideal method for deploying Kubernetes workloads on EKS. Consider setting a minimum of two public subnets and two private subnets in two distinct availability zones. The related route table of a public subnet contains a route to an internet gateway . Pods are able to interact with the Internet via a NAT gateway. Private subnets are supported by egress-only internet gateways in the IPv6 environment (EIGW).

Instantiating nodes in private subnets offers maximal control over traffic to the nodes and is effective for the vast majority of Kubernetes applications. Ingress resources (like as load balancers) are instantiated in public subnets and route traffic to Pods operating on private subnets.

Consider private only mode if you demand strict security and network isolation. In this configuration, three private subnets are deployed in distinct Availability Zones within the AWS Region's VPC. The resources deployed to the subnets cannot access the internet, nor can the internet access the resources in the subnets. In order for your Kubernetes application to access other AWS services, you must configure PrivateLink interfaces and/or gateway endpoints. You may setup internal load balancers to redirect traffic to Pods using AWS Load Balancer Controller. The private subnets must be tagged (kubernetes.io/role/internal-elb: 1) for the controller to provision load balancers. For nodes to register with the cluster, the cluster endpoint must be set to private mode. Please visit private cluster guide for complete requirements and considerations.

"},{"location":"networking/subnets/#consider-public-and-private-mode-for-cluster-endpoint","title":"Consider Public and Private Mode for Cluster Endpoint","text":"

Amazon EKS offers public-only, public-and-private, and private-only cluster endpoint modes. The default mode is public-only, however we recommend configuring cluster endpoint in public and private mode. This option allows Kubernetes API calls within your cluster's VPC (such as node-to-control-plane communication) to utilize the private VPC endpoint and traffic to remain within your cluster's VPC. Your cluster API server, on the other hand, can be reached from the internet. However, we strongly recommend limiting the CIDR blocks that can use the public endpoint. Learn how to configure public and private endpoint access, including limiting CIDR blocks.

We suggest a private-only endpoint when you need security and network isolation. We recommend using either of the options listed in the EKS user guide to connect to an API server privately.

"},{"location":"networking/subnets/#configure-security-groups-carefully","title":"Configure Security Groups Carefully","text":"

Amazon EKS supports using custom security groups. Any custom security groups must allow communication between nodes and the Kubernetes control plane. Please check port requirements and configure rules manually when your organization doesn't allow for open communication.

EKS applies the custom security groups that you provide during cluster creation to the managed interfaces (X-ENIs). However, it does not immediately associate them with nodes. While creating node groups, it is strongly recommended to associate custom security groups manually. Please consider enabling securityGroupSelectorTerms to enable Karpenter node template discovery of custom security groups during autoscaling of nodes.

We strongly recommend creating a security group to allow all inter-node communication traffic. During the bootstrap process, nodes require outbound Internet connectivity to access the cluster endpoint. Evaluate outward access requirements, such as on-premise connection and container registry access, and set rules appropriately. Before putting changes into production, we strongly suggest that you check connections carefully in your development environment.

"},{"location":"networking/subnets/#deploy-nat-gateways-in-each-availability-zone","title":"Deploy NAT Gateways in each Availability Zone","text":"

If you deploy nodes in private subnets (IPv4 and IPv6), consider creating a NAT Gateway in each Availability Zone (AZ) to ensure zone-independent architecture and reduce cross AZ expenditures. Each NAT gateway in an AZ is implemented with redundancy.

"},{"location":"networking/subnets/#use-cloud9-to-access-private-clusters","title":"Use Cloud9 to access Private Clusters","text":"

AWS Cloud9 is a web-based IDE than can run securely in Private Subnets without ingress access, using AWS Systems Manager. Egress can also be disabled on the Cloud9 instance. Learn more about using Cloud9 to access private clusters and subnets.

"},{"location":"networking/vpc-cni/","title":"Amazon VPC CNI","text":"

Amazon EKS implements cluster networking through the Amazon VPC Container Network Interface(VPC CNI) plugin. The CNI plugin allows Kubernetes Pods to have the same IP address as they do on the VPC network. More specifically, all containers inside the Pod share a network namespace, and they can communicate with each-other using local ports.

Amazon VPC CNI has two components:

  • CNI Binary, which will setup Pod network to enable Pod-to-Pod communication. The CNI binary runs on a node root file system and is invoked by the kubelet when a new Pod gets added to, or an existing Pod removed from the node.
  • ipamd, a long-running node-local IP Address Management (IPAM) daemon and is responsible for:
  • managing ENIs on a node, and
  • maintaining a warm-pool of available IP addresses or prefix

When an instance is created, EC2 creates and attaches a primary ENI associated with a primary subnet. The primary subnet may be public or private. The Pods that run in hostNetwork mode use the primary IP address assigned to the node primary ENI and share the same network namespace as the host.

The CNI plugin manages Elastic Network Interfaces (ENI) on the node. When a node is provisioned, the CNI plugin automatically allocates a pool of slots (IPs or Prefix\u2019s) from the node\u2019s subnet to the primary ENI. This pool is known as the warm pool, and its size is determined by the node\u2019s instance type. Depending on CNI settings, a slot may be an IP address or a prefix. When a slot on an ENI has been assigned, the CNI may attach additional ENIs with warm pool of slots to the nodes. These additional ENIs are called Secondary ENIs. Each ENI can only support a certain number of slots, based on instance type. The CNI attaches more ENIs to instances based on the number of slots needed, which usually corresponds to the number of Pods. This process continues until the node can no longer support additional ENI. The CNI also pre-allocates \u201cwarm\u201d ENIs and slots for faster Pod startup. Note each instance type has a maximum number of ENIs that may be attached. This is one constraint on Pod density (number of Pods per node), in addition to compute resources.

The maximum number of network interfaces, and the maximum number of slots that you can use varies by the type of EC2 Instance. Since each Pod consumes an IP address on a slot, the number of Pods you can run on a particular EC2 Instance depends on how many ENIs can be attached to it and how many slots each ENI supports. We suggest setting the maximum Pods per EKS user guide to avoid exhaustion of the instance\u2019s CPU and memory resources. Pods using hostNetwork are excluded from this calculation. You may consider using a script called max-pods-calculator.sh to calculate EKS\u2019s recommended maximum Pods for a given instance type.

"},{"location":"networking/vpc-cni/#overview","title":"Overview","text":"

Secondary IP mode is the default mode for VPC CNI. This guide provides a generic overview of VPC CNI behavior when Secondary IP mode is enabled. The functionality of ipamd (allocation of IP addresses) may vary depending on the configuration settings for VPC CNI, such as Prefix Mode, Security Groups Per Pod, and Custom Networking.

The Amazon VPC CNI is deployed as a Kubernetes Daemonset named aws-node on worker nodes. When a worker node is provisioned, it has a default ENI, called the primary ENI, attached to it. The CNI allocates a warm pool of ENIs and secondary IP addresses from the subnet attached to the node\u2019s primary ENI. By default, ipamd attempts to allocate an additional ENI to the node. The IPAMD allocates additional ENI when a single Pod is scheduled and assigned a secondary IP address from the primary ENI. This \"warm\" ENI enables faster Pod networking. As the pool of secondary IP addresses runs out, the CNI adds another ENI to assign more.

The number of ENIs and IP addresses in a pool are configured through environment variables called WARM_ENI_TARGET, WARM_IP_TARGET, MINIMUM_IP_TARGET. The aws-node Daemonset will periodically check that a sufficient number of ENIs are attached. A sufficient number of ENIs are attached when all of the WARM_ENI_TARGET, or WARM_IP_TARGET and MINIMUM_IP_TARGET conditions are met. If there are insufficient ENIs attached, the CNI will make an API call to EC2 to attach more until MAX_ENI limit is reached.

  • WARM_ENI_TARGET - Integer, Values >0 indicate requirement Enabled
  • The number of Warm ENIs to be maintained. An ENI is \u201cwarm\u201d when it is attached as a secondary ENI to a node, but it is not in use by any Pod. More specifically, no IP addresses of the ENI have been associated with a Pod.
  • Example: Consider an instance with 2 ENIs, each ENI supporting 5 IP addresses. WARM_ENI_TARGET is set to 1. If exactly 5 IP addresses are associated with the instance, the CNI maintains 2 ENIs attached to the instance. The first ENI is in use, and all 5 possible IP addresses of this ENI are used. The second ENI is \u201cwarm\u201d with all 5 IP addresses in pool. If another Pod is launched on the instance, a 6th IP address will be needed. The CNI will assign this 6th Pod an IP address from the second ENI and from 5 IPs from the pool. The second ENI is now in use, and no longer in a \u201cwarm\u201d status. The CNI will allocate a 3rd ENI to maintain at least 1 warm ENI.

Note

The warm ENIs still consume IP addresses from the CIDR of your VPC. IP addresses are \u201cunused\u201d or \u201cwarm\u201d until they are associated with a workload, such as a Pod.

  • WARM_IP_TARGET, Integer, Values >0 indicate requirement Enabled
  • The number of Warm IP addresses to be maintained. A Warm IP is available on an actively attached ENI, but has not been assigned to a Pod. In other words, the number of Warm IPs available is the number of IPs that may be assigned to a Pod without requiring an additional ENI.
  • Example: Consider an instance with 1 ENI, each ENI supporting 20 IP addresses. WARM_IP_TARGET is set to 5. WARM_ENI_TARGET is set to 0. Only 1 ENI will be attached until a 16th IP address is needed. Then, the CNI will attach a second ENI, consuming 20 possible addresses from the subnet CIDR.
  • MINIMUM_IP_TARGET, Integer, Values >0 indicate requirement Enabled
  • The minimum number of IP addresses to be allocated at any time. This is commonly used to front-load the assignment of multiple ENIs at instance launch.
  • Example: Consider a newly launched instance. It has 1 ENI and each ENI supports 10 IP addresses. MINIMUM_IP_TARGET is set to 100. The ENI immediately attaches 9 more ENIs for a total of 100 addresses. This happens regardless of any WARM_IP_TARGET or WARM_ENI_TARGET values.

This project includes a Subnet Calculator Excel Document. This calculator document simulates the IP address consumption of a specified workload under different ENI configuration options, such as WARM_IP_TARGET and WARM_ENI_TARGET.

When Kubelet receives an add Pod request, the CNI binary queries ipamd for an available IP address, which ipamd then provides to the Pod. The CNI binary wires up the host and Pod network.

Pods deployed on a node are, by default, assigned to the same security groups as the primary ENI. Alternatively, Pods may be configured with different security groups.

As the pool of IP addresses is depleted, the plugin automatically attaches another elastic network interface to the instance and allocates another set of secondary IP addresses to that interface. This process continues until the node can no longer support additional elastic network interfaces.

When a Pod is deleted, VPC CNI places the Pod\u2019s IP address in a 30-second cool down cache. The IPs in a cool down cache are not assigned to new Pods. When the cooling-off period is over, VPC CNI moves Pod IP back to the warm pool. The cooling-off period prevents Pod IP addresses from being recycled prematurely and allows kube-proxy on all cluster nodes to finish updating the iptables rules. When the number of IPs or ENIs exceeds the number of warm pool settings, the ipamd plugin returns IPs and ENIs to the VPC.

As described above in Secondary IP mode, each Pod receives one secondary private IP address from one of the ENIs attached to an instance. Since each Pod uses an IP address, the number of Pods you can run on a particular EC2 Instance depends on how many ENIs can be attached to it and how many IP addresses it supports. The VPC CNI checks the limits file to find out how many ENIs and IP addresses are allowed for each type of instance.

You can use the following formula to determine maximum number of Pods you can deploy on a node.

(Number of network interfaces for the instance type \u00d7 (the number of IP addresses per network interface - 1)) + 2

The +2 indicates Kubernetes Pods that use host networking, such as kube-proxy and VPC CNI. Amazon EKS requires kube-proxy and VPC CNI to be running on every node and are calculated towards max-pods. Consider updating max-pods if you plan to run more host networking Pods. You can specify --kubelet-extra-args \"\u2014max-pods=110\" as user data in the launch template.

As an example, on a cluster with 3 c5.large nodes (3 ENIs and max 10 IPs per ENI), when the cluster starts up and has 2 CoreDNS pods, the CNI will consume 49 IP addresses and keeps them in warm pool. The warm pool enables faster Pod launches when the application is deployed.

Node 1 (with CoreDNS pod): 2 ENIs, 20 IPs assigned

Node 2 (with CoreDNS pod): 2 ENIs, 20 IPs assigned

Node 3 (no Pod): 1 ENI. 10 IPs assigned.

Keep in mind that infrastructure pods, often running as daemon sets, each contribute to the max-pod count. These can include:

  • CoreDNS
  • Amazon Elastic LoadBalancer
  • Operational pods for metrics-server

We suggest that you plan your infrastructure by combining these Pods' capacities. For a list of the maximum number of Pods supported by each instance type, see eni-max-Pods.txt on GitHub.

"},{"location":"networking/vpc-cni/#recommendations","title":"Recommendations","text":""},{"location":"networking/vpc-cni/#deploy-vpc-cni-managed-add-on","title":"Deploy VPC CNI Managed Add-On","text":"

When you provision a cluster, Amazon EKS installs VPC CNI automatically. Amazon EKS nevertheless supports managed add-ons that enable the cluster to interact with underlying AWS resources such as computing, storage, and networking. We highly recommend that you deploy clusters with managed add-ons including VPC CNI.

Amazon EKS managed add-on offer VPC CNI installation and management for Amazon EKS clusters. Amazon EKS add-ons include the latest security patches, bug fixes, and are validated by AWS to work with Amazon EKS. The VPC CNI add-on enables you to continuously ensure the security and stability of your Amazon EKS clusters and decrease the amount of effort required to install, configure, and update add-ons. Additionally, a managed add-on can be added, updated, or deleted via the Amazon EKS API, AWS Management Console, AWS CLI, and eksctl.

You can find the managed fields of VPC CNI using --show-managed-fields flag with the kubectl get command.

kubectl get daemonset aws-node --show-managed-fields -n kube-system -o yaml\n

Managed add-ons prevents configuration drift by automatically overwriting configurations every 15 minutes. This means that any changes to managed add-ons, made via the Kubernetes API after add-on creation, will overwrite by the automated drift-prevention process and also set to defaults during add-on update process.

The fields managed by EKS are listed under managedFields with manager as EKS. Fields managed by EKS include service account, image, image url, liveness probe, readiness probe, labels, volumes, and volume mounts.

Info

The most frequently used fields such as WARM_ENI_TARGET, WARM_IP_TARGET, and MINIMUM_IP_TARGET are not managed and will not be reconciled. The changes to these fields will be preserved upon updating of the add-on.

We suggest testing the add-on behavior in your non-production clusters for a specific configuration before updating production clusters. Additionally, follow the steps in the EKS user guide for add-on configurations.

"},{"location":"networking/vpc-cni/#migrate-to-managed-add-on","title":"Migrate to Managed Add-On","text":"

You will manage the version compatibility and update the security patches of self-managed VPC CNI. To update a self-managed add-on, you must use the Kubernetes APIs and instructions outlined in the EKS user guide. We recommend migrating to a managed add-on for existing EKS clusters and highly suggest creating a backup of your current CNI settings prior to migration. To configure managed add-ons, you can utilize the Amazon EKS API, AWS Management Console, or AWS Command Line Interface.

kubectl apply view-last-applied daemonset aws-node -n kube-system > aws-k8s-cni-old.yaml\n

Amazon EKS will replace the CNI configuration settings if the field is listed as managed with default settings. We caution against modifying the managed fields. The add-on does not reconcile configuration fields such as the warm environment variables and CNI modes. The Pods and applications will continue to run while you migrate to a managed CNI.

"},{"location":"networking/vpc-cni/#backup-cni-settings-before-update","title":"Backup CNI Settings Before Update","text":"

VPC CNI runs on customer data plane (nodes), and hence Amazon EKS does not automatically update the add-on (managed and self-managed) when new versions are released or after you update your cluster to a new Kubernetes minor version. To update the add-on for an existing cluster, you must trigger an update via update-addon API or clicking update now link in the EKS console for add-ons. If you have deployed self-managed add-on, follow steps mentioned under updating self-managed VPC CNI add-on.

We strongly recommend that you update one minor version at a time. For example, if your current minor version is 1.9 and you want to update to 1.11, you should update to the latest patch version of 1.10 first, then update to the latest patch version of 1.11.

Perform an inspection of the aws-node Daemonset before updating Amazon VPC CNI. Take a backup of existing settings. If using a managed add-on, confirm that you have not updated any settings that Amazon EKS might override. We recommend a post update hook in your automation workflow or a manual apply step after an add-on update.

kubectl apply view-last-applied daemonset aws-node -n kube-system > aws-k8s-cni-old.yaml\n

For a self-managed add-on, compare the backup with releases on GitHub to see the available versions and familiarize yourself with the changes in the version that you want to update to. We recommend using Helm to manage self-managed add-ons and leverage values files to apply settings. Any update operations involving Daemonset delete will result in application downtime and must be avoided.

"},{"location":"networking/vpc-cni/#understand-security-context","title":"Understand Security Context","text":"

We strongly suggest you to understand the security contexts configured for managing VPC CNI efficiently. Amazon VPC CNI has two components CNI binary and ipamd (aws-node) Daemonset. The CNI runs as a binary on a node and has access to node root file system, also has privileged access as it deals with iptables at the node level. The CNI binary is invoked by the kubelet when Pods gets added or removed.

The aws-node Daemonset is a long-running process responsible for IP address management at the node level. The aws-node runs in hostNetwork mode and allows access to the loopback device, and network activity of other pods on the same node. The aws-node init-container runs in privileged mode and mounts the CRI socket allowing the Daemonset to monitor IP usage by the Pods running on the node. Amazon EKS is working to remove the privileged requirement of aws-node init container. Additionally, the aws-node needs to update NAT entries and to load the iptables modules and hence runs with NET_ADMIN privileges.

Amazon EKS recommends deploying the security policies as defined by the aws-node manifest for IP management for the Pods and networking settings. Please consider updating to the latest version of VPC CNI. Furthermore, please consider opening a GitHub issue if you have a specific security requirement.

"},{"location":"networking/vpc-cni/#use-separate-iam-role-for-cni","title":"Use separate IAM role for CNI","text":"

The AWS VPC CNI requires AWS Identity and Access Management (IAM) permissions. The CNI policy needs to be set up before the IAM role can be used. You can use AmazonEKS_CNI_Policy, which is an AWS managed policy for IPv4 clusters. AmazonEKS CNI managed policy only has permissions for IPv4 clusters. You must create a separate IAM policy for IPv6 clusters with the permissions listed here.

By default, VPC CNI inherits the Amazon EKS node IAM role (both managed and self-managed node groups).

Configuring a separate IAM role with the relevant policies for Amazon VPC CNI is strongly recommended. If not, the pods of Amazon VPC CNI gets the permission assigned to the node IAM role and have access to the instance profile assigned to the node.

The VPC CNI plugin creates and configures a service account called aws-node. By default, the service account binds to the Amazon EKS node IAM role with Amazon EKS CNI policy attached. To use the separate IAM role, we recommend that you create a new service account with Amazon EKS CNI policy attached. To use a new service account you must redeploy the CNI pods. Consider specifying a --service-account-role-arn for VPC CNI managed add-on when creating new clusters. Make sure you remove Amazon EKS CNI policy for both IPv4 and IPv6 from Amazon EKS node role.

It is advised that you block access instance metadata to minimize the blast radius of security breach.

"},{"location":"networking/vpc-cni/#handle-livenessreadiness-probe-failures","title":"Handle Liveness/Readiness Probe failures","text":"

We advise increasing the liveness and readiness probe timeout values (default timeoutSeconds: 10) for EKS 1.20 an later clusters to prevent probe failures from causing your application's Pod to become stuck in a containerCreating state. This problem has been seen in data-intensive and batch-processing clusters. High CPU use causes aws-node probe health failures, leading to unfulfilled Pod CPU requests. In addition to modifying the probe timeout, ensure that the CPU resource requests (default CPU: 25m) for aws-node are correctly configured. We do not suggest updating the settings unless your node is having issues.

We highly encourage you to run sudo bash /opt/cni/bin/aws-cni-support.sh on a node while you engage Amazon EKS support. The script will assist in evaluating kubelet logs and memory utilization on the node. Please consider installing SSM Agent on Amazon EKS worker nodes to run the script.

"},{"location":"networking/vpc-cni/#configure-iptables-forward-policy-on-non-eks-optimized-ami-instances","title":"Configure IPTables Forward Policy on non-EKS Optimized AMI Instances","text":"

If you are using custom AMI, make sure to set iptables forward policy to ACCEPT under kubelet.service. Many systems set the iptables forward policy to DROP. You can build custom AMI using HashiCorp Packer and a build specification with resources and configuration scripts from the Amazon EKS AMI repository on AWS GitHub. You can update the kubelet.service and follow the instructions specified here to create a custom AMI.

"},{"location":"networking/vpc-cni/#routinely-upgrade-cni-version","title":"Routinely Upgrade CNI Version","text":"

The VPC CNI is backward compatible. The latest version works with all Amazon EKS supported Kubernetes versions. Additionally, the VPC CNI is offered as an EKS add-on (see \u201cDeploy VPC CNI Managed Add-On\u201d above). While EKS add-ons orchestrates upgrades of add-ons, it will not automatically upgrade add-ons like the CNI because they run on the data plane. You are responsible for upgrading the VPC CNI add-on following managed and self-managed worker node upgrades.

"},{"location":"performance/performance_WIP/","title":"Performance Efficiency Pillar","text":"

The performance efficiency pillar focuses on the efficient use of computing resources to meet requirements and how to maintain that efficiency as demand changes and technologies evolve. This section provides in-depth, best practices guidance for architecting for performance efficiency on AWS.

"},{"location":"performance/performance_WIP/#definition","title":"Definition","text":"

To ensure the efficient use of EKS container services, you should gather data on all aspects of the architecture, from the high-level design to the selection of EKS resource types. By reviewing your choices on a regular basis, you ensure that you are taking advantage of the continually evolving Amazon EKS and Container services. Monitoring will ensure that you are aware of any deviance from expected performance so you can take action on it.

Performance efficiency for EKS containers is composed of three areas:

  • Optimize your container

  • Resource Management

  • Scalability Management

"},{"location":"performance/performance_WIP/#best-practices","title":"Best Practices","text":""},{"location":"performance/performance_WIP/#optimize-your-container","title":"Optimize your container","text":"

You can run most applications in a Docker container without too much hassle. There are a number of things that you need to do to ensure it's running effectively in a production environment, including streamlining the build process. The following best practices will help you to achieve that.

"},{"location":"performance/performance_WIP/#recommendations","title":"Recommendations","text":"
  • Make your container images stateless: A container created with a Docker image should be ephemeral and immutable. In other words, the container should be disposable and independent, i.e. a new one can be built and put in place with absolutely no configuration changes. Design your containers to be stateless. If you would like to use persistent data, use volumes instead. If you would like to store secrets or sensitive application data used by services, you can use solutions like AWS Systems ManagerParameter Store or third-party offerings or open source solutions, such as HashiCorp Valut and Consul, for runtime configurations.
  • Minimal base image : Start with a small base image. Every other instruction in the Dockerfile builds on top of this image. The smaller the base image, the smaller the resulting image is, and the more quickly it can be downloaded. For example, the alpine:3.7 image is 71 MB smaller than the centos:7 image. You can even use the scratch base image, which is an empty image on which you can build your own runtime environment.
  • Avoid unnecessary packages: When building a container image, include only the dependencies what your application needs and avoid installing unnecessary packages. For example if your application does not need an SSH server, don't include one. This will reduce complexity, dependencies, file sizes, and build times. To exclude files not relevant to the build use a .dockerignore file.
  • Use multi-stage build:Multi-stage builds allow you to build your application in a first \"build\" container and use the result in another container, while using the same Dockerfile. To expand a bit on that, in multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don't want in the final image. This method drastically reduces the size of your final image, without struggling to reduce the number of intermediate layers and files.
  • Minimize number of layers: Each instruction in the Dockerfile adds an extra layer to the Docker image. The number of instructions and layers should be kept to a minimum as this affects build performance and time. For example, the first instruction below will create multiple layers, whereas the second instruction by using &&(chaining) we reduced the number of layers, which will help provide better performance. The is the best way to reduce the number of layers that will be created in your Dockerfile.
  •         RUN apt-get -y update\n        RUN apt-get install -y python\n        RUN apt-get -y update && apt-get install -y python\n
  • Properly tag your images: When building images, always tag them with useful and meaningful tags. This is a good way to organize and document metadata describing an image, for example, by including a unique counter like build id from a CI server (e.g. CodeBuild or Jenkins) to help with identifying the correct image. The tag latest is used by default if you do not provide one in your Docker commands. We recommend not to use the automatically created latest tag, because by using this tag you'll automatically be running future major releases, which could include breaking changes for your application. The best practice is to avoid the latest tag and instead use the unique digest created by your CI server.

  • Use Build Cache to improve build speed : The cache allows you to take advantage of existing cached images, rather than building each image from scratch. For example, you should add the source code of your application as late as possible in your Dockerfile so that the base image and your application's dependencies get cached and aren't rebuilt on every build. To reuse already cached images, By default in Amazon EKS, the kubelet will try to pull each image from the specified registry. However, if the imagePullPolicy property of the container is set to IfNotPresent or Never, then a local image is used (preferentially or exclusively, respectively).
  • Image Security : Using public images may be a great way to start working on containers and deploying it to Kubernetes. However, using them in production can come with a set of challenges. Especially when it comes to security. Ensure to follow the best practices for packaging and distributing the containers/applications. For example, don't build your containers with passwords baked in also you might need to control what's inside them. Recommend to use private repository such as Amazon ECR and leverage the in-built image scanning feature to identify software vulnerabilities in your container images.

  • Right size your containers: As you develop and run applications in containers, there are a few key areas to consider. How you size containers and manage your application deployments can negatively impact the end-user experience of services that you provide. To help you succeed, the following best practices will help you right size your containers. After you determine the number of resources required for your application, you should set requests and limits Kubernetes to ensure that your applications are running correctly.

(a) Perform testing of the application: to gather vital statistics and other performance \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Based upon this data you can work out the optimal configuration, in terms of memory and \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CPU, for your container. Vital statistics such as : CPU, Latency, I/O, Memory usage, \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Network . Determine expected, mean, and peak container memory and CPU usage by \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0doing a separate load test if necessary. Also consider all the processes that might \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0potentially run in parallel in the container.

\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Recommend to use CloudWatch Container insights or partner products, which will give \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0you the right information to size containers and the Worker nodes.

(b)Test services independently: As many applications depend on each other in a true \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0microservice architecture, you need to test them with a high degree of independence \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0meaning that the services are both able to properly function by themselves, as well as \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0function as part of a cohesive system.

"},{"location":"performance/performance_WIP/#resource-management","title":"Resource Management","text":"

One of the most common questions that asked in the adoption of Kubernetes is \"What should I put in a Pod?\". For example, a three tier LAMP application container. Should we keep this application in the same pod? Well, this works effectively as a single pod but this is an example of an anti-pattern for Pod creation. There are two reasons for that

(a) If you have both the containers in the same Pod, you are forced to use the same scaling strategy which is not ideal for production environment also you can't effectively manage or constraint resources based on the usage. E.g: you might need to scale just the frontend not frontend and backend (MySQL) as a unit also if you would like to increase the resources dedicated just to the backend, you cant just do that.

(b) If you have two separate pods, one for frontend and other for backend. Scaling would be very easy and you get a better reliability.

The above might not work in all the use-cases. In the above example frontend and backend may land in different machines and they will communicate with each other via network, So you need to ask the question \"Will my application work correctly, If they are placed and run on different machines?\" If the answer is a \"no\" may be because of the application design or for some other technical reasons, then grouping of containers in a single pod makes sense. If the answer is \"Yes\" then multiple Pods is the correct approach.

"},{"location":"performance/performance_WIP/#recommendations_1","title":"Recommendations","text":"
  • Package a single application per container: A container works best when a single application runs inside it. This application should have a single parent process. For example, do not run PHP and MySQL in the same container: it's harder to debug, and you can't horizontally scale the PHP container alone. This separation allows you to better tie the lifecycle of the application to that of the container. Your containers should be both stateless and immutable. Stateless means that any state (persistent data of any kind) is stored outside of the container, for example, you can use different kinds of external storage like Persistent disk, Amazon EBS, and Amazon EFS if needed, or managed database like Amazon RDS. Immutable means that a container will not be modified during its life: no updates, no patches, and no configuration changes. To update the application code or apply a patch, you build a new image and deploy it.

  • Use Labels to Kubernetes Objects: Labels allow Kubernetes objects to be queried and operated upon in bulk. They can also be used to identify and organize Kubernetes objects into groups. As such defining labels should figure right at the top of any Kubernetes best practices list.

  • Setting resource request limits: Setting request limits is the mechanism used to control the amount of system resources that a container can consume such as CPU and memory. These settings are what the container is guaranteed to get when the container initially starts. If a container requests a resource, container orchestrators such as Kubernetes will only schedule it on a node that can provide that resource. Limits, on the other hand, make sure that a container never goes above a certain value. The container is only allowed to go up to the limit, and then it is restricted.

\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 In the below example Pod manifest, we add a limit of 1.0 CPU and 256 MB of memory

        apiVersion: v1\n        kind: Pod\n        metadata:\n          name: nginx-pod-webserver\n          labels:\n            name: nginx-pod\n        spec:\n          containers:\n          - name: nginx\n            image: nginx:latest\n            resources:\n              limits:\n                memory: \"256Mi\"\n                cpu: \"1000m\"\n              requests:\n                memory: \"128Mi\"\n                cpu: \"500m\"\n            ports:\n            - containerPort: 80\n

\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0It's a best practice to define these requests and limits in your pod definitions. If you don't \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0include these values, the scheduler doesn't understand what resources are needed. \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Without this information, the scheduler might schedule the pod on a node without \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sufficient resources to provide acceptable application performance.

  • Limit the number of concurrent disruptions: Use PodDisruptionBudget, This settings allows you to set a policy on the minimum available and maximum unavailable pods during voluntary eviction events. An example of an eviction would be when perform maintenance on the node or draining a node.

Example: A web frontend might want to ensure that 8 Pods to be available at any \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0given time. In this scenario, an eviction can evict as many pods as it wants, as long as \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0eight are available.

apiVersion: policy/v1beta1\nkind: PodDisruptionBudget\nmetadata:\n  name: frontend-demo\nspec:\n  minAvailable: 8\n  selector:\n    matchLabels:\n      app: frontend\n

N.B: You can also specify pod disruption budget as a percentage by using maxAvailable \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0or maxUnavailable parameter.

  • Use Namespaces: Namespaces allows a physical cluster to be shared by multiple teams. A namespace allows to partition created resources into a logically named group. This allows you to set resource quotas per namespace, Role-Based Access Control (RBAC) per namespace, and also network policies per namespace. It gives you soft multitenancy features.

\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0For example, If you have three applications running on a single Amazon EKS cluster \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0accessed by three different teams which requires multiple resource constraints and \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0different levels of QoS each group you could create a namespace per team and give each \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0team a quota on the number of resources that it can utilize, such as CPU and memory.

\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0You can also specify default limits in Kubernetes namespaces level by enabling \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0LimitRange admission controller. These default limits will constrain the amount of CPU \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0or memory a given Pod can use unless the defaults are explicitly overridden by the Pod's \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0configuration.

  • Manage Resource Quota: Each namespace can be assigned resource quota. Specifying quota allows to restrict how much of cluster resources can be consumed across all resources in a namespace. Resource quota can be defined by a ResourceQuota object. A presence of ResourceQuota object in a namespace ensures that resource quotas are enforced.

  • Configure Health Checks for Pods: Health checks are a simple way to let the system know if an instance of your app is working or not. If an instance of your app is not working, then other services should not access it or send requests to it. Instead, requests should be sent to another instance of the app that is working. The system also should bring your app back to a healthy state. By default, all the running pods have the restart policy set to always which means the kubelet running within a node will automatically restart a pod when the container encounters an error. Health checks extend this capability of kubelet through the concept of container probes.

Kubernetes provides two types of health checks: readiness and liveness probes. For example, consider if one of your applications, which typically runs for long periods of time, transitions to a non-running state and can only recover by being restarted. You can use liveness probes to detect and remedy such situations. Using health checks gives your applications better reliability, and higher uptime.

  • Advanced Scheduling Techniques: Generally, schedulers ensure that pods are placed only on nodes that have sufficient free resources, and across nodes, they try to balance out the resource utilization across nodes, deployments, replicas, and so on. But sometimes you want to control how your pods are scheduled. For example, perhaps you want to ensure that certain pods are only scheduled on nodes with specialized hardware, such as requiring a GPU machine for an ML workload. Or you want to collocate services that communicate frequently.

Kubernetes offers manyadvanced scheduling featuresand multiple filters/constraints to schedule the pods on the right node. For example, when using Amazon EKS, you can usetaints and tolerationsto restrict what workloads can run on specific nodes. You can also control pod scheduling using node selectorsandaffinity and anti-affinityconstructs and even have your own custom scheduler built for this purpose.

"},{"location":"performance/performance_WIP/#scalability-management","title":"Scalability Management","text":"

Containers are stateless. They are born and when they die, they are not resurrected. There are many techniques that you can leverage on Amazon EKS, not only to scale out your containerized applications but also the Kubernetes worker node.

"},{"location":"performance/performance_WIP/#recommendations_2","title":"Recommendations","text":"
  • On Amazon EKS, you can configure Horizontal pod autoscaler,which automatically scales the number of pods in a replication controller, deployment, or replica set based on observed CPU utilization (or usecustom metricsbased on application-provided metrics).

  • You can use Vertical Pod Autoscaler which automatically adjusts the CPU and memory reservations for your pods to help \"right size\" your applications. This adjustment can improve cluster resource utilization and free up CPU and memory for other pods. This is useful in scenarios like your production database \"MongoDB\" does not scale the same way as a stateless application frontend, In this scenario you could use VPA to scale up the MongoDB Pod.

  • To enable VPA you need to use Kubernetes metrics server, which is an aggregator of resource usage data in your cluster. It is not deployed by default in Amazon EKS clusters. You need to configure it before configure VPA alternatively you can also use Prometheus to provide metrics for the Vertical Pod Autoscaler.

  • While HPA and VPA scale the deployments and pods, Cluster Autoscaler will scale-out and scale-in the size of the pool of worker nodes. It adjusts the size of a Kubernetes cluster based on the current utilization. Cluster Autoscaler increases the size of the cluster when there are pods that failed to schedule on any of the current nodes due to insufficient resources or when adding a new node would increase the overall availability of cluster resources. Please follow this step by step guide to setup Cluster Autoscaler. If you are using Amazon EKS on AWS Fargate, AWS Manages the control plane for you.

    Please have a look at the reliability pillar for detailed information.

"},{"location":"performance/performance_WIP/#monitoring","title":"Monitoring","text":""},{"location":"performance/performance_WIP/#deployment-best-practices","title":"Deployment Best Practices","text":""},{"location":"performance/performance_WIP/#trade-offs","title":"Trade-Offs","text":""},{"location":"reliability/docs/","title":"Amazon EKS Best Practices Guide for Reliability","text":"

This section provides guidance about making workloads running on EKS resilient and highly-available

"},{"location":"reliability/docs/#how-to-use-this-guide","title":"How to use this guide","text":"

This guide is meant for developers and architects who want to develop and operate highly-available and fault-tolerant services in EKS. The guide is organized into different topic areas for easier consumption. Each topic starts with a brief overview, followed by a list of recommendations and best practices for the reliability of your EKS clusters.

"},{"location":"reliability/docs/#introduction","title":"Introduction","text":"

The reliability best practices for EKS have been grouped under the following topics:

  • Applications
  • Control Plane
  • Data Plane

What makes a system reliable? If a system can function consistently and meet demands in spite of changes in its environment over a period of time, it can be called reliable. To achieve this, the system has to detect failures, automatically heal itself, and have the ability to scale based on demand.

Customers can use Kubernetes as a foundation to operate mission-critical applications and services reliably. But aside from incorporating container-based application design principles, running workloads reliably also requires a reliable infrastructure. In Kubernetes, infrastructure comprises the control plane and data plane.

EKS provides a production-grade Kubernetes control plane that is designed to be highly-available and fault-tolerant.

In EKS, AWS is responsible for the reliability of the Kubernetes control plane. EKS runs Kubernetes control plane across three availability zones in an AWS Region. It automatically manages the availability and scalability of the Kubernetes API servers and the etcd cluster.

The responsibility for the data plane\u2019s reliability is shared between you, the customer, and AWS. EKS offers three worker node options for deploying the Kubernetes data plane. Fargate, which is the most managed option, handles provisioning and scaling of the data plane. The second option, managed nodes groups, handles provisioning, and updates of the data plane. And finally, self-managed nodes is the least managed option for the data plane. The more AWS-managed data plane you use, the less responsibility you have.

Managed node groups automate the provisioning and lifecycle management of EC2 nodes. You can use the EKS API (using EKS console, AWS API, AWS CLI, CloudFormation, Terraform, or eksctl), to create, scale, and upgrade managed nodes. Managed nodes run EKS-optimized Amazon Linux 2 EC2 instances in your account, and you can install custom software packages by enabling SSH access. When you provision managed nodes, they run as part of an EKS-managed Auto Scaling Group that can span multiple Availability Zones; you control this through the subnets you provide when creating managed nodes. EKS also automatically tags managed nodes so they can be used with Cluster Autoscaler.

Amazon EKS follows the shared responsibility model for CVEs and security patches on managed node groups. Because managed nodes run the Amazon EKS-optimized AMIs, Amazon EKS is responsible for building patched versions of these AMIs when bug fixes. However, you are responsible for deploying these patched AMI versions to your managed node groups.

EKS also manages updating the nodes although you have to initiate the update process. The process of updating managed node is explained in the EKS documentation.

If you run self-managed nodes, you can use Amazon EKS-optimized Linux AMI to create worker nodes. You are responsible for patching and upgrading the AMI and the nodes. It is a best practice to use eksctl, CloudFormation, or infrastructure as code tools to provision self-managed nodes because this will make it easy for you to upgrade self-managed nodes. Consider migrating to new nodes when updating worker nodes because the migration process taints the old node group as NoSchedule and drains the nodes after a new stack is ready to accept the existing pod workload. However, you can also perform an in-place upgrade of self-managed nodes.

This guide includes a set of recommendations that you can use to improve the reliability of your EKS data plane, Kubernetes core components, and your applications.

"},{"location":"reliability/docs/#feedback","title":"Feedback","text":"

This guide is being released on GitHub to collect direct feedback and suggestions from the broader EKS/Kubernetes community. If you have a best practice that you feel we ought to include in the guide, please file an issue or submit a PR in the GitHub repository. We intend to update the guide periodically as new features are added to the service or when a new best practice evolves.

"},{"location":"reliability/docs/application/","title":"Running highly-available applications","text":"

Your customers expect your application to be always available, including when you're making changes and especially during spikes in traffic. A scalable and resilient architecture keeps your applications and services running without disruptions, which keeps your users happy. A scalable infrastructure grows and shrinks based on the needs of the business. Eliminating single points of failure is a critical step towards improving an application\u2019s availability and making it resilient.

With Kubernetes, you can operate your applications and run them in a highly-available and resilient fashion. Its declarative management ensures that once you\u2019ve set up the application, Kubernetes will continuously try to match the current state with the desired state.

"},{"location":"reliability/docs/application/#recommendations","title":"Recommendations","text":""},{"location":"reliability/docs/application/#avoid-running-singleton-pods","title":"Avoid running singleton Pods","text":"

If your entire application runs in a single Pod, then your application will be unavailable if that Pod gets terminated. Instead of deploying applications using individual pods, create Deployments. If a Pod that is created by a Deployment fails or gets terminated, the Deployment controller will start a new pod to ensure the specified number of replica Pods are always running.

"},{"location":"reliability/docs/application/#run-multiple-replicas","title":"Run multiple replicas","text":"

Running multiple replicas Pods of an app using a Deployment helps it run in a highly-available manner. If one replica fails, the remaining replicas will still function, albeit at reduced capacity until Kubernetes creates another Pod to make up for the loss. Furthermore, you can use the Horizontal Pod Autoscaler to scale replicas automatically based on workload demand.

"},{"location":"reliability/docs/application/#schedule-replicas-across-nodes","title":"Schedule replicas across nodes","text":"

Running multiple replicas won\u2019t be very useful if all the replicas are running on the same node, and the node becomes unavailable. Consider using pod anti-affinity or pod topology spread constraints to spread replicas of a Deployment across multiple worker nodes.

You can further improve a typical application\u2019s reliability by running it across multiple AZs.

"},{"location":"reliability/docs/application/#using-pod-anti-affinity-rules","title":"Using Pod anti-affinity rules","text":"

The manifest below tells Kubernetes scheduler to prefer to place pods on separate nodes and AZs. It doesn\u2019t require distinct nodes or AZ because if it did, then Kubernetes will not be able to schedule any pods once there is a pod running in each AZ. If your application requires just three replicas, you can use requiredDuringSchedulingIgnoredDuringExecution for topologyKey: topology.kubernetes.io/zone, and Kubernetes scheduler will not schedule two pods in the same AZ.

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: spread-host-az\n  labels:\n    app: web-server\nspec:\n  replicas: 4\n  selector:\n    matchLabels:\n      app: web-server\n  template:\n    metadata:\n      labels:\n        app: web-server\n    spec:\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: app\n                  operator: In\n                  values:\n                  - web-server\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: app\n                  operator: In\n                  values:\n                  - web-server\n              topologyKey: kubernetes.io/hostname \n            weight: 99\n      containers:\n      - name: web-app\n        image: nginx:1.16-alpine\n
"},{"location":"reliability/docs/application/#using-pod-topology-spread-constraints","title":"Using Pod topology spread constraints","text":"

Similar to pod anti-affinity rules, pod topology spread constraints allow you to make your application available across different failure (or topology) domains like hosts or AZs. This approach works very well when you're trying to ensure fault tolerance as well as availability by having multiple replicas in each of the different topology domains. Pod anti-affinity rules, on the other hand, can easily produce a result where you have a single replica in a topology domain because the pods with an anti-affinity toward each other have a repelling effect. In such cases, a single replica on a dedicated node isn't ideal for fault tolerance nor is it a good use of resources. With topology spread constraints, you have more control over the spread or distribution that the scheduler should try to apply across the topology domains. Here are some important properties to use in this approach: 1. The maxSkew is used to control or determine the maximum point to which things can be uneven across the topology domains. For example, if an application has 10 replicas and is deployed across 3 AZs, you can't get an even spread, but you can influence how uneven the distribution will be. In this case, the maxSkew can be anything between 1 and 10. A value of 1 means you can potentially end up with a spread like 4,3,3, 3,4,3 or 3,3,4 across the 3 AZs. In contrast, a value of 10 means you can potentially end up with a spread like 10,0,0, 0,10,0 or 0,0,10 across 3 AZs. 2. The topologyKey is a key for one of the node labels and defines the type of topology domain that should be used for the pod distribution. For example, a zonal spread would have the following key-value pair:

topologyKey: \"topology.kubernetes.io/zone\"\n
3. The whenUnsatisfiable property is used to determine how you want the scheduler to respond if the desired constraints can't be satisfied. 4. The labelSelector is used to find matching pods so that the scheduler can be aware of them when deciding where to place pods in accordance with the constraints that you specify.

In addition to these above, there are other fields that you can read about further in the Kubernetes documentation.

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: spread-host-az\n  labels:\n    app: web-server\nspec:\n  replicas: 10\n  selector:\n    matchLabels:\n      app: web-server\n  template:\n    metadata:\n      labels:\n        app: web-server\n    spec:\n      topologySpreadConstraints:\n      - maxSkew: 1\n        topologyKey: \"topology.kubernetes.io/zone\"\n        whenUnsatisfiable: ScheduleAnyway\n        labelSelector:\n          matchLabels:\n            app: express-test\n      containers:\n      - name: web-app\n        image: nginx:1.16-alpine\n
"},{"location":"reliability/docs/application/#run-kubernetes-metrics-server","title":"Run Kubernetes Metrics Server","text":"

Install the Kubernetes metrics server to help scale your applications. Kubernetes autoscaler add-ons like HPA and VPA need to track metrics of applications to scale them. The metrics-server collects resource metrics that can be used to make scaling decisions. The metrics are collected from kubelets and served in Metrics API format.

The metrics server doesn\u2019t retain any data, and it\u2019s not a monitoring solution. Its purpose is to expose CPU and memory usage metrics to other systems. If you want to track your application's state over time, you need a monitoring tool like Prometheus or Amazon CloudWatch.

Follow the EKS documentation to install metrics-server in your EKS cluster.

"},{"location":"reliability/docs/application/#horizontal-pod-autoscaler-hpa","title":"Horizontal Pod Autoscaler (HPA)","text":"

HPA can automatically scale your application in response to demand and help you avoid impacting your customers during peak traffic. It is implemented as a control loop in Kubernetes that periodically queries metrics from APIs that provide resource metrics.

HPA can retrieve metrics from the following APIs: 1. metrics.k8s.io also known as Resource Metrics API \u2014 Provides CPU and memory usage for pods 2. custom.metrics.k8s.io \u2014 Provides metrics from other metric collectors like Prometheus; these metrics are internal to your Kubernetes cluster. 3. external.metrics.k8s.io \u2014 Provides metrics that are external to your Kubernetes cluster (E.g., SQS Queue Depth, ELB latency).

You must use one of these three APIs to provide the metric to scale your application.

"},{"location":"reliability/docs/application/#scaling-applications-based-on-custom-or-external-metrics","title":"Scaling applications based on custom or external metrics","text":"

You can use custom or external metrics to scale your application on metrics other than CPU or memory utilization. Custom Metrics API servers provide the custom-metrics.k8s.io API that HPA can use to autoscale applications.

You can use the Prometheus Adapter for Kubernetes Metrics APIs to collect metrics from Prometheus and use with the HPA. In this case, Prometheus adapter will expose Prometheus metrics in Metrics API format.

Once you deploy the Prometheus Adapter, you can query custom metrics using kubectl. kubectl get \u2014raw /apis/custom.metrics.k8s.io/v1beta1/

External metrics, as the name suggests, provide the Horizontal Pod Autoscaler the ability to scale deployments using metrics that are external to the Kubernetes cluster. For example, in batch processing workloads, it is common to scale the number of replicas based on the number of jobs in flight in an SQS queue.

To autoscale Kubernetes workloads you can use KEDA (Kubernetes Event-driven Autoscaling), an open-source project that can drive container scaling based on a number of custom events. This AWS blog outlines how to use Amazon Managed Service for Prometheus for Kubernetes workload auto-scaling.

"},{"location":"reliability/docs/application/#vertical-pod-autoscaler-vpa","title":"Vertical Pod Autoscaler (VPA)","text":"

VPA automatically adjusts the CPU and memory reservation for your Pods to help you \u201cright-size\u201d your applications. For applications that need to be scaled vertically - which is done by increasing resource allocation - you can use VPA to automatically scale Pod replicas or provide scaling recommendations.

Your application may become temporarily unavailable if VPA needs to scale it because VPA\u2019s current implementation does not perform in-place adjustments to Pods; instead, it will recreate the Pod that needs to be scaled.

EKS Documentation includes a walkthrough for setting up VPA.

Fairwinds Goldilocks project provides a dashboard to visualize VPA recommendations for CPU and memory requests and limits. Its VPA update mode allows you to auto-scale Pods based on VPA recommendations.

"},{"location":"reliability/docs/application/#updating-applications","title":"Updating applications","text":"

Modern applications require rapid innovation with a high degree of stability and availability. Kubernetes gives you the tools to update your applications continuously without disrupting your customers.

Let\u2019s look at some of the best practices that make it possible to quickly deploy changes without sacrificing availability.

"},{"location":"reliability/docs/application/#have-a-mechanism-to-perform-rollbacks","title":"Have a mechanism to perform rollbacks","text":"

Having an undo button can evade disasters. It is a best practice to test deployments in a separate lower environment (test or development environment) before updating the production cluster. Using a CI/CD pipeline can help you automate and test deployments. With a continuous deployment pipeline, you can quickly revert to the older version if the upgrade happens to be defective.

You can use Deployments to update a running application. This is typically done by updating the container image. You can use kubectl to update a Deployment like this:

kubectl --record deployment.apps/nginx-deployment set image nginx-deployment nginx=nginx:1.16.1\n

The --record argument record the changes to the Deployment and helps you if you need to perform a rollback. kubectl rollout history deployment shows you the recorded changes to Deployments in your cluster. You can rollback a change using kubectl rollout undo deployment <DEPLOYMENT_NAME>.

By default, when you update a Deployment that requires a recreation of pods, Deployment will perform a rolling update. In other words, Kubernetes will only update a portion of the running pods in a Deployment and not all the Pods at once. You can control how Kubernetes performs rolling updates through RollingUpdateStrategy property.

When performing a rolling update of a Deployment, you can use the Max Unavailable property to specify the maximum number of Pods that can be unavailable during the update. The Max Surge property of Deployment allows you to set the maximum number of Pods that can be created over the desired number of Pods.

Consider adjusting max unavailable to ensure that a rollout doesn\u2019t disrupt your customers. For example, Kubernetes sets 25% max unavailable by default, which means if you have 100 Pods, you may have only 75 Pods actively working during a rollout. If your application needs a minimum of 80 Pods, this rollout can be disruptive. Instead, you can set max unavailable to 20% to ensure that there are at least 80 functional Pods throughout the rollout.

"},{"location":"reliability/docs/application/#use-bluegreen-deployments","title":"Use blue/green deployments","text":"

Changes are inherently risky, but changes that cannot be undone can be potentially catastrophic. Change procedures that allow you to effectively turn back time through a rollback make enhancements and experimentation safer. Blue/green deployments give you a method to quickly retract the changes if things go wrong. In this deployment strategy, you create an environment for the new version. This environment is identical to the current version of the application being updated. Once the new environment is provisioned, traffic is routed to the new environment. If the new version produces the desired results without generating errors, the old environment is terminated. Otherwise, traffic is restored to the old version.

You can perform blue/green deployments in Kubernetes by creating a new Deployment that is identical to the existing version\u2019s Deployment. Once you verify that the Pods in the new Deployment are running without errors, you can start sending traffic to the new Deployment by changing the selector spec in the Service that routes traffic to your application\u2019s Pods.

Many continuous integration tools such as Flux, Jenkins, and Spinnaker let you automate blue/green deployments. AWS Containers Blog includes a walkthrough using AWS Load Balancer Controller: Using AWS Load Balancer Controller for blue/green deployment, canary deployment and A/B testing

"},{"location":"reliability/docs/application/#use-canary-deployments","title":"Use Canary deployments","text":"

Canary deployments are a variant of blue/green deployments that can significantly remove risk from changes. In this deployment strategy, you create a new Deployment with fewer Pods alongside your old Deployment, and divert a small percentage of traffic to the new Deployment. If metrics indicate that the new version is performing as well or better than the existing version, you progressively increase traffic to the new Deployment while scaling it up until all traffic is diverted to the new Deployment. If there's an issue, you can route all traffic to the old Deployment and stop sending traffic to the new Deployment.

Although Kubernetes offers no native way to perform canary deployments, you can use tools such as Flagger with Istio or App Mesh.

"},{"location":"reliability/docs/application/#health-checks-and-self-healing","title":"Health checks and self-healing","text":"

No software is bug-free, but Kubernetes can help you to minimize the impact of software failures. In the past, if an application crashed, someone had to remediate the situation by restarting the application manually. Kubernetes gives you the ability to detect software failures in your Pods and automatically replace them with new replicas. With Kubernetes you can monitor the health of your applications and automatically replace unhealthy instances.

Kubernetes supports three types of health-checks:

  1. Liveness probe
  2. Startup probe (supported in Kubernetes version 1.16+)
  3. Readiness probe

Kubelet, the Kubernetes agent, is responsible for running all the above-mentioned checks. Kubelet can check a Pods' health in three ways: kubelet can either run a shell command inside a Pod's container, send an HTTP GET request to its container, or open a TCP socket on a specified port.

If you choose an exec-based probe, which runs a shell script inside a container, ensure that the shell command exits before the timeoutSeconds value expires. Otherwise, your node will have <defunct> processes, leading to node failure.

"},{"location":"reliability/docs/application/#recommendations_1","title":"Recommendations","text":""},{"location":"reliability/docs/application/#use-liveness-probe-to-remove-unhealthy-pods","title":"Use Liveness Probe to remove unhealthy pods","text":"

The Liveness probe can detect deadlock conditions where the process continues to run, but the application becomes unresponsive. For example, if you are running a web service that listens on port 80, you can configure a Liveness probe to send an HTTP GET request on Pod\u2019s port 80. Kubelet will periodically send a GET request to the Pod and expect a response; if the Pod responds between 200-399 then the kubelet considers that Pod is healthy; otherwise, the Pod will be marked as unhealthy. If a Pod fails health-checks continuously, the kubelet will terminate it.

You can use initialDelaySeconds to delay the first probe.

When using the Liveness Probe, ensure that your application doesn\u2019t run into a situation in which all Pods simultaneously fail the Liveness Probe because Kubernetes will try to replace all your Pods, which will render your application offline. Furthermore, Kubernetes will continue to create new Pods that will also fail Liveness Probes, putting unnecessary strain on the control plane. Avoid configuring the Liveness Probe to depend on an a factor that is external to your Pod, for example, a external database. In other words, a non-responsive external-to-your-Pod database shouldn\u2019t make your Pods fail their Liveness Probes.

Sandor Sz\u00fccs\u2019s post LIVENESS PROBES ARE DANGEROUS describes problems that can be caused by misconfigured probes.

"},{"location":"reliability/docs/application/#use-startup-probe-for-applications-that-take-longer-to-start","title":"Use Startup Probe for applications that take longer to start","text":"

When your app needs additional time to startup, you can use the Startup Probe to delay the Liveness and Readiness Probe. For example, a Java app that needs to hydrate cache from a database may need up to two minutes before it is fully functional. Any Liveness or Readiness Probe until it becomes fully functional might fail. Configuring a Startup Probe will allow the Java app to become healthy before Liveness or Readiness Probe are executed.

Until the Startup Probe succeeds, all the other Probes are disabled. You can define the maximum time Kubernetes should wait for application startup. If, after the maximum configured time, the Pod still fails Startup Probes, it will be terminated, and a new Pod will be created.

The Startup Probe is similar to the Liveness Probe -- if they fail, the Pod is recreated. As Ricardo A. explains in his post Fantastic Probes And How To Configure Them, Startup Probes should be used when the startup time of an application is unpredictable. If you know your application needs ten seconds to start, you should use Liveness/Readiness Probe with initialDelaySeconds instead.

"},{"location":"reliability/docs/application/#use-readiness-probe-to-detect-partial-unavailability","title":"Use Readiness Probe to detect partial unavailability","text":"

While the Liveness probe detects failures in an app that are resolved by terminating the Pod (hence, restarting the app), Readiness Probe detects conditions where the app may be temporarily unavailable. In these situations, the app may become temporarily unresponsive; however, it is expected to be healthy again once this operation completes.

For example, during intense disk I/O operations, applications may be temporarily unavailable to handle requests. Here, terminating the application\u2019s Pod is not a remedy; at the same time, additional requests sent to the Pod can fail.

You can use the Readiness Probe to detect temporary unavailability in your app and stop sending requests to its Pod until it becomes functional again. Unlike Liveness Probe, where a failure would result in a recreation of Pod, a failed Readiness Probe would mean that Pod will not receive any traffic from Kubernetes Service. When the Readiness Probe succeeds, Pod will resume receiving traffic from Service.

Just like the Liveness Probe, avoid configuring Readiness Probes that depend on a resource that\u2019s external to the Pod (such as a database). Here\u2019s a scenario where a poorly configured Readiness can render the application nonfunctional - if a Pod\u2019s Readiness Probe fails when the app\u2019s database is unreachable, other Pod replicas will also fail simultaneously since they share the same health-check criteria. Setting the probe in this way will ensure that whenever the database is unavailable, the Pod\u2019s Readiness Probes will fail, and Kubernetes will stop sending traffic all Pods.

A side-effect of using Readiness Probes is that they can increase the time it takes to update Deployments. New replicas will not receive traffic unless Readiness Probes are successful; until then, old replicas will continue to receive traffic.

"},{"location":"reliability/docs/application/#dealing-with-disruptions","title":"Dealing with disruptions","text":"

Pods have a finite lifetime - even if you have long-running Pods, it\u2019s prudent to ensure Pods terminate correctly when the time comes. Depending on your upgrade strategy, Kubernetes cluster upgrades may require you to create new worker nodes, which requires all Pods to be recreated on newer nodes. Proper termination handling and Pod Disruption Budgets can help you avoid service disruptions as Pods are removed from older nodes and recreated on newer nodes.

The preferred way to upgrade worker nodes is by creating new worker nodes and terminating old ones. Before terminating worker nodes, you should drain it. When a worker node is drained, all its pods are safely evicted. Safely is a key word here; when pods on a worker are evicted, they are not simply sent a SIGKILL signal. Instead, a SIGTERM signal is sent to the main process (PID 1) of each container in the Pods being evicted. After the SIGTERM signal is sent, Kubernetes will give the process some time (grace period) before a SIGKILL signal is sent. This grace period is 30 seconds by default; you can override the default by using grace-period flag in kubectl or declare terminationGracePeriodSeconds in your Podspec.

kubectl delete pod <pod name> \u2014grace-period=<seconds>

It is common to have containers in which the main process doesn\u2019t have PID 1. Consider this Python-based sample container:

$ kubectl exec python-app -it ps\n PID USER TIME COMMAND\n 1   root 0:00 {script.sh} /bin/sh ./script.sh\n 5   root 0:00 python app.py\n

In this example, the shell script receives SIGTERM, the main process, which happens to be a Python application in this example, doesn\u2019t get a SIGTERM signal. When the Pod is terminated, the Python application will be killed abruptly. This can be remediated by changing the ENTRYPOINT of the container to launch the Python application. Alternatively, you can use a tool like dumb-init to ensure that your application can handle signals.

You can also use Container hooks to execute a script or an HTTP request at container start or stop. The PreStop hook action runs before the container receives a SIGTERM signal and must complete before this signal is sent. The terminationGracePeriodSeconds value applies from when the PreStop hook action begins executing, not when the SIGTERM signal is sent.

"},{"location":"reliability/docs/application/#recommendations_2","title":"Recommendations","text":""},{"location":"reliability/docs/application/#protect-critical-workload-with-pod-disruption-budgets","title":"Protect critical workload with Pod Disruption Budgets","text":"

Pod Disruption Budget or PDB can temporarily halt the eviction process if the number of replicas of an application falls below the declared threshold. The eviction process will continue once the number of available replicas is over the threshold. You can use PDB to declare the minAvailable and maxUnavailable number of replicas. For example, if you want at least three copies of your app to be available, you can create a PDB.

apiVersion: policy/v1beta1\nkind: PodDisruptionBudget\nmetadata:\n  name: my-svc-pdb\nspec:\n  minAvailable: 3\n  selector:\n    matchLabels:\n      app: my-svc\n

The above PDB policy tells Kubernetes to halt the eviction process until three or more replicas are available. Node draining respects PodDisruptionBudgets. During an EKS managed node group upgrade, nodes are drained with a fifteen-minute timeout. After fifteen minutes, if the update is not forced (the option is called Rolling update in the EKS console), the update fails. If the update is forced, the pods are deleted.

For self-managed nodes, you can also use tools like AWS Node Termination Handler, which ensures that the Kubernetes control plane responds appropriately to events that can cause your EC2 instance to become unavailable, such as EC2 maintenance events and EC2 Spot interruptions. It uses the Kubernetes API to cordon the node to ensure no new Pods are scheduled, then drains it, terminating any running Pods.

You can use Pod anti-affinity to schedule a Deployment\u2018s Pods on different nodes and avoid PDB related delays during node upgrades.

"},{"location":"reliability/docs/application/#practice-chaos-engineering","title":"Practice chaos engineering","text":"

Chaos Engineering is the discipline of experimenting on a distributed system in order to build confidence in the system\u2019s capability to withstand turbulent conditions in production.

In his blog, Dominik Tornow explains that Kubernetes is a declarative system where \u201cthe user supplies a representation of the desired state of the system to the system. The system then considers the current state and the desired state to determine the sequence of commands to transition from the current state to the desired state.\u201d This means Kubernetes always stores the desired state and if the system deviates, Kubernetes will take action to restore the state. For example, if a worker node becomes unavailable, Kubernetes will reschedule the Pods onto another worker node. Similarly, if a replica crashes, the Deployment Contoller will create a new replica. In this way, Kubernetes controllers automatically fix failures.

Chaos engineering tools like Gremlin help you test the resiliency of your Kubernetes cluster and identify single points of failure. Tools that introduce artificial chaos in your cluster (and beyond) can uncover systemic weaknesses, present an opportunity to identify bottlenecks and misconfigurations, and rectify problems in a controlled environment. The Chaos Engineering philosophy advocates breaking things on purpose and stress testing infrastructure to minimize unanticipated downtime.

"},{"location":"reliability/docs/application/#use-a-service-mesh","title":"Use a Service Mesh","text":"

You can use a service mesh to improve your application\u2019s resiliency. Service meshes enable service-to-service communication and increase the observability of your microservices network. Most service mesh products work by having a small network proxy run alongside each service that intercepts and inspects the application\u2019s network traffic. You can place your application in a mesh without modifying your application. Using service proxy\u2019s built-in features, you can have it generate network statistics, create access logs, and add HTTP headers to outbound requests for distributed tracing.

A service mesh can help you make your microservices more resilient with features like automatic request retries, timeouts, circuit-breaking, and rate-limiting.

If you operate multiple clusters, you can use a service mesh to enable cross-cluster service-to-service communication.

"},{"location":"reliability/docs/application/#service-meshes","title":"Service Meshes","text":"
  • AWS App Mesh
  • Istio
  • LinkerD
  • Consul
"},{"location":"reliability/docs/application/#observability","title":"Observability","text":"

Observability is an umbrella term that includes monitoring, logging, and tracing. Microservices based applications are distributed by nature. Unlike monolithic applications where monitoring a single system is sufficient, in a distributed application architecture, you need to monitor each component\u2019s performance. You can use cluster-level monitoring, logging, and distributed tracing systems to identify issues in your cluster before they disrupt your customers.

Kubernetes built-in tools for troubleshooting and monitoring are limited. The metrics-server collects resource metrics and stores them in memory but doesn\u2019t persist them. You can view the logs of a Pod using kubectl, but Kubernetes doesn't automatically retain logs. And the implementation of distributed tracing is done either at the application code level or using services meshes.

Kubernetes' extensibility shines here. Kubernetes allows you to bring your preferred centralized monitoring, logging, and tracing solution.

"},{"location":"reliability/docs/application/#recommendations_3","title":"Recommendations","text":""},{"location":"reliability/docs/application/#monitor-your-applications","title":"Monitor your applications","text":"

The number of metrics you need to monitor in modern applications is growing continuously. It helps if you have an automated way to track your applications so you can focus on solving your customer\u2019s challenges. Cluster-wide monitoring tools like Prometheus or CloudWatch Container Insights can monitor your cluster and workload and provide you signals when, or preferably, before things go wrong.

Monitoring tools allow you to create alerts that your operations team can subscribe to. Consider rules to activate alarms for events that can, when exacerbated, lead to an outage or impact application performance.

If you\u2019re unclear on which metrics you should monitor, you can take inspiration from these methods:

  • RED method. Stands for requests, errors, and duration.
  • USE method. Stands for utilization, saturation, and errors.

Sysdig\u2019s post Best practices for alerting on Kubernetes includes a comprehensive list of components that can impact the availability of your applications.

"},{"location":"reliability/docs/application/#use-prometheus-client-library-to-expose-application-metrics","title":"Use Prometheus client library to expose application metrics","text":"

In addition to monitoring the state of the application and aggregating standard metrics, you can also use the Prometheus client library to expose application-specific custom metrics to improve the application's observability.

"},{"location":"reliability/docs/application/#use-centralized-logging-tools-to-collect-and-persist-logs","title":"Use centralized logging tools to collect and persist logs","text":"

Logging in EKS falls under two categories: control plane logs and application logs. EKS control plane logging provides audit and diagnostic logs directly from the control plane to CloudWatch Logs in your account. Application logs are logs produced by Pods running inside your cluster. Application logs include logs produced by Pods that run the business logic applications and Kubernetes system components such as CoreDNS, Cluster Autoscaler, Prometheus, etc.

EKS provide five types of control plane logs:

  1. Kubernetes API server component logs
  2. Audit
  3. Authenticator
  4. Controller manager
  5. Scheduler

The controller manager and scheduler logs can help diagnose control plane problems such as bottlenecks and errors. By default, EKS control plane logs aren\u2019t sent to CloudWatch Logs. You can enable control plane logging and select the types of EKS control plane logs you\u2019d like to capture for each cluster in your account

Collecting application logs requires installing a log aggregator tool like Fluent Bit, Fluentd, or CloudWatch Container Insights in your cluster.

Kubernetes log aggregator tools run as DaemonSets and scrape container logs from nodes. Application logs are then sent to a centralized destination for storage. For example, CloudWatch Container Insights can use either Fluent Bit or Fluentd to collect logs and ship them to CloudWatch Logs for storage. Fluent Bit and Fluentd support many popular log analytics systems such as Elasticsearch and InfluxDB giving you the ability to change the storage backend for your logs by modifying Fluent bit or Fluentd\u2019s log configuration.

"},{"location":"reliability/docs/application/#use-a-distributed-tracing-system-to-identify-bottlenecks","title":"Use a distributed tracing system to identify bottlenecks","text":"

A typical modern application has components distributed over the network, and its reliability depends on the proper functioning of each of the components that make up the application. You can use a distributed tracing solution to understand how requests flow and how systems communicate. Traces can show you where bottlenecks exist in your application network and prevent problems that can cause cascading failures.

You have two options to implement tracing in your applications: you can either implement distributed tracing at the code level using shared libraries or use a service mesh.

Implementing tracing at the code level can be disadvantageous. In this method, you have to make changes to your code. This is further complicated if you have polyglot applications. You\u2019re also responsible for maintaining yet another library, across your services.

Service Meshes like LinkerD, Istio, and AWS App Mesh can be used to implement distributed tracing in your application with minimal changes to the application code. You can use service mesh to standardize metrics generation, logging, and tracing.

Tracing tools like AWS X-Ray, Jaeger support both shared library and service mesh implementations.

Consider using a tracing tool like AWS X-Ray or Jaeger that supports both (shared library and service mesh) implementations so you will not have to switch tools if you later adopt service mesh.

"},{"location":"reliability/docs/controlplane/","title":"EKS Control Plane","text":"

Amazon Elastic Kubernetes Service (EKS) is a managed Kubernetes service that makes it easy for you to run Kubernetes on AWS without needing to install, operate, and maintain your own Kubernetes control plane or worker nodes. It runs upstream Kubernetes and is certified Kubernetes conformant. This conformance ensures that EKS supports the Kubernetes APIs, just like the open-source community version that you can install on EC2 or on-premises. Existing applications running on upstream Kubernetes are compatible with Amazon EKS.

EKS automatically manages the availability and scalability of the Kubernetes control plane nodes, and it automatically replaces unhealthy control plane nodes.

"},{"location":"reliability/docs/controlplane/#eks-architecture","title":"EKS Architecture","text":"

EKS architecture is designed to eliminate any single points of failure that may compromise the availability and durability of the Kubernetes control plane.

The Kubernetes control plane managed by EKS runs inside an EKS managed VPC. The EKS control plane comprises the Kubernetes API server nodes, etcd cluster. Kubernetes API server nodes that run components like the API server, scheduler, and kube-controller-manager run in an auto-scaling group. EKS runs a minimum of two API server nodes in distinct Availability Zones (AZs) within in AWS region. Likewise, for durability, the etcd server nodes also run in an auto-scaling group that spans three AZs. EKS runs a NAT Gateway in each AZ, and API servers and etcd servers run in a private subnet. This architecture ensures that an event in a single AZ doesn\u2019t affect the EKS cluster's availability.

When you create a new cluster, Amazon EKS creates a highly-available endpoint for the managed Kubernetes API server that you use to communicate with your cluster (using tools like kubectl). The managed endpoint uses NLB to load balance Kubernetes API servers. EKS also provisions two ENIs in different AZs to facilitate communication to your worker nodes.

You can configure whether your Kubernetes cluster\u2019s API server is reachable from the public internet (using the public endpoint) or through your VPC (using the EKS-managed ENIs) or both.

Whether users and worker nodes connect to the API server using the public endpoint or the EKS-managed ENI, there are redundant paths for connection.

"},{"location":"reliability/docs/controlplane/#recommendations","title":"Recommendations","text":""},{"location":"reliability/docs/controlplane/#monitor-control-plane-metrics","title":"Monitor Control Plane Metrics","text":"

Monitoring Kubernetes API metrics can give you insights into control plane performance and identify issues. An unhealthy control plane can compromise the availability of the workloads running inside the cluster. For example, poorly written controllers can overload the API servers, affecting your application's availability.

Kubernetes exposes control plane metrics at the /metrics endpoint.

You can view the metrics exposed using kubectl:

kubectl get --raw /metrics\n

These metrics are represented in a Prometheus text format.

You can use Prometheus to collect and store these metrics. In May 2020, CloudWatch added support for monitoring Prometheus metrics in CloudWatch Container Insights. So you can also use Amazon CloudWatch to monitor the EKS control plane. You can use Tutorial for Adding a New Prometheus Scrape Target: Prometheus KPI Server Metrics to collect metrics and create CloudWatch dashboard to monitor your cluster\u2019s control plane.

You can find Kubernetes API server metrics here. For example, apiserver_request_duration_seconds can indicate how long API requests are taking to run.

Consider monitoring these control plane metrics:

"},{"location":"reliability/docs/controlplane/#api-server","title":"API Server","text":"Metric Description apiserver_request_total Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code. apiserver_request_duration_seconds* Response latency distribution in seconds for each verb, dry run value, group, version, resource, subresource, scope, and component. apiserver_admission_controller_admission_duration_seconds Admission controller latency histogram in seconds, identified by name and broken out for each operation and API resource and type (validate or admit). apiserver_admission_webhook_rejection_count Count of admission webhook rejections. Identified by name, operation, rejection_code, type (validating or admit), error_type (calling_webhook_error, apiserver_internal_error, no_error) rest_client_request_duration_seconds Request latency in seconds. Broken down by verb and URL. rest_client_requests_total Number of HTTP requests, partitioned by status code, method, and host."},{"location":"reliability/docs/controlplane/#etcd","title":"etcd","text":"Metric Description etcd_request_duration_seconds Etcd request latency in seconds for each operation and object type. etcd_db_total_size_in_bytes or apiserver_storage_db_total_size_in_bytes (starting with EKS v1.26) or apiserver_storage_size_bytes (starting with EKS v1.28) Etcd database size.

Consider using the Kubernetes Monitoring Overview Dashboard to visualize and monitor Kubernetes API server requests and latency and etcd latency metrics.

The following Prometheus query can be used to monitor the current size of etcd. The query assumes there is job called kube-apiserver for scraping metrics from API metrics endpoint and the EKS version is below v1.26.

max(etcd_db_total_size_in_bytes{job=\"kube-apiserver\"} / (8 * 1024 * 1024 * 1024))\n

Attention

When the database size limit is exceeded, etcd emits a no space alarm and stops taking further write requests. In other words, the cluster becomes read-only, and all requests to mutate objects such as creating new pods, scaling deployments, etc., will be rejected by the cluster\u2019s API server.

"},{"location":"reliability/docs/controlplane/#cluster-authentication","title":"Cluster Authentication","text":"

EKS currently supports two types of authentication: bearer/service account tokens and IAM authentication which uses webhook token authentication. When users call the Kubernetes API, a webhook passes an authentication token included in the request to IAM. The token, a base 64 signed URL, is generated by the AWS Command Line Interface (AWS CLI).

The IAM user or role that creates the EKS Cluster automatically gets full access to the cluster. You can manage access to the EKS cluster by editing the aws-auth configmap.

If you misconfigure the aws-auth configmap and lose access to the cluster, you can still use the cluster creator\u2019s user or role to access your EKS cluster.

In the unlikely event that you cannot use the IAM service in the AWS region, you can also use the Kubernetes service account\u2019s bearer token to manage the cluster.

Create a \u201csuper-admin\u201d account that is permitted to perform all actions in the cluster:

kubectl -n kube-system create serviceaccount super-admin\n

Create a role binding that gives super-admin cluster-admin role:

kubectl create clusterrolebinding super-admin-rb --clusterrole=cluster-admin --serviceaccount=kube-system:super-admin\n

Get service account\u2019s secret:

SECRET_NAME=`kubectl -n kube-system get serviceaccount/super-admin -o jsonpath='{.secrets[0].name}'`\n

Get token associated with the secret:

TOKEN=`kubectl -n kube-system get secret $SECRET_NAME -o jsonpath='{.data.token}'| base64 --decode`\n

Add service account and token to kubeconfig:

kubectl config set-credentials super-admin --token=$TOKEN\n

Set the current-context in kubeconfig to use super-admin account:

kubectl config set-context --current --user=super-admin\n

Final kubeconfig should look like this:

apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data:<REDACTED>\n    server: https://<CLUSTER>.gr7.us-west-2.eks.amazonaws.com\n  name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>\ncontexts:\n- context:\n    cluster: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>\n    user: super-admin\n  name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>\ncurrent-context: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>\nkind: Config\npreferences: {}\nusers:\n#- name: arn:aws:eks:us-west-2:<account number>:cluster/<cluster name>\n#  user:\n#    exec:\n#      apiVersion: client.authentication.k8s.io/v1alpha1\n#      args:\n#      - --region\n#      - us-west-2\n#      - eks\n#      - get-token\n#      - --cluster-name\n#      - <<cluster name>>\n#      command: aws\n#      env: null\n- name: super-admin\n  user:\n    token: <<super-admin sa\u2019s secret>>\n
"},{"location":"reliability/docs/controlplane/#admission-webhooks","title":"Admission Webhooks","text":"

Kubernetes has two types of admission webhooks: validating admission webhooks and mutating admission webhooks. These allow a user to extend the kubernetes API and validate or mutate objects before they are accepted by the API. Poor configurations of these webhooks can distabilize the EKS control plane by blocking cluster critical operations.

In order to avoid impacting cluster critical operations either avoid setting \"catch-all\" webhooks like the following:

- name: \"pod-policy.example.com\"\n  rules:\n  - apiGroups:   [\"*\"]\n    apiVersions: [\"*\"]\n    operations:  [\"*\"]\n    resources:   [\"*\"]\n    scope: \"*\"\n

Or make sure the webhook has a fail open policy with a timeout shorter than 30 seconds to ensure that if your webhook is unavailable it will not impair cluster critical workloads.

"},{"location":"reliability/docs/controlplane/#block-pods-with-unsafe-sysctls","title":"Block Pods with unsafe sysctls","text":"

Sysctl is a Linux utility that allows users to modify kernel parameters during runtime. These kernel parameters control various aspects of the operating system's behavior, such as network, file system, virtual memory, and process management.

Kubernetes allows assigning sysctl profiles for Pods. Kubernetes categorizes systcls as safe and unsafe. Safe sysctls are namespaced in the container or Pod, and setting them doesn\u2019t impact other Pods on the node or the node itself. In contrast, unsafe sysctls are disabled by default since they can potentially disrupt other Pods or make the node unstable.

As unsafe sysctls are disabled by default, the kubelet will not create a Pod with unsafe sysctl profile. If you create such a Pod, the scheduler will repeatedly assign such Pods to nodes, while the node fails to launch it. This infinite loop ultimately strains the cluster control plane, making the cluster unstable.

Consider using OPA Gatekeeper or Kyverno to reject Pods with unsafe sysctls.

"},{"location":"reliability/docs/controlplane/#handling-cluster-upgrades","title":"Handling Cluster Upgrades","text":"

Since April 2021, Kubernetes release cycle has been changed from four releases a year (once a quarter) to three releases a year. A new minor version (like 1.21 or 1.22) is released approximately every fifteen weeks. Starting with Kubernetes 1.19, each minor version is supported for approximately twelve months after it's first released. With the advent of Kubernetes v1.28, the compatibility skew between the control plane and worker nodes has expanded from n-2 to n-3 minor versions. To learn more, see Best Practices for Cluster Upgrades.

"},{"location":"reliability/docs/controlplane/#cluster-endpoint-connectivity","title":"Cluster Endpoint Connectivity","text":"

When working with Amazon EKS (Elastic Kubernetes Service), you may encounter connection timeouts or errors during events such as Kubernetes control plane scaling or patching. These events can cause the kube-apiserver instances to be replaced, potentially resulting in different IP addresses being returned when resolving the FQDN. This document outlines best practices for Kubernetes API consumers to maintain reliable connectivity. Note: Implementing these best practices may require updates to client configurations or scripts to handle new DNS re-resolution and retry strategies effectively.

The main issue stems from DNS client-side caching and the potential for stale IP addresses of EKS endpoint - public NLB for public endpoint or X-ENI for private endpoint. When the kube-apiserver instances are replaced, the Fully Qualified Domain Name (FQDN) may resolve to new IP addresses. However, due to DNS Time to Live (TTL)settings, which are set to 60 seconds in the AWS managed Route 53 zone, clients may continue to use outdated IP addresses for a short period of time.

To mitigate these issues, Kubernetes API consumers (such as kubectl, CI/CD pipelines, and custom applications) should implement the following best practices:

  • Implement DNS re-resolution
  • Implement Retries with Backoff and Jitter. For example, see this article titled Failures Happen
  • Implement Client Timeouts. Set appropriate timeouts to prevent long-running requests from blocking your application. Be aware that some Kubernetes client libraries, particularly those generated by OpenAPI generators, may not allow setting custom timeouts easily.

    • Example 1 with kubectl:
    kubectl get pods --request-timeout 10s # default: no timeout\n
    • Example 2 with Python: Kubernetes client provides a _request_timeout parameter

By implementing these best practices, you can significantly improve the reliability and resilience of your applications when interacting with Kubernetes API. Remember to test these implementations thoroughly, especially under simulated failure conditions, to ensure they behave as expected during actual scaling or patching events.

"},{"location":"reliability/docs/controlplane/#running-large-clusters","title":"Running large clusters","text":"

EKS actively monitors the load on control plane instances and automatically scales them to ensure high performance. However, you should account for potential performance issues and limits within Kubernetes and quotas in AWS services when running large clusters.

  • Clusters with more than 1000 services may experience network latency with using kube-proxy in iptables mode according to the tests performed by the ProjectCalico team. The solution is to switch to running kube-proxy in ipvs mode.
  • You may also experience EC2 API request throttling if the CNI needs to request IP addresses for Pods or if you need to create new EC2 instances frequently. You can reduce calls EC2 API by configuring the CNI to cache IP addresses. You can use larger EC2 instance types to reduce EC2 scaling events.
"},{"location":"reliability/docs/controlplane/#additional-resources","title":"Additional Resources:","text":"
  • De-mystifying cluster networking for Amazon EKS worker nodes
  • Amazon EKS cluster endpoint access control
  • AWS re:Invent 2019: Amazon EKS under the hood (CON421-R1)
"},{"location":"reliability/docs/dataplane/","title":"EKS Data Plane","text":"

To operate high-available and resilient applications, you need a highly-available and resilient data plane. An elastic data plane ensures that Kubernetes can scale and heal your applications automatically. A resilient data plane consists of two or more worker nodes, can grow and shrink with the workload, and automatically recover from failures.

You have two choices for worker nodes with EKS: EC2 instances and Fargate. If you choose EC2 instances, you can manage the worker nodes yourself or use EKS managed node groups. You can have a cluster with a mix of managed, self-managed worker nodes, and Fargate.

EKS on Fargate offers the easiest path to a resilient data plane. Fargate runs each Pod in an isolated compute environment. Each Pod running on Fargate gets its own worker node. Fargate automatically scales the data plane as Kubernetes scales pods. You can scale both the data plane and your workload by using the horizontal pod autoscaler.

The preferred way to scale EC2 worker nodes is by using Kubernetes Cluster Autoscaler, EC2 Auto Scaling groups or community projects like Atlassian\u2019s Escalator.

"},{"location":"reliability/docs/dataplane/#recommendations","title":"Recommendations","text":""},{"location":"reliability/docs/dataplane/#use-ec2-auto-scaling-groups-to-create-worker-nodes","title":"Use EC2 Auto Scaling Groups to create worker nodes","text":"

It is a best practice to create worker nodes using EC2 Auto Scaling groups instead of creating individual EC2 instances and joining them to the cluster. Auto Scaling Groups will automatically replace any terminated or failed nodes ensuring that the cluster always has the capacity to run your workload.

"},{"location":"reliability/docs/dataplane/#use-kubernetes-cluster-autoscaler-to-scale-nodes","title":"Use Kubernetes Cluster Autoscaler to scale nodes","text":"

Cluster Autoscaler adjusts the size of the data plane when there are pods that cannot be run because the cluster has insufficient resources, and adding another worker node would help. Although Cluster Autoscaler is a reactive process, it waits until pods are in Pending state due to insufficient capacity in the cluster. When such an event occurs, it adds EC2 instances to the cluster. Whenever the cluster runs out of capacity, new replicas - or new pods - will be unavailable (in Pending state) until worker nodes are added. This delay may impact your applications' reliability if the data plane cannot scale fast enough to meet the demands of the workload. If a worker node is consistently underutilized and all of its pods can be scheduled on other worker nodes, Cluster Autoscaler terminates it.

"},{"location":"reliability/docs/dataplane/#configure-over-provisioning-with-cluster-autoscaler","title":"Configure over-provisioning with Cluster Autoscaler","text":"

Cluster Autoscaler triggers a scale-up of the data-plane when Pods in the cluster are already Pending. Hence, there may be a delay between the time your application needs more replicas, and when it, in fact, gets more replicas. An option to account for this possible delay is through adding more than required replicas, inflating the number of replicas for the application.

Another pattern that Cluster Autoscaler recommends uses pause Pods and the Priority Preemption feature. The pause Pod runs a pause container, which as the name suggests, does nothing but acts as a placeholder for compute capacity that can be used by other Pods in your cluster. Because it runs with a very low assigned priority, the pause Pod gets evicted from the node when another Pod needs to be created, and the cluster doesn\u2019t have available capacity. The Kubernetes Scheduler notices the eviction of the pause Pod and tries to reschedule it. But since the cluster is running at capacity, the pause Pod remains Pending, to which the Cluster Autoscaler reacts by adding nodes.

"},{"location":"reliability/docs/dataplane/#using-cluster-autoscaler-with-multiple-auto-scaling-groups","title":"Using Cluster Autoscaler with multiple Auto Scaling Groups","text":"

Run the Cluster Autoscaler with the --node-group-auto-discovery flag enabled. Doing so will allow the Cluster Autoscaler to find all autoscaling groups that include a particular defined tag and prevents the need to define and maintain each autoscaling group in the manifest.

"},{"location":"reliability/docs/dataplane/#using-cluster-autoscaler-with-local-storage","title":"Using Cluster Autoscaler with local storage","text":"

By default, the Cluster Autoscaler does not scale-down nodes that have pods deployed with local storage attached. Set the --skip-nodes-with-local-storage flag to false to allow Cluster Autoscaler to scale-down these nodes.

"},{"location":"reliability/docs/dataplane/#spread-worker-nodes-and-workload-across-multiple-azs","title":"Spread worker nodes and workload across multiple AZs","text":"

You can protect your workloads from failures in an individual AZ by running worker nodes and pods in multiple AZs. You can control the AZ the worker nodes are created in using the subnets you create the nodes in.

If you are using Kubernetes 1.18+, the recommended method for spreading pods across AZs is to use Topology Spread Constraints for Pods.

The deployment below spreads pods across AZs if possible, letting those pods run anyway if not:

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web-server\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: web-server\n  template:\n    metadata:\n      labels:\n        app: web-server\n    spec:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          whenUnsatisfiable: ScheduleAnyway\n          topologyKey: topology.kubernetes.io/zone\n          labelSelector:\n            matchLabels:\n              app: web-server\n      containers:\n      - name: web-app\n        image: nginx\n        resources:\n          requests:\n            cpu: 1\n

Note

kube-scheduler is only aware of topology domains via nodes that exist with those labels. If the above deployment is deployed to a cluster with nodes only in a single zone, all of the pods will schedule on those nodes as kube-scheduler isn't aware of the other zones. For this topology spread to work as expected with the scheduler, nodes must already exist in all zones. This issue will be resolved in Kubernetes 1.24 with the addition of the MinDomainsInPodToplogySpread feature gate which allows specifying a minDomains property to inform the scheduler of the number of eligible domains.

Warning

Setting whenUnsatisfiable to DoNotSchedule will cause pods to be unschedulable if the topology spread constraint can't be fulfilled. It should only be set if its preferable for pods to not run instead of violating the topology spread constraint.

On older versions of Kubernetes, you can use pod anti-affinity rules to schedule pods across multiple AZs. The manifest below informs Kubernetes scheduler to prefer scheduling pods in distinct AZs.

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web-server\n  labels:\n    app: web-server\nspec:\n  replicas: 4\n  selector:\n    matchLabels:\n      app: web-server\n  template:\n    metadata:\n      labels:\n        app: web-server\n    spec:\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: app\n                  operator: In\n                  values:\n                  - web-server\n              topologyKey: failure-domain.beta.kubernetes.io/zone\n            weight: 100\n      containers:\n      - name: web-app\n        image: nginx\n

Warning

Do not require that pods be scheduled across distinct AZs otherwise, the number of pods in a deployment will never exceed the number of AZs.

"},{"location":"reliability/docs/dataplane/#ensure-capacity-in-each-az-when-using-ebs-volumes","title":"Ensure capacity in each AZ when using EBS volumes","text":"

If you use Amazon EBS to provide Persistent Volumes, then you need to ensure that the pods and associated EBS volume are located in the same AZ. At the time of writing, EBS volumes are only available within a single AZ. A Pod cannot access EBS-backed persistent volumes located in a different AZ. Kubernetes scheduler knows which AZ a worker node is located in. Kubernetes will always schedule a Pod that requires an EBS volume in the same AZ as the volume. However, if there are no worker nodes available in the AZ where the volume is located, then the Pod cannot be scheduled.

Create Auto Scaling Group for each AZ with enough capacity to ensure that the cluster always has capacity to schedule pods in the same AZ as the EBS volumes they need. In addition, you should enable the --balance-similar-node-groups feature in Cluster Autoscaler.

If you are running an application that uses EBS volume but has no requirements to be highly available, then you can restrict the deployment of the application to a single AZ. In EKS, worker nodes are automatically added failure-domain.beta.kubernetes.io/zone label, which contains the name of the AZ. You can see the labels attached to your nodes by running kubectl get nodes --show-labels. More information about built-in node labels is available here. You can use node selectors to schedule a pod in a particular AZ.

In the example below, the pod will only be scheduled in us-west-2c AZ:

apiVersion: v1\nkind: Pod\nmetadata:\n  name: single-az-pod\nspec:\n  affinity:\n    nodeAffinity:\n      requiredDuringSchedulingIgnoredDuringExecution:\n        nodeSelectorTerms:\n        - matchExpressions:\n          - key: failure-domain.beta.kubernetes.io/zone\n            operator: In\n            values:\n            - us-west-2c\n  containers:\n  - name: single-az-container\n    image: kubernetes/pause\n

Persistent volumes (backed by EBS) are also automatically labeled with the name of AZ; you can see which AZ your persistent volume belongs to by running kubectl get pv -L topology.ebs.csi.aws.com/zone. When a pod is created and claims a volume, Kubernetes will schedule the Pod on a node in the same AZ as the volume.

Consider this scenario; you have an EKS cluster with one node group. This node group has three worker nodes spread across three AZs. You have an application that uses an EBS-backed Persistent Volume. When you create this application and the corresponding volume, its Pod gets created in the first of the three AZs. Then, the worker node that runs this Pod becomes unhealthy and subsequently unavailable for use. Cluster Autoscaler will replace the unhealthy node with a new worker node; however, because the autoscaling group spans across three AZs, the new worker node may get launched in the second or the third AZ, but not in the first AZ as the situation demands. As the AZ-constrained EBS volume only exists in the first AZ, but there are no worker nodes available in that AZ, the Pod cannot be scheduled. Therefore, you should create one node group in each AZ, so there is always enough capacity available to run pods that cannot be scheduled in other AZs.

Alternatively, EFS can simplify cluster autoscaling when running applications that need persistent storage. Clients can access EFS file systems concurrently from all the AZs in the region. Even if a Pod using EFS-backed Persistent Volume gets terminated and gets scheduled in different AZ, it will be able to mount the volume.

"},{"location":"reliability/docs/dataplane/#run-node-problem-detector","title":"Run node-problem-detector","text":"

Failures in worker nodes can impact the availability of your applications. node-problem-detector is a Kubernetes add-on that you can install in your cluster to detect worker node issues. You can use a npd\u2019s remedy system to drain and terminate the node automatically.

"},{"location":"reliability/docs/dataplane/#reserving-resources-for-system-and-kubernetes-daemons","title":"Reserving resources for system and Kubernetes daemons","text":"

You can improve worker nodes' stability by reserving compute capacity for the operating system and Kubernetes daemons. Pods - especially ones without limits declared - can saturate system resources putting nodes in a situation where operating system processes and Kubernetes daemons (kubelet, container runtime, etc.) compete with pods for system resources. You can use kubelet flags --system-reserved and --kube-reserved to reserve resources for system process (udev, sshd, etc.) and Kubernetes daemons respectively.

If you use the EKS-optimized Linux AMI, the CPU, memory, and storage are reserved for the system and Kubernetes daemons by default. When worker nodes based on this AMI launch, EC2 user-data is configured to trigger the bootstrap.sh script. This script calculates CPU and memory reservations based on the number of CPU cores and total memory available on the EC2 instance. The calculated values are written to the KubeletConfiguration file located at /etc/kubernetes/kubelet/kubelet-config.json.

You may need to increase the system resource reservation if you run custom daemons on the node and the amount of CPU and memory reserved by default is insufficient.

eksctl offers the easiest way to customize resource reservation for system and Kubernetes daemons.

"},{"location":"reliability/docs/dataplane/#implement-qos","title":"Implement QoS","text":"

For critical applications, consider defining requests=limits for the container in the Pod. This will ensure that the container will not be killed if another Pod requests resources.

It is a best practice to implement CPU and memory limits for all containers as it prevents a container inadvertently consuming system resources impacting the availability of other co-located processes.

"},{"location":"reliability/docs/dataplane/#configure-and-size-resource-requestslimits-for-all-workloads","title":"Configure and Size Resource Requests/Limits for all Workloads","text":"

Some general guidance can be applied to sizing resource requests and limits for workloads:

  • Do not specify resource limits on CPU. In the absence of limits, the request acts as a weight on how much relative CPU time containers get. This allows your workloads to use the full CPU without an artificial limit or starvation.

  • For non-CPU resources, configuring requests=limits provides the most predictable behavior. If requests!=limits, the container also has its QOS reduced from Guaranteed to Burstable making it more likely to be evicted in the event of node pressure.

  • For non-CPU resources, do not specify a limit that is much larger than the request. The larger limits are configured relative to requests, the more likely nodes will be overcommitted leading to high chances of workload interruption.

  • Correctly sized requests are particularly important when using a node auto-scaling solution like Karpenter or Cluster AutoScaler. These tools look at your workload requests to determine the number and size of nodes to be provisioned. If your requests are too small with larger limits, you may find your workloads evicted or OOM killed if they have been tightly packed on a node.

Determining resource requests can be difficult, but tools like the Vertical Pod Autoscaler can help you 'right-size' the requests by observing container resource usage at runtime. Other tools that may be useful for determining request sizes include:

  • Goldilocks
  • Parca
  • Prodfiler
  • rsg
"},{"location":"reliability/docs/dataplane/#configure-resource-quotas-for-namespaces","title":"Configure resource quotas for namespaces","text":"

Namespaces are intended for use in environments with many users spread across multiple teams, or projects. They provide a scope for names and are a way to divide cluster resources between multiple teams, projects, workloads. You can limit the aggregate resource consumption in a namespace. The ResourceQuota object can limit the quantity of objects that can be created in a namespace by type, as well as the total amount of compute resources that may be consumed by resources in that project. You can limit the total sum of storage and/or compute (CPU and memory) resources that can be requested in a given namespace.

If resource quota is enabled for a namespace for compute resources like CPU and memory, users must specify requests or limits for each container in that namespace.

Consider configuring quotas for each namespace. Consider using LimitRanges to automatically apply preconfigured limits to containers within a namespaces.

"},{"location":"reliability/docs/dataplane/#limit-container-resource-usage-within-a-namespace","title":"Limit container resource usage within a namespace","text":"

Resource Quotas help limit the amount of resources a namespace can use. The LimitRange object can help you implement minimum and maximum resources a container can request. Using LimitRange you can set a default request and limits for containers, which is helpful if setting compute resource limits is not a standard practice in your organization. As the name suggests, LimitRange can enforce minimum and maximum compute resources usage per Pod or Container in a namespace. As well as, enforce minimum and maximum storage request per PersistentVolumeClaim in a namespace.

Consider using LimitRange in conjunction with ResourceQuota to enforce limits at a container as well as namespace level. Setting these limits will ensure that a container or a namespace does not impinge on resources used by other tenants in the cluster.

"},{"location":"reliability/docs/dataplane/#coredns","title":"CoreDNS","text":"

CoreDNS fulfills name resolution and service discovery functions in Kubernetes. It is installed by default on EKS clusters. For interoperability, the Kubernetes Service for CoreDNS is still named kube-dns. CoreDNS Pods run as part of a Deployment in kube-system namespace, and in EKS, by default, it runs two replicas with declared requests and limits. DNS queries are sent to the kube-dns Service that runs in the kube-system Namespace.

"},{"location":"reliability/docs/dataplane/#recommendations_1","title":"Recommendations","text":""},{"location":"reliability/docs/dataplane/#monitor-coredns-metrics","title":"Monitor CoreDNS metrics","text":"

CoreDNS has built in support for Prometheus. You should especially consider monitoring CoreDNS latency (coredns_dns_request_duration_seconds_sum, before 1.7.0 version the metric was called core_dns_response_rcode_count_total), errors (coredns_dns_responses_total, NXDOMAIN, SERVFAIL, FormErr) and CoreDNS Pod\u2019s memory consumption.

For troubleshooting purposes, you can use kubectl to view CoreDNS logs:

for p in $(kubectl get pods -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[*].metadata.name}'); do kubectl logs $p -n kube-system; done\n
"},{"location":"reliability/docs/dataplane/#use-nodelocal-dnscache","title":"Use NodeLocal DNSCache","text":"

You can improve the Cluster DNS performance by running NodeLocal DNSCache. This feature runs a DNS caching agent on cluster nodes as a DaemonSet. All the pods use the DNS caching agent running on the node for name resolution instead of using kube-dns Service.

"},{"location":"reliability/docs/dataplane/#configure-cluster-proportional-scaler-for-coredns","title":"Configure cluster-proportional-scaler for CoreDNS","text":"

Another method of improving Cluster DNS performance is by automatically horizontally scaling the CoreDNS Deployment based on the number of nodes and CPU cores in the cluster. Horizontal cluster-proportional-autoscaler is a container that resizes the number of replicas of a Deployment based on the size of the schedulable data-plane.

Nodes and the aggregate of CPU cores in the nodes are the two metrics with which you can scale CoreDNS. You can use both metrics simultaneously. If you use larger nodes, CoreDNS scaling is based on the number of CPU cores. Whereas, if you use smaller nodes, the number of CoreDNS replicas depends on the CPU cores in your data-plane. Proportional autoscaler configuration looks like this:

linear: '{\"coresPerReplica\":256,\"min\":1,\"nodesPerReplica\":16}'\n
"},{"location":"reliability/docs/dataplane/#choosing-an-ami-with-node-group","title":"Choosing an AMI with Node Group","text":"

EKS provides optimized EC2 AMIs that are used by customers to create both self-managed and managed nodegroups. These AMIs are published in every region for every supported Kubernetes version. EKS marks these AMIs as deprecated when any CVEs or bugs are discovered. Hence, the recommendation is not to consume deprecated AMIs while choosing an AMI for the node group.

Deprecated AMIs can be filtered using Ec2 describe-images api using below command:

aws ec2 describe-images --image-id ami-0d551c4f633e7679c --no-include-deprecated\n

You can also recognize a deprecated AMI by verifying if the describe-image output contains a DeprecationTime as a field. For ex:

aws ec2 describe-images --image-id ami-xxx --no-include-deprecated\n{\n    \"Images\": [\n        {\n            \"Architecture\": \"x86_64\",\n            \"CreationDate\": \"2022-07-13T15:54:06.000Z\",\n            \"ImageId\": \"ami-xxx\",\n            \"ImageLocation\": \"123456789012/eks_xxx\",\n            \"ImageType\": \"machine\",\n            \"Public\": false,\n            \"OwnerId\": \"123456789012\",\n            \"PlatformDetails\": \"Linux/UNIX\",\n            \"UsageOperation\": \"RunInstances\",\n            \"State\": \"available\",\n            \"BlockDeviceMappings\": [\n                {\n                    \"DeviceName\": \"/dev/xvda\",\n                    \"Ebs\": {\n                        \"DeleteOnTermination\": true,\n                        \"SnapshotId\": \"snap-0993a2fc4bbf4f7f4\",\n                        \"VolumeSize\": 20,\n                        \"VolumeType\": \"gp2\",\n                        \"Encrypted\": false\n                    }\n                }\n            ],\n            \"Description\": \"EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.19.15, docker: 20.10.13-2.amzn2, containerd: 1.4.13-3.amzn2)\",\n            \"EnaSupport\": true,\n            \"Hypervisor\": \"xen\",\n            \"Name\": \"aws_eks_optimized_xxx\",\n            \"RootDeviceName\": \"/dev/xvda\",\n            \"RootDeviceType\": \"ebs\",\n            \"SriovNetSupport\": \"simple\",\n            \"VirtualizationType\": \"hvm\",\n            \"DeprecationTime\": \"2023-02-09T19:41:00.000Z\"\n        }\n    ]\n}\n
"},{"location":"scalability/docs/","title":"EKS Scalability best practices","text":"

This guide provides advice for scaling EKS clusters. The goal of scaling an EKS cluster is to maximize the amount of work a single cluster can perform. Using a single, large EKS cluster can reduce operational load compared to using multiple clusters, but it has trade-offs for things like multi-region deployments, tenant isolation, and cluster upgrades. In this document we will focus on how to achieve maximum scalability with a single cluster.

"},{"location":"scalability/docs/#how-to-use-this-guide","title":"How to use this guide","text":"

This guide is meant for developers and administrators responsible for creating and managing EKS clusters in AWS. It focuses on some generic Kubernetes scaling practices, but it does not have specifics for self-managed Kubernetes clusters or clusters that run outside of an AWS region with EKS Anywhere.

Each topic has a brief overview, followed by recommendations and best practices for operating EKS clusters at scale. Topics do not need to be read in a particular order and recommendations should not be applied without testing and verifying they work in your clusters.

"},{"location":"scalability/docs/#understanding-scaling-dimensions","title":"Understanding scaling dimensions","text":"

Scalability is different from performance and reliability, and all three should be considered when planning your cluster and workload needs. As clusters scale, they need to be monitored, but this guide will not cover monitoring best practices. EKS can scale to large sizes, but you will need to plan how you are going to scale a cluster beyond 300 nodes or 5000 pods. These are not absolute numbers, but they come from collaborating this guide with multiple users, engineers, and support professionals.

Scaling in Kubernetes is multi-dimensional and there are no specific settings or recommendations that work in every situation. The main areas areas where we can provide guidance for scaling include:

  • Kubernetes Control Plane
  • Kubernetes Data Plane
  • Cluster Services
  • Workloads

Kubernetes Control Plane in an EKS cluster includes all of the services AWS runs and scales for you automatically (e.g. Kubernetes API server). Scaling the Control Plane is AWS's responsibility, but using the Control Plane responsibly is your responsibility.

Kubernetes Data Plane scaling deals with AWS resources that are required for your cluster and workloads, but they are outside of the EKS Control Plane. Resources including EC2 instances, kubelet, and storage all need to be scaled as your cluster scales.

Cluster services are Kubernetes controllers and applications that run inside the cluster and provide functionality for your cluster and workloads. These can be EKS Add-ons and also other services or Helm charts you install for compliance and integrations. These services are often depended on by workloads and as your workloads scale your cluster services will need to scale with them.

Workloads are the reason you have a cluster and should scale horizontally with the cluster. There are integrations and settings that workloads have in Kubernetes that can help the cluster scale. There are also architectural considerations with Kubernetes abstractions such as namespaces and services.

"},{"location":"scalability/docs/#extra-large-scaling","title":"Extra large scaling","text":"

If you are scaling a single cluster beyond 1000 nodes or 50,000 pods we would love to talk to you. We recommend reaching out to your support team or technical account manager to get in touch with specialists who can help you plan and scale beyond the information provided in this guide.

"},{"location":"scalability/docs/cluster-services/","title":"Cluster Services","text":"

Cluster services run inside an EKS cluster, but they are not user workloads. If you have a Linux server you often need to run services like NTP, syslog, and a container runtime to support your workloads. Cluster services are similar, supporting services that help you automate and operate your cluster. In Kubernetes these are usually run in the kube-system namespace and some are run as DaemonSets.

Cluster services are expected to have a high up-time and are often critical during outages and for troubleshooting. If a core cluster service is not available you may lose access to data that can help recover or prevent an outage (e.g. high disk utilization). They should run on dedicated compute instances such as a separate node group or AWS Fargate. This will ensure that the cluster services are not impacted on shared instances by workloads that may be scaling up or using more resources.

"},{"location":"scalability/docs/cluster-services/#scale-coredns","title":"Scale CoreDNS","text":"

Scaling CoreDNS has two primary mechanisms. Reducing the number of calls to the CoreDNS service and increasing the number of replicas.

"},{"location":"scalability/docs/cluster-services/#reduce-external-queries-by-lowering-ndots","title":"Reduce external queries by lowering ndots","text":"

The ndots setting specifies how many periods (a.k.a. \"dots\") in a domain name are considered enough to avoid querying DNS. If your application has an ndots setting of 5 (default) and you request resources from an external domain such as api.example.com (2 dots) then CoreDNS will be queried for each search domain defined in /etc/resolv.conf for a more specific domain. By default the following domains will be searched before making an external request.

api.example.<namespace>.svc.cluster.local\napi.example.svc.cluster.local\napi.example.cluster.local\napi.example.<region>.compute.internal\n

The namespace and region values will be replaced with your workloads namespace and your compute region. You may have additional search domains based on your cluster settings.

You can reduce the number of requests to CoreDNS by lowering the ndots option of your workload or fully qualifying your domain requests by including a trailing . (e.g. api.example.com. ). If your workload connects to external services via DNS we recommend setting ndots to 2 so workloads do not make unnecessary, cluster DNS queries inside the cluster. You can set a different DNS server and search domain if the workload doesn\u2019t require access to services inside the cluster.

spec:\n  dnsPolicy: \"None\"\n  dnsConfig:\n    options:\n      - name: ndots\n        value: \"2\"\n      - name: edns0\n

If you lower ndots to a value that is too low or the domains you are connecting to do not include enough specificity (including trailing .) then it is possible DNS lookups will fail. Make sure you test how this setting will impact your workloads.

"},{"location":"scalability/docs/cluster-services/#scale-coredns-horizontally","title":"Scale CoreDNS Horizontally","text":"

CoreDNS instances can scale by adding additional replicas to the deployment. It's recommended you use NodeLocal DNS or the cluster proportional autoscaler to scale CoreDNS.

NodeLocal DNS will require run one instance per node\u2014as a DaemonSet\u2014which requires more compute resources in the cluster, but it will avoid failed DNS requests and decrease the response time for DNS queries in the cluster. The cluster proportional autoscaler will scale CoreDNS based on the number of nodes or cores in the cluster. This isn\u2019t a direct correlation to request queries, but can be useful depending on your workloads and cluster size. The default proportional scale is to add an additional replica for every 256 cores or 16 nodes in the cluster\u2014whichever happens first.

"},{"location":"scalability/docs/cluster-services/#scale-kubernetes-metrics-server-vertically","title":"Scale Kubernetes Metrics Server Vertically","text":"

The Kubernetes Metrics Server supports horizontal and vertical scaling. By horizontally scaling the Metrics Server it will be highly available, but it will not scale horizontally to handle more cluster metrics. You will need to vertically scale the Metrics Server based on their recommendations as nodes and collected metrics are added to the cluster.

The Metrics Server keeps the data it collects, aggregates, and serves in memory. As a cluster grows, the amount of data the Metrics Server stores increases. In large clusters the Metrics Server will require more compute resources than the memory and CPU reservation specified in the default installation. You can use the Vertical Pod Autoscaler (VPA) or Addon Resizer to scale the Metrics Server. The Addon Resizer scales vertically in proportion to worker nodes and VPA scales based on CPU and memory usage.

"},{"location":"scalability/docs/cluster-services/#coredns-lameduck-duration","title":"CoreDNS lameduck duration","text":"

Pods use the kube-dns Service for name resolution. Kubernetes uses destination NAT (DNAT) to redirect kube-dns traffic from nodes to CoreDNS backend pods. As you scale the CoreDNS Deployment, kube-proxy updates iptables rules and chains on nodes to redirect DNS traffic to CoreDNS pods. Propagating new endpoints when you scale up and deleting rules when you scale down CoreDNS can take between 1 to 10 seconds depending on the size of the cluster.

This propagation delay can cause DNS lookup failures when a CoreDNS pod gets terminated yet the node\u2019s iptables rules haven\u2019t been updated. In this scenario, the node may continue to send DNS queries to a terminated CoreDNS Pod.

You can reduce DNS lookup failures by setting a lameduck duration in your CoreDNS pods. While in lameduck mode, CoreDNS will continue to respond to in-flight requests. Setting a lameduck duration will delay the CoreDNS shutdown process, allowing nodes the time they need to update their iptables rules and chains.

We recommend setting CoreDNS lameduck duration to 30 seconds.

"},{"location":"scalability/docs/cluster-services/#coredns-readiness-probe","title":"CoreDNS readiness probe","text":"

We recommend using /ready instead of /health for CoreDNS's readiness probe.

In alignment with the earlier recommendation to set the lameduck duration to 30 seconds, providing ample time for the node's iptables rules to be updated before pod termination, employing /ready instead of /health for the CoreDNS readiness probe ensures that the CoreDNS pod is fully prepared at startup to promptly respond to DNS requests.

readinessProbe:\n  httpGet:\n    path: /ready\n    port: 8181\n    scheme: HTTP\n

For more information about the CoreDNS Ready plugin please refer to https://coredns.io/plugins/ready/

"},{"location":"scalability/docs/cluster-services/#logging-and-monitoring-agents","title":"Logging and monitoring agents","text":"

Logging and monitoring agents can add significant load to your cluster control plane because the agents query the API server to enrich logs and metrics with workload metadata. The agent on a node only has access to the local node resources to see things like container and process name. Querying the API server it can add more details such as Kubernetes deployment name and labels. This can be extremely helpful for troubleshooting but detrimental to scaling.

Because there are so many different options for logging and monitoring we cannot show examples for every provider. With fluentbit we recommend enabling Use_Kubelet to fetch metadata from the local kubelet instead of the Kubernetes API Server and set Kube_Meta_Cache_TTL to a number that reduces repeated calls when data can be cached (e.g. 60).

Scaling monitoring and logging has two general options:

  • Disable integrations
  • Sampling and filtering

Disabling integrations is often not an option because you lose log metadata. This eliminates the API scaling problem, but it will introduce other issues by not having the required metadata when needed.

Sampling and filtering reduces the number of metrics and logs that are collected. This will lower the amount of requests to the Kubernetes API, and it will reduce the amount of storage needed for the metrics and logs that are collected. Reducing the storage costs will lower the cost for the overall system.

The ability to configure sampling depends on the agent software and can be implemented at different points of ingestion. It\u2019s important to add sampling as close to the agent as possible because that is likely where the API server calls happen. Contact your provider to find out more about sampling support.

If you are using CloudWatch and CloudWatch Logs you can add agent filtering using patterns described in the documentation.

To avoid losing logs and metrics you should send your data to a system that can buffer data in case of an outage on the receiving endpoint. With fluentbit you can use Amazon Kinesis Data Firehose to temporarily keep data which can reduce the chance of overloading your final data storage location.

"},{"location":"scalability/docs/control-plane/","title":"Kubernetes Control Plane","text":"

The Kubernetes control plane consists of the Kubernetes API Server, Kubernetes Controller Manager, Scheduler and other components that are required for Kubernetes to function. Scalability limits of these components are different depending on what you\u2019re running in the cluster, but the areas with the biggest impact to scaling include the Kubernetes version, utilization, and individual Node scaling.

"},{"location":"scalability/docs/control-plane/#use-eks-124-or-above","title":"Use EKS 1.24 or above","text":"

EKS 1.24 introduced a number of changes and switches the container runtime to containerd instead of docker. Containerd helps clusters scale by increasing individual node performance by limiting container runtime features to closely align with Kubernetes\u2019 needs. Containerd is available in every supported version of EKS and if you would like to switch to containerd in versions prior to 1.24 please use the --container-runtime bootstrap flag.

"},{"location":"scalability/docs/control-plane/#limit-workload-and-node-bursting","title":"Limit workload and node bursting","text":"

Attention

To avoid reaching API limits on the control plane you should limit scaling spikes that increase cluster size by double digit percentages at a time (e.g. 1000 nodes to 1100 nodes or 4000 to 4500 pods at once).

The EKS control plane will automatically scale as your cluster grows, but there are limits on how fast it will scale. When you first create an EKS cluster the Control Plane will not immediately be able to scale to hundreds of nodes or thousands of pods. To read more about how EKS has made scaling improvements see this blog post.

Scaling large applications requires infrastructure to adapt to become fully ready (e.g. warming load balancers). To control the speed of scaling make sure you are scaling based on the right metrics for your application. CPU and memory scaling may not accurately predict your application constraints and using custom metrics (e.g. requests per second) in Kubernetes Horizontal Pod Autoscaler (HPA) may be a better scaling option.

To use a custom metric see the examples in the Kubernetes documentation. If you have more advanced scaling needs or need to scale based on external sources (e.g. AWS SQS queue) then use KEDA for event based workload scaling.

"},{"location":"scalability/docs/control-plane/#scale-nodes-and-pods-down-safely","title":"Scale nodes and pods down safely","text":""},{"location":"scalability/docs/control-plane/#replace-long-running-instances","title":"Replace long running instances","text":"

Replacing nodes regularly keeps your cluster healthy by avoiding configuration drift and issues that only happen after extended uptime (e.g. slow memory leaks). Automated replacement will give you good process and practices for node upgrades and security patching. If every node in your cluster is replaced regularly then there is less toil required to maintain separate processes for ongoing maintenance.

Use Karpenter\u2019s time to live (TTL) settings to replace instances after they\u2019ve been running for a specified amount of time. Self managed node groups can use the max-instance-lifetime setting to cycle nodes automatically. Managed node groups do not currently have this feature but you can track the request here on GitHub.

"},{"location":"scalability/docs/control-plane/#remove-underutilized-nodes","title":"Remove underutilized nodes","text":"

You can remove nodes when they have no running workloads using the scale down threshold in the Kubernetes Cluster Autoscaler with the --scale-down-utilization-threshold or in Karpenter you can use the ttlSecondsAfterEmpty provisioner setting.

"},{"location":"scalability/docs/control-plane/#use-pod-disruption-budgets-and-safe-node-shutdown","title":"Use pod disruption budgets and safe node shutdown","text":"

Removing pods and nodes from a Kubernetes cluster requires controllers to make updates to multiple resources (e.g. EndpointSlices). Doing this frequently or too quickly can cause API server throttling and application outages as changes propogate to controllers. Pod Disruption Budgets are a best practice to slow down churn to protect workload availability as nodes are removed or rescheduled in a cluster.

"},{"location":"scalability/docs/control-plane/#use-client-side-cache-when-running-kubectl","title":"Use Client-Side Cache when running Kubectl","text":"

Using the kubectl command inefficiently can add additional load to the Kubernetes API Server. You should avoid running scripts or automation that uses kubectl repeatedly (e.g. in a for loop) or running commands without a local cache.

kubectl has a client-side cache that caches discovery information from the cluster to reduce the amount of API calls required. The cache is enabled by default and is refreshed every 10 minutes.

If you run kubectl from a container or without a client-side cache you may run into API throttling issues. It is recommended to retain your cluster cache by mounting the --cache-dir to avoid making uncessesary API calls.

"},{"location":"scalability/docs/control-plane/#disable-kubectl-compression","title":"Disable kubectl Compression","text":"

Disabling kubectl compression in your kubeconfig file can reduce API and client CPU usage. By default the server will compress data sent to the client to optimize network bandwidth. This adds CPU load on the client and server for every request and disabling compression can reduce the overhead and latency if you have adequate bandwidth. To disable compression you can use the --disable-compression=true flag or set disable-compression: true in your kubeconfig file.

apiVersion: v1\nclusters:\n- cluster:\n    server: serverURL\n    disable-compression: true\n  name: cluster\n
"},{"location":"scalability/docs/control-plane/#shard-cluster-autoscaler","title":"Shard Cluster Autoscaler","text":"

The Kubernetes Cluster Autoscaler has been tested to scale up to 1000 nodes. On a large cluster with more than 1000 nodes, it is recommended to run multiple instances of the Cluster Autoscaler in shard mode. Each Cluster Autoscaler instance is configured to scale a set of node groups. The following example shows 2 cluster autoscaling configurations that are configured to each scale 4 node groups.

ClusterAutoscaler-1

autoscalingGroups:\n- name: eks-core-node-grp-20220823190924690000000011-80c1660e-030d-476d-cb0d-d04d585a8fcb\n  maxSize: 50\n  minSize: 2\n- name: eks-data_m1-20220824130553925600000011-5ec167fa-ca93-8ca4-53a5-003e1ed8d306\n  maxSize: 450\n  minSize: 2\n- name: eks-data_m2-20220824130733258600000015-aac167fb-8bf7-429d-d032-e195af4e25f5\n  maxSize: 450\n  minSize: 2\n- name: eks-data_m3-20220824130553914900000003-18c167fa-ca7f-23c9-0fea-f9edefbda002\n  maxSize: 450\n  minSize: 2\n

ClusterAutoscaler-2

autoscalingGroups:\n- name: eks-data_m4-2022082413055392550000000f-5ec167fa-ca86-6b83-ae9d-1e07ade3e7c4\n  maxSize: 450\n  minSize: 2\n- name: eks-data_m5-20220824130744542100000017-02c167fb-a1f7-3d9e-a583-43b4975c050c\n  maxSize: 450\n  minSize: 2\n- name: eks-data_m6-2022082413055392430000000d-9cc167fa-ca94-132a-04ad-e43166cef41f\n  maxSize: 450\n  minSize: 2\n- name: eks-data_m7-20220824130553921000000009-96c167fa-ca91-d767-0427-91c879ddf5af\n  maxSize: 450\n  minSize: 2\n
"},{"location":"scalability/docs/control-plane/#api-priority-and-fairness","title":"API Priority and Fairness","text":""},{"location":"scalability/docs/control-plane/#overview","title":"Overview","text":"

To protect itself from being overloaded during periods of increased requests, the API Server limits the number of inflight requests it can have outstanding at a given time. Once this limit is exceeded, the API Server will start rejecting requests and return a 429 HTTP response code for \"Too Many Requests\" back to clients. The server dropping requests and having clients try again later is preferable to having no server-side limits on the number of requests and overloading the control plane, which could result in degraded performance or unavailability.

The mechanism used by Kubernetes to configure how these inflights requests are divided among different request types is called API Priority and Fairness. The API Server configures the total number of inflight requests it can accept by summing together the values specified by the --max-requests-inflight and --max-mutating-requests-inflight flags. EKS uses the default values of 400 and 200 requests for these flags, allowing a total of 600 requests to be dispatched at a given time. However, as it scales the control-plane to larger sizes in response to increased utilization and workload churn, it correspondingly increases the inflight request quota all the way till 2000 (subject to change). APF specifies how these inflight request quota is further sub-divided among different request types. Note that EKS control planes are highly available with at least 2 API Servers registered to each cluster. This means the total number of inflight requests your cluster can handle is twice (or higher if horizontally scaled out further) the inflight quota set per kube-apiserver. This amounts to several thousands of requests/second on the largest EKS clusters.

Two kinds of Kubernetes objects, called PriorityLevelConfigurations and FlowSchemas, configure how the total number of requests is divided between different request types. These objects are maintained by the API Server automatically and EKS uses the default configuration of these objects for the given Kubernetes minor version. PriorityLevelConfigurations represent a fraction of the total number of allowed requests. For example, the workload-high PriorityLevelConfiguration is allocated 98 out of the total of 600 requests. The sum of requests allocated to all PriorityLevelConfigurations will equal 600 (or slightly above 600 because the API Server will round up if a given level is granted a fraction of a request). To check the PriorityLevelConfigurations in your cluster and the number of requests allocated to each, you can run the following command. These are the defaults on EKS 1.24:

$ kubectl get --raw /metrics | grep apiserver_flowcontrol_request_concurrency_limit\napiserver_flowcontrol_request_concurrency_limit{priority_level=\"catch-all\"} 13\napiserver_flowcontrol_request_concurrency_limit{priority_level=\"global-default\"} 49\napiserver_flowcontrol_request_concurrency_limit{priority_level=\"leader-election\"} 25\napiserver_flowcontrol_request_concurrency_limit{priority_level=\"node-high\"} 98\napiserver_flowcontrol_request_concurrency_limit{priority_level=\"system\"} 74\napiserver_flowcontrol_request_concurrency_limit{priority_level=\"workload-high\"} 98\napiserver_flowcontrol_request_concurrency_limit{priority_level=\"workload-low\"} 245\n

The second type of object are FlowSchemas. API Server requests with a given set of properties are classified under the same FlowSchema. These properties include either the authenticated user or attributes of the request, such as the API group, namespace, or resource. A FlowSchema also specifies which PriorityLevelConfiguration this type of request should map to. The two objects together say, \"I want this type of request to count towards this share of inflight requests.\" When a request hits the API Server, it will check each of its FlowSchemas until it finds one that matches all the required properties. If multiple FlowSchemas match a request, the API Server will choose the FlowSchema with the smallest matching precedence which is specified as a property in the object.

The mapping of FlowSchemas to PriorityLevelConfigurations can be viewed using this command:

$ kubectl get flowschemas\nNAME                           PRIORITYLEVEL     MATCHINGPRECEDENCE   DISTINGUISHERMETHOD   AGE     MISSINGPL\nexempt                         exempt            1                    <none>                7h19m   False\neks-exempt                     exempt            2                    <none>                7h19m   False\nprobes                         exempt            2                    <none>                7h19m   False\nsystem-leader-election         leader-election   100                  ByUser                7h19m   False\nendpoint-controller            workload-high     150                  ByUser                7h19m   False\nworkload-leader-election       leader-election   200                  ByUser                7h19m   False\nsystem-node-high               node-high         400                  ByUser                7h19m   False\nsystem-nodes                   system            500                  ByUser                7h19m   False\nkube-controller-manager        workload-high     800                  ByNamespace           7h19m   False\nkube-scheduler                 workload-high     800                  ByNamespace           7h19m   False\nkube-system-service-accounts   workload-high     900                  ByNamespace           7h19m   False\neks-workload-high              workload-high     1000                 ByUser                7h14m   False\nservice-accounts               workload-low      9000                 ByUser                7h19m   False\nglobal-default                 global-default    9900                 ByUser                7h19m   False\ncatch-all                      catch-all         10000                ByUser                7h19m   False\n

PriorityLevelConfigurations can have a type of Queue, Reject, or Exempt. For types Queue and Reject, a limit is enforced on the maximum number of inflight requests for that priority level, however, the behavior differs when that limit is reached. For example, the workload-high PriorityLevelConfiguration uses type Queue and has 98 requests available for use by the controller-manager, endpoint-controller, scheduler,eks related controllers and from pods running in the kube-system namespace. Since type Queue is used, the API Server will attempt to keep requests in memory and hope that the number of inflight requests drops below 98 before these requests time out. If a given request times out in the queue or if too many requests are already queued, the API Server has no choice but to drop the request and return the client a 429. Note that queuing may prevent a request from receiving a 429, but it comes with the tradeoff of increased end-to-end latency on the request.

Now consider the catch-all FlowSchema that maps to the catch-all PriorityLevelConfiguration with type Reject. If clients reach the limit of 13 inflight requests, the API Server will not exercise queuing and will drop the requests instantly with a 429 response code. Finally, requests mapping to a PriorityLevelConfiguration with type Exempt will never receive a 429 and always be dispatched immediately. This is used for high-priority requests such as healthz requests or requests coming from the system:masters group.

"},{"location":"scalability/docs/control-plane/#monitoring-apf-and-dropped-requests","title":"Monitoring APF and Dropped Requests","text":"

To confirm if any requests are being dropped due to APF, the API Server metrics for apiserver_flowcontrol_rejected_requests_total can be monitored to check the impacted FlowSchemas and PriorityLevelConfigurations. For example, this metric shows that 100 requests from the service-accounts FlowSchema were dropped due to requests timing out in workload-low queues:

% kubectl get --raw /metrics | grep apiserver_flowcontrol_rejected_requests_total\napiserver_flowcontrol_rejected_requests_total{flow_schema=\"service-accounts\",priority_level=\"workload-low\",reason=\"time-out\"} 100\n

To check how close a given PriorityLevelConfiguration is to receiving 429s or experiencing increased latency due to queuing, you can compare the difference between the concurrency limit and the concurrency in use. In this example, we have a buffer of 100 requests.

% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_request_concurrency_limit.*workload-low'\napiserver_flowcontrol_request_concurrency_limit{priority_level=\"workload-low\"} 245\n\n% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_request_concurrency_in_use.*workload-low'\napiserver_flowcontrol_request_concurrency_in_use{flow_schema=\"service-accounts\",priority_level=\"workload-low\"} 145\n

To check if a given PriorityLevelConfiguration is experiencing queuing but not necessarily dropped requests, the metric for apiserver_flowcontrol_current_inqueue_requests can be referenced:

% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_current_inqueue_requests.*workload-low'\napiserver_flowcontrol_current_inqueue_requests{flow_schema=\"service-accounts\",priority_level=\"workload-low\"} 10\n

Other useful Prometheus metrics include:

  • apiserver_flowcontrol_dispatched_requests_total
  • apiserver_flowcontrol_request_execution_seconds
  • apiserver_flowcontrol_request_wait_duration_seconds

See the upstream documentation for a complete list of APF metrics.

"},{"location":"scalability/docs/control-plane/#preventing-dropped-requests","title":"Preventing Dropped Requests","text":""},{"location":"scalability/docs/control-plane/#prevent-429s-by-changing-your-workload","title":"Prevent 429s by changing your workload","text":"

When APF is dropping requests due to a given PriorityLevelConfiguration exceeding its maximum number of allowed inflight requests, clients in the affected FlowSchemas can decrease the number of requests executing at a given time. This can be accomplished by reducing the total number of requests made over the period where 429s are occurring. Note that long-running requests such as expensive list calls are especially problematic because they count as an inflight request for the entire duration they are executing. Reducing the number of these expensive requests or optimizing the latency of these list calls (for example, by reducing the number of objects fetched per request or switching to using a watch request) can help reduce the total concurrency required by the given workload.

"},{"location":"scalability/docs/control-plane/#prevent-429s-by-changing-your-apf-settings","title":"Prevent 429s by changing your APF settings","text":"

Warning

Only change default APF settings if you know what you are doing. Misconfigured APF settings can result in dropped API Server requests and significant workload disruptions.

One other approach for preventing dropped requests is changing the default FlowSchemas or PriorityLevelConfigurations installed on EKS clusters. EKS installs the upstream default settings for FlowSchemas and PriorityLevelConfigurations for the given Kubernetes minor version. The API Server will automatically reconcile these objects back to their defaults if modified unless the following annotation on the objects is set to false:

  metadata:\n    annotations:\n      apf.kubernetes.io/autoupdate-spec: \"false\"\n

At a high-level, APF settings can be modified to either:

  • Allocate more inflight capacity to requests you care about.
  • Isolate non-essential or expensive requests that can starve capacity for other request types.

This can be accomplished by either changing the default FlowSchemas and PriorityLevelConfigurations or by creating new objects of these types. Operators can increase the values for assuredConcurrencyShares for the relevant PriorityLevelConfigurations objects to increase the fraction of inflight requests they are allocated. Additionally, the number of requests that can be queued at a given time can also be increased if the application can handle the additional latency caused by requests being queued before they are dispatched.

Alternatively, new FlowSchema and PriorityLevelConfigurations objects can be created that are specific to the customer's workload. Be aware that allocating more assuredConcurrencyShares to either existing PriorityLevelConfigurations or to new PriorityLevelConfigurations will cause the number of requests that can be handled by other buckets to be reduced as the overall limit will stay as 600 inflight per API Server.

When making changes to APF defaults, these metrics should be monitored on a non-production cluster to ensure changing the settings do not cause unintended 429s:

  1. The metric for apiserver_flowcontrol_rejected_requests_total should be monitored for all FlowSchemas to ensure that no buckets start to drop requests.
  2. The values for apiserver_flowcontrol_request_concurrency_limit and apiserver_flowcontrol_request_concurrency_in_use should be compared to ensure that the concurrency in use is not at risk for breaching the limit for that priority level.

One common use-case for defining a new FlowSchema and PriorityLevelConfiguration is for isolation. Suppose we want to isolate long-running list event calls from pods to their own share of requests. This will prevent important requests from pods using the existing service-accounts FlowSchema from receiving 429s and being starved of request capacity. Recall that the total number of inflight requests is finite, however, this example shows APF settings can be modified to better divide request capacity for the given workload:

Example FlowSchema object to isolate list event requests:

apiVersion: flowcontrol.apiserver.k8s.io/v1beta1\nkind: FlowSchema\nmetadata:\n  name: list-events-default-service-accounts\nspec:\n  distinguisherMethod:\n    type: ByUser\n  matchingPrecedence: 8000\n  priorityLevelConfiguration:\n    name: catch-all\n  rules:\n  - resourceRules:\n    - apiGroups:\n      - '*'\n      namespaces:\n      - default\n      resources:\n      - events\n      verbs:\n      - list\n    subjects:\n    - kind: ServiceAccount\n      serviceAccount:\n        name: default\n        namespace: default\n
  • This FlowSchema captures all list event calls made by service accounts in the default namespace.
  • The matching precedence 8000 is lower than the value of 9000 used by the existing service-accounts FlowSchema so these list event calls will match list-events-default-service-accounts rather than service-accounts.
  • We're using the catch-all PriorityLevelConfiguration to isolate these requests. This bucket only allows 13 inflight requests to be used by these long-running list event calls. Pods will start to receive 429s as soon they try to issue more than 13 of these requests concurrently.
"},{"location":"scalability/docs/control-plane/#retrieving-resources-in-the-api-server","title":"Retrieving resources in the API server","text":"

Getting information from the API server is an expected behavior for clusters of any size. As you scale the number of resources in the cluster the frequency of requests and volume of data can quickly become a bottleneck for the control plane and will lead to API latency and slowness. Depending on the severity of the latency it cause unexpected downtime if you are not careful.

Being aware of what you are requesting and how often are the first steps to avoiding these types of problems. Here is guidance to limit the volume of queries based on the scaling best practices. Suggestions in this section are provided in order starting with the options that are known to scale the best.

"},{"location":"scalability/docs/control-plane/#use-shared-informers","title":"Use Shared Informers","text":"

When building controllers and automation that integrate with the Kubernetes API you will often need to get information from Kubernetes resources. If you poll for these resources regularly it can cause a significant load on the API server.

Using an informer from the client-go library will give you benefits of watching for changes to the resources based on events instead of polling for changes. Informers further reduce the load by using shared cache for the events and changes so multiple controllers watching the same resources do not add additional load.

Controllers should avoid polling cluster wide resources without labels and field selectors especially in large clusters. Each un-filtered poll requires a lot of unnecessary data to be sent from etcd through the API server to be filtered by the client. By filtering based on labels and namespaces you can reduce the amount of work the API server needs to perform to fullfil the request and data sent to the client.

"},{"location":"scalability/docs/control-plane/#optimize-kubernetes-api-usage","title":"Optimize Kubernetes API usage","text":"

When calling the Kubernetes API with custom controllers or automation it's important that you limit the calls to only the resources you need. Without limits you can cause unneeded load on the API server and etcd.

It is recommended that you use the watch argument whenever possible. With no arguments the default behavior is to list objects. To use watch instead of list you can append ?watch=true to the end of your API request. For example, to get all pods in the default namespace with a watch use:

/api/v1/namespaces/default/pods?watch=true\n

If you are listing objects you should limit the scope of what you are listing and the amount of data returned. You can limit the returned data by adding limit=500 argument to requests. The fieldSelector argument and /namespace/ path can be useful to make sure your lists are as narrowly scoped as needed. For example, to list only running pods in the default namespace use the following API path and arguments.

/api/v1/namespaces/default/pods?fieldSelector=status.phase=Running&limit=500\n

Or list all pods that are running with:

/api/v1/pods?fieldSelector=status.phase=Running&limit=500\n

Another option to limit watch calls or listed objects is to use resourceVersions which you can read about in the Kubernetes documentation. Without a resourceVersion argument you will receive the most recent version available which requires an etcd quorum read which is the most expensive and slowest read for the database. The resourceVersion depends on what resources you are trying to query and can be found in the metadata.resourseVersion field. This is also recommended in case of using watch calls and not just list calls

There is a special resourceVersion=0 available that will return results from the API server cache. This can reduce etcd load but it does not support pagination.

/api/v1/namespaces/default/pods?resourceVersion=0\n
It's recommended to use watch with a resourceVersion set to be the most recent known value received from its preceding list or watch. This is handled automatically in client-go. But it's suggested to double check it if you are using a k8s client in other languages.

/api/v1/namespaces/default/pods?watch=true&resourceVersion=362812295\n
If you call the API without any arguments it will be the most resource intensive for the API server and etcd. This call will get all pods in all namespaces without pagination or limiting the scope and require a quorum read from etcd.

/api/v1/pods\n
"},{"location":"scalability/docs/data-plane/","title":"Kubernetes Data Plane","text":"

The Kubernetes Data Plane includes EC2 instances, load balancers, storage, and other APIs used by the Kubernetes Control Plane. For organization purposes we grouped cluster services in a separate page and load balancer scaling can be found in the workloads section. This section will focus on scaling compute resources.

Selecting EC2 instance types is possibly one of the hardest decisions customers face because in clusters with multiple workloads. There is no one-size-fits all solution. Here are some tips to help you avoid common pitfalls with scaling compute.

"},{"location":"scalability/docs/data-plane/#automatic-node-autoscaling","title":"Automatic node autoscaling","text":"

We recommend you use node autoscaling that reduces toil and integrates deeply with Kubernetes. Managed node groups and Karpenter are recommended for large scale clusters.

Managed node groups will give you the flexibility of Amazon EC2 Auto Scaling groups with added benefits for managed upgrades and configuration. It can be scaled with the Kubernetes Cluster Autoscaler and is a common option for clusters that have a variety of compute needs.

Karpenter is an open source, workload-native node autoscaler created by AWS. It scales nodes in a cluster based on the workload requirements for resources (e.g. GPU) and taints and tolerations (e.g. zone spread) without managing node groups. Nodes are created directly from EC2 which avoids default node group quotas\u2014450 nodes per group\u2014and provides greater instance selection flexibility with less operational overhead. We recommend customers use Karpenter when possible.

"},{"location":"scalability/docs/data-plane/#use-many-different-ec2-instance-types","title":"Use many different EC2 instance types","text":"

Each AWS region has a limited number of available instances per instance type. If you create a cluster that uses only one instance type and scale the number of nodes beyond the capacity of the region you will receive an error that no instances are available. To avoid this issue you should not arbitrarily limit the type of instances that can be use in your cluster.

Karpenter will use a broad set of compatible instance types by default and will pick an instance at provisioning time based on pending workload requirements, availability, and cost. You can broaden the list of instance types used in the karpenter.k8s.aws/instance-category key of NodePools.

The Kubernetes Cluster Autoscaler requires node groups to be similarly sized so they can be consistently scaled. You should create multiple groups based on CPU and memory size and scale them independently. Use the ec2-instance-selector to identify instances that are similarly sized for your node groups.

ec2-instance-selector --service eks --vcpus-min 8 --memory-min 16\na1.2xlarge\na1.4xlarge\na1.metal\nc4.4xlarge\nc4.8xlarge\nc5.12xlarge\nc5.18xlarge\nc5.24xlarge\nc5.2xlarge\nc5.4xlarge\nc5.9xlarge\nc5.metal\n
"},{"location":"scalability/docs/data-plane/#prefer-larger-nodes-to-reduce-api-server-load","title":"Prefer larger nodes to reduce API server load","text":"

When deciding what instance types to use, fewer, large nodes will put less load on the Kubernetes Control Plane because there will be fewer kubelets and DaemonSets running. However, large nodes may not be utilized fully like smaller nodes. Node sizes should be evaluated based on your workload availability and scale requirements.

A cluster with three u-24tb1.metal instances (24 TB memory and 448 cores) has 3 kubelets, and would be limited to 110 pods per node by default. If your pods use 4 cores each then this might be expected (4 cores x 110 = 440 cores/node). With a 3 node cluster your ability to handle an instance incident would be low because 1 instance outage could impact 1/3 of the cluster. You should specify node requirements and pod spread in your workloads so the Kubernetes scheduler can place workloads properly.

Workloads should define the resources they need and the availability required via taints, tolerations, and PodTopologySpread. They should prefer the largest nodes that can be fully utilized and meet availability goals to reduce control plane load, lower operations, and reduce cost.

The Kubernetes Scheduler will automatically try to spread workloads across availability zones and hosts if resources are available. If no capacity is available the Kubernetes Cluster Autoscaler will attempt to add nodes in each Availability Zone evenly. Karpenter will attempt to add nodes as quickly and cheaply as possible unless the workload specifies other requirements.

To force workloads to spread with the scheduler and new nodes to be created across availability zones you should use topologySpreadConstraints:

spec:\n  topologySpreadConstraints:\n    - maxSkew: 3\n      topologyKey: \"topology.kubernetes.io/zone\"\n      whenUnsatisfiable: ScheduleAnyway\n      labelSelector:\n        matchLabels:\n          dev: my-deployment\n    - maxSkew: 2\n      topologyKey: \"kubernetes.io/hostname\"\n      whenUnsatisfiable: ScheduleAnyway\n      labelSelector:\n        matchLabels:\n          dev: my-deployment\n
"},{"location":"scalability/docs/data-plane/#use-similar-node-sizes-for-consistent-workload-performance","title":"Use similar node sizes for consistent workload performance","text":"

Workloads should define what size nodes they need to be run on to allow consistent performance and predictable scaling. A workload requesting 500m CPU will perform differently on an instance with 4 cores vs one with 16 cores. Avoid instance types that use burstable CPUs like T series instances.

To make sure your workloads get consistent performance a workload can use the supported Karpenter labels to target specific instances sizes.

kind: deployment\n...\nspec:\n  template:\n    spec:\n    containers:\n    nodeSelector:\n      karpenter.k8s.aws/instance-size: 8xlarge\n

Workloads being scheduled in a cluster with the Kubernetes Cluster Autoscaler should match a node selector to node groups based on label matching.

spec:\n  affinity:\n    nodeAffinity:\n      requiredDuringSchedulingIgnoredDuringExecution:\n        nodeSelectorTerms:\n        - matchExpressions:\n          - key: eks.amazonaws.com/nodegroup\n            operator: In\n            values:\n            - 8-core-node-group    # match your node group name\n
"},{"location":"scalability/docs/data-plane/#use-compute-resources-efficiently","title":"Use compute resources efficiently","text":"

Compute resources include EC2 instances and availability zones. Using compute resources effectively will increase your scalability, availability, performance, and reduce your total cost. Efficient resource usage is extremely difficult to predict in an autoscaling environment with multiple applications. Karpenter was created to provision instances on-demand based on the workload needs to maximize utilization and flexibility.

Karpenter allows workloads to declare the type of compute resources it needs without first creating node groups or configuring label taints for specific nodes. See the Karpenter best practices for more information. Consider enabling consolidation in your Karpenter provisioner to replace nodes that are under utilized.

"},{"location":"scalability/docs/data-plane/#automate-amazon-machine-image-ami-updates","title":"Automate Amazon Machine Image (AMI) updates","text":"

Keeping worker node components up to date will make sure you have the latest security patches and compatible features with the Kubernetes API. Updating the kubelet is the most important component for Kubernetes functionality, but automating OS, kernel, and locally installed application patches will reduce maintenance as you scale.

It is recommended that you use the latest Amazon EKS optimized Amazon Linux 2 or Amazon EKS optimized Bottlerocket AMI for your node image. Karpenter will automatically use the latest available AMI to provision new nodes in the cluster. Managed node groups will update the AMI during a node group update but will not update the AMI ID at node provisioning time.

For Managed Node Groups you need to update the Auto Scaling Group (ASG) launch template with new AMI IDs when they are available for patch releases. AMI minor versions (e.g. 1.23.5 to 1.24.3) will be available in the EKS console and API as upgrades for the node group. Patch release versions (e.g. 1.23.5 to 1.23.6) will not be presented as upgrades for the node groups. If you want to keep your node group up to date with AMI patch releases you need to create new launch template version and let the node group replace instances with the new AMI release.

You can find the latest available AMI from this page or use the AWS CLI.

aws ssm get-parameter \\\n  --name /aws/service/eks/optimized-ami/1.24/amazon-linux-2/recommended/image_id \\\n  --query \"Parameter.Value\" \\\n  --output text\n
"},{"location":"scalability/docs/data-plane/#use-multiple-ebs-volumes-for-containers","title":"Use multiple EBS volumes for containers","text":"

EBS volumes have input/output (I/O) quota based on the type of volume (e.g. gp3) and the size of the disk. If your applications share a single EBS root volume with the host this can exhaust the disk quota for the entire host and cause other applications to wait for available capacity. Applications write to disk if they write files to their overlay partition, mount a local volume from the host, and also when they log to standard out (STDOUT) depending on the logging agent used.

To avoid disk I/O exhaustion you should mount a second volume to the container state folder (e.g. /run/containerd), use separate EBS volumes for workload storage, and disable unnecessary local logging.

To mount a second volume to your EC2 instances using eksctl you can use a node group with this configuration:

managedNodeGroups:\n  - name: al2-workers\n    amiFamily: AmazonLinux2\n    desiredCapacity: 2\n    volumeSize: 80\n    additionalVolumes:\n      - volumeName: '/dev/sdz'\n        volumeSize: 100\n    preBootstrapCommands:\n    - |\n      \"systemctl stop containerd\"\n      \"mkfs -t ext4 /dev/nvme1n1\"\n      \"rm -rf /var/lib/containerd/*\"\n      \"mount /dev/nvme1n1 /var/lib/containerd/\"\n      \"systemctl start containerd\"\n

If you are using terraform to provision your node groups please see examples in EKS Blueprints for terraform. If you are using Karpenter to provision nodes you can use blockDeviceMappings with node user-data to add additional volumes.

To mount an EBS volume directly to your pod you should use the AWS EBS CSI driver and consume a volume with a storage class.

---\napiVersion: storage.k8s.io/v1\nkind: StorageClass\nmetadata:\n  name: ebs-sc\nprovisioner: ebs.csi.aws.com\nvolumeBindingMode: WaitForFirstConsumer\n---\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: ebs-claim\nspec:\n  accessModes:\n    - ReadWriteOnce\n  storageClassName: ebs-sc\n  resources:\n    requests:\n      storage: 4Gi\n---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: app\nspec:\n  containers:\n  - name: app\n    image: public.ecr.aws/docker/library/nginx\n    volumeMounts:\n    - name: persistent-storage\n      mountPath: /data\n  volumes:\n  - name: persistent-storage\n    persistentVolumeClaim:\n      claimName: ebs-claim\n
"},{"location":"scalability/docs/data-plane/#avoid-instances-with-low-ebs-attach-limits-if-workloads-use-ebs-volumes","title":"Avoid instances with low EBS attach limits if workloads use EBS volumes","text":"

EBS is one of the easiest ways for workloads to have persistent storage, but it also comes with scalability limitations. Each instance type has a maximum number of EBS volumes that can be attached. Workloads need to declare what instance types they should run on and limit the number of replicas on a single instance with Kubernetes taints.

"},{"location":"scalability/docs/data-plane/#disable-unnecessary-logging-to-disk","title":"Disable unnecessary logging to disk","text":"

Avoid unnecessary local logging by not running your applications with debug logging in production and disabling logging that reads and writes to disk frequently. Journald is the local logging service that keeps a log buffer in memory and flushes to disk periodically. Journald is preferred over syslog which logs every line immediately to disk. Disabling syslog also lowers the total amount of storage you need and avoids needing complicated log rotation rules. To disable syslog you can add the following snippet to your cloud-init configuration:

runcmd:\n  - [ systemctl, disable, --now, syslog.service ]\n
"},{"location":"scalability/docs/data-plane/#patch-instances-in-place-when-os-update-speed-is-a-necessity","title":"Patch instances in place when OS update speed is a necessity","text":"

Attention

Patching instances in place should only be done when required. Amazon recommends treating infrastructure as immutable and thoroughly testing updates that are promoted through lower environments the same way applications are. This section applies when that is not possible.

It takes seconds to install a package on an existing Linux host without disrupting containerized workloads. The package can be installed and validated without cordoning, draining, or replacing the instance.

To replace an instance you first need to create, validate, and distribute new AMIs. The instance needs to have a replacement created, and the old instance needs to be cordoned and drained. Then workloads need to be created on the new instance, verified, and repeated for all instances that need to be patched. It takes hours, days, or weeks to replace instances safely without disrupting workloads.

Amazon recommends using immutable infrastructure that is built, tested, and promoted from an automated, declarative system, but if you have a requirement to patch systems quickly then you will need to patch systems in place and replace them as new AMIs are made available. Because of the large time differential between patching and replacing systems we recommend using AWS Systems Manager Patch Manager to automate patching nodes when required to do so.

Patching nodes will allow you to quickly roll out security updates and replace the instances on a regular schedule after your AMI has been updated. If you are using an operating system with a read-only root file system like Flatcar Container Linux or Bottlerocket OS we recommend using the update operators that work with those operating systems. The Flatcar Linux update operator and Bottlerocket update operator will reboot instances to keep nodes up to date automatically.

"},{"location":"scalability/docs/kcp_monitoring/","title":"Control Plane Monitoring","text":""},{"location":"scalability/docs/kcp_monitoring/#api-server","title":"API Server","text":"

When looking at our API server it\u2019s important to remember that one of its functions is to throttle inbound requests to prevent overloading the control plane. What can seem like a bottleneck at the API server level might actually be protecting it from more serious issues. We need to factor in the pros and cons of increasing the volume of requests moving through the system. To make a determination if the API server values should be increased, here is small sampling of the things we need to be mindful of:

  1. What is the latency of requests moving through the system?
  2. Is that latency the API server itself, or something \u201cdownstream\u201d like etcd?
  3. Is the API server queue depth a factor in this latency?
  4. Are the API Priority and Fairness (APF) queues setup correctly for the API call patterns we want?
"},{"location":"scalability/docs/kcp_monitoring/#where-is-the-issue","title":"Where is the issue?","text":"

To start, we can use the metric for API latency to give us insight into how long it\u2019s taking the API server to service requests. Let\u2019s use the below PromQL and Grafana heatmap to display this data.

max(increase(apiserver_request_duration_seconds_bucket{subresource!=\"status\",subresource!=\"token\",subresource!=\"scale\",subresource!=\"/healthz\",subresource!=\"binding\",subresource!=\"proxy\",verb!=\"WATCH\"}[$__rate_interval])) by (le)\n

Tip

For an in depth write up on how to monitor the API server with the API dashboard used in this article, please see the following blog

These requests are all under the one second mark, which is a good indication that the control plane is handling requests in a timely fashion. But what if that was not the case?

The format we are using in the above API Request Duration is a heatmap. What\u2019s nice about the heatmap format, is that it tells us the timeout value for the API by default (60 sec). However, what we really need to know is at what threshold should this value be of concern before we reach the timeout threshold. For a rough guideline of what acceptable thresholds are we can use the upstream Kubernetes SLO, which can be found here

Tip

Notice the max function on this statement? When using metrics that are aggregating multiple servers (by default two API servers on EKS) it\u2019s important not to average those servers together.

"},{"location":"scalability/docs/kcp_monitoring/#asymmetrical-traffic-patterns","title":"Asymmetrical traffic patterns","text":"

What if one API server [pod] was lightly loaded, and the other heavily loaded? If we averaged those two numbers together we might misinterpret what was happening. For example, here we have three API servers but all of the load is on one of these API servers. As a rule anything that has multiple servers such as etcd and API servers should be broken out when investing scale and performance issues.

With the move to API Priority and Fairness the total number of requests on the system is only one factor to check to see if the API server is oversubscribed. Since the system now works off a series of queues, we must look to see if any of these queues are full and if the traffic for that queue is getting dropped.

Let\u2019s look at these queues with the following query:

max without(instance)(apiserver_flowcontrol_request_concurrency_limit{})\n

Note

For more information on how API A&F works please see the following best practices guide

Here we see the seven different priority groups that come by default on the cluster

Next we want to see what percentage of that priority group is being used, so that we can understand if a certain priority level is being saturated. Throttling requests in the workload-low level might be desirable, however drops in a leader election level would not be.

The API Priority and Fairness (APF) system has a number of complex options, some of those options can have unintended consequences. A common issue we see in the field is increasing the queue depth to the point it starts adding unnecessary latency. We can monitor this problem by using the apiserver_flowcontrol_current_inqueue_request metric. We can check for drops using the apiserver_flowcontrol_rejected_requests_total. These metrics will be a non-zero value if any bucket exceeds its concurrency.

Increasing the queue depth can make the API Server a significant source of latency and should be done with care. We recommend being judicious with the number of queues created. For example, the number of shares on a EKS system is 600, if we create too many queues, this can reduce the shares in important queues that need the throughput such as the leader-election queue or system queue. Creating too many extra queues can make it more difficult to size theses queues correctly.

To focus on a simple impactful change you can make in APF we simply take shares from underutilized buckets and increase the size of buckets that are at their max usage. By intelligently redistributing the shares among these buckets, you can make drops less likely.

For more information, visit API Priority and Fairness settings in the EKS Best Practices Guide.

"},{"location":"scalability/docs/kcp_monitoring/#api-vs-etcd-latency","title":"API vs. etcd latency","text":"

How can we use the metrics/logs of the API server to determine whether there\u2019s a problem with API server, or a problem that\u2019s upstream/downstream of the API server, or a combination of both. To understand this better, lets look at how API Server and etcd can be related, and how easy it can be to troubleshoot the wrong system.

In the below chart we see API server latency, but we also see much of this latency is correlated to the etcd server due to the bars in the graph showing most of the latency at the etcd level. If there is 15 secs of etcd latency at the same time there is 20 seconds of API server latency, then the majority of the latency is actually at the etcd level.

By looking at the whole flow, we see that it\u2019s wise to not focus solely on the API Server, but also look for signals that indicate that etcd is under duress (i.e. slow apply counters increasing). Being able to quickly move to the right problem area with just a glance is what makes a dashboard powerful.

Tip

The dashboard in section can be found at https://github.com/RiskyAdventure/Troubleshooting-Dashboards/blob/main/api-troubleshooter.json

"},{"location":"scalability/docs/kcp_monitoring/#control-plane-vs-client-side-issues","title":"Control plane vs. Client side issues","text":"

In this chart we are looking for the API calls that took the most time to complete for that period. In this case we see a custom resource (CRD) is calling a APPLY function that is the most latent call during the 05:40 time frame.

Armed with this data we can use an Ad-Hoc PromQL or a CloudWatch Insights query to pull LIST requests from the audit log during that time frame to see which application this might be.

"},{"location":"scalability/docs/kcp_monitoring/#finding-the-source-with-cloudwatch","title":"Finding the Source with CloudWatch","text":"

Metrics are best used to find the problem area we want to look at and narrow both the timeframe and the search parameters of the problem. Once we have this data we want to transition to logs for more detailed times and errors. To do this we will turn our logs into metrics using CloudWatch Logs Insights.

For example, to investigate the issue above, we will use the following CloudWatch Logs Insights query to pull the userAgent and requestURI so that we can pin down which application is causing this latency.

Tip

An appropriate Count needs to be used as to not pull normal List/Resync behavior on a Watch.

fields *@timestamp*, *@message*\n| filter *@logStream* like \"kube-apiserver-audit\"\n| filter ispresent(requestURI)\n| filter verb = \"list\"\n| parse requestReceivedTimestamp /\\d+-\\d+-(?<StartDay>\\d+)T(?<StartHour>\\d+):(?<StartMinute>\\d+):(?<StartSec>\\d+).(?<StartMsec>\\d+)Z/\n| parse stageTimestamp /\\d+-\\d+-(?<EndDay>\\d+)T(?<EndHour>\\d+):(?<EndMinute>\\d+):(?<EndSec>\\d+).(?<EndMsec>\\d+)Z/\n| fields (StartHour * 3600 + StartMinute * 60 + StartSec + StartMsec / 1000000) as StartTime, (EndHour * 3600 + EndMinute * 60 + EndSec + EndMsec / 1000000) as EndTime, (EndTime - StartTime) as DeltaTime\n| stats avg(DeltaTime) as AverageDeltaTime, count(*) as CountTime by requestURI, userAgent\n| filter CountTime >=50\n| sort AverageDeltaTime desc\n

Using this query we found two different agents running a large number of high latency list operations. Splunk and CloudWatch agent. Armed with the data, we can make a decision to remove, update, or replace this controller with another project.

Tip

For more details on this subject please see the following blog

"},{"location":"scalability/docs/kcp_monitoring/#scheduler","title":"Scheduler","text":"

Since the EKS control plane instances are run in separate AWS account we will not be able to scrape those components for metrics (The API server being the exception). However, since we have access to the audit logs for these components, we can turn those logs into metrics to see if any of the sub-systems are causing a scaling bottleneck. Let\u2019s use CloudWatch Logs Insights to see how many unscheduled pods are in the scheduler queue.

"},{"location":"scalability/docs/kcp_monitoring/#unscheduled-pods-in-the-scheduler-log","title":"Unscheduled pods in the scheduler log","text":"

If we had access to scrape the scheduler metrics directly on a self managed Kubernetes (such as Kops) we would use the following PromQL to understand the scheduler backlog.

max without(instance)(scheduler_pending_pods)\n

Since we do not have access to the above metric in EKS, we will use the below CloudWatch Logs Insights query to see the backlog by checking for how many pods were unable to unscheduled during a particular time frame. Then we could dive further into into the messages at the peak time frame to understand the nature of the bottleneck. For example, nodes not spinning up fast enough, or the rate limiter in the scheduler itself.

fields timestamp, pod, err, *@message*\n| filter *@logStream* like \"scheduler\"\n| filter *@message* like \"Unable to schedule pod\"\n| parse *@message*  /^.(?<date>\\d{4})\\s+(?<timestamp>\\d+:\\d+:\\d+\\.\\d+)\\s+\\S*\\s+\\S+\\]\\s\\\"(.*?)\\\"\\s+pod=(?<pod>\\\"(.*?)\\\")\\s+err=(?<err>\\\"(.*?)\\\")/\n| count(*) as count by pod, err\n| sort count desc\n

Here we see the errors from the scheduler saying the pod did not deploy because the storage PVC was unavailable.

Note

Audit logging must be turned on the control plane to enable this function. It is also a best practice to limit the log retention as to not drive up cost over time unnecessarily. An example for turning on all logging functions using the EKSCTL tool below.

cloudWatch:\n  clusterLogging:\n    enableTypes: [\"*\"]\n    logRetentionInDays: 10\n
"},{"location":"scalability/docs/kcp_monitoring/#kube-controller-manager","title":"Kube Controller Manager","text":"

Kube Controller Manager, like all other controllers, has limits on how many operations it can do at once. Let\u2019s review what some of those flags are by looking at a KOPS configuration where we can set these parameters.

  kubeControllerManager:\n    concurrentEndpointSyncs: 5\n    concurrentReplicasetSyncs: 5\n    concurrentNamespaceSyncs: 10\n    concurrentServiceaccountTokenSyncs: 5\n    concurrentServiceSyncs: 5\n    concurrentResourceQuotaSyncs: 5\n    concurrentGcSyncs: 20\n    kubeAPIBurst: 20\n    kubeAPIQPS: \"30\"\n

These controllers have queues that fill up during times of high churn on a cluster. In this case we see the replicaset set controller has a large backlog in its queue.

We have two different ways of addressing such a situation. If running self managed we could simply increase the concurrent goroutines, however this would have an impact on etcd by processing more data in the KCM. The other option would be to reduce the number of replicaset objects using .spec.revisionHistoryLimit on the deployment to reduce the number of replicaset objects we can rollback, thus reducing the pressure on this controller.

spec:\n  revisionHistoryLimit: 2\n

Other Kubernetes features can be tuned or turned off to reduce pressure in high churn rate systems. For example, if the application in our pods doesn\u2019t need to speak to the k8s API directly then turning off the projected secret into those pods would decrease the load on ServiceaccountTokenSyncs. This is the more desirable way to address such issues if possible.

kind: Pod\nspec:\n  automountServiceAccountToken: false\n

In systems where we can\u2019t get access to the metrics, we can again look at the logs to detect contention. If we wanted to see the number of requests being being processed on a per controller or an aggregate level we would use the following CloudWatch Logs Insights Query.

"},{"location":"scalability/docs/kcp_monitoring/#total-volume-processed-by-the-kcm","title":"Total Volume Processed by the KCM","text":"
# Query to count API qps coming from kube-controller-manager, split by controller type.\n# If you're seeing values close to 20/sec for any particular controller, it's most likely seeing client-side API throttling.\nfields @timestamp, @logStream, @message\n| filter @logStream like /kube-apiserver-audit/\n| filter userAgent like /kube-controller-manager/\n# Exclude lease-related calls (not counted under kcm qps)\n| filter requestURI not like \"apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager\"\n# Exclude API discovery calls (not counted under kcm qps)\n| filter requestURI not like \"?timeout=32s\"\n# Exclude watch calls (not counted under kcm qps)\n| filter verb != \"watch\"\n# If you want to get counts of API calls coming from a specific controller, uncomment the appropriate line below:\n# | filter user.username like \"system:serviceaccount:kube-system:job-controller\"\n# | filter user.username like \"system:serviceaccount:kube-system:cronjob-controller\"\n# | filter user.username like \"system:serviceaccount:kube-system:deployment-controller\"\n# | filter user.username like \"system:serviceaccount:kube-system:replicaset-controller\"\n# | filter user.username like \"system:serviceaccount:kube-system:horizontal-pod-autoscaler\"\n# | filter user.username like \"system:serviceaccount:kube-system:persistent-volume-binder\"\n# | filter user.username like \"system:serviceaccount:kube-system:endpointslice-controller\"\n# | filter user.username like \"system:serviceaccount:kube-system:endpoint-controller\"\n# | filter user.username like \"system:serviceaccount:kube-system:generic-garbage-controller\"\n| stats count(*) as count by user.username\n| sort count desc\n

The key takeaway here is when looking into scalability issues, to look at every step in the path (API, scheduler, KCM, etcd) before moving to the detailed troubleshooting phase. Often in production you will find that it takes adjustments to more than one part of Kubernetes to allow the system to work at its most performant. It\u2019s easy to inadvertently troubleshoot what is just a symptom (such as a node timeout) of a much larger bottle neck.

"},{"location":"scalability/docs/kcp_monitoring/#etcd","title":"ETCD","text":"

etcd uses a memory mapped file to store key value pairs efficiently. There is a protection mechanism to set the size of this memory space available set commonly at the 2, 4, and 8GB limits. Fewer objects in the database means less clean up etcd needs to do when objects are updated and older versions needs to be cleaned out. This process of cleaning old versions of an object out is referred to as compaction. After a number of compaction operations, there is a subsequent process that recovers usable space space called defragging that happens above a certain threshold or on a fixed schedule of time.

There are a couple user related items we can do to limit the number of objects in Kubernetes and thus reduce the impact of both the compaction and de-fragmentation process. For example, Helm keeps a high revisionHistoryLimit. This keeps older objects such as ReplicaSets on the system to be able to do rollbacks. By setting the history limits down to 2 we can reduce the number of objects (like ReplicaSets) from ten to two which in turn would put less load on the system.

apiVersion: apps/v1\nkind: Deployment\nspec:\n  revisionHistoryLimit: 2\n

From a monitoring standpoint, if system latency spikes occur in a set pattern separated by hours, checking to see if this defragmentation process is the source can be helpful. We can see this by using CloudWatch Logs.

If you want to see start/end times of defrag use the following query:

fields *@timestamp*, *@message*\n| filter *@logStream* like /etcd-manager/\n| filter *@message* like /defraging|defraged/\n| sort *@timestamp* asc\n

"},{"location":"scalability/docs/kubernetes_slos/","title":"Kubernetes Upstream SLOs","text":"

Amazon EKS runs the same code as the upstream Kubernetes releases and ensures that EKS clusters operate within the SLOs defined by the Kubernetes community. The KubernetesScalability Special Interest Group (SIG) defines the scalability goals and investigates bottlenecks in performance through SLIs and SLOs.

SLIs are how we measure a system like metrics or measures that can be used to determine how \u201cwell\u201d the system is running, e.g. request latency or count. SLOs define the values that are expected for when the system is running \u201cwell\u201d, e.g. request latency remains less than 3 seconds. The Kubernetes SLOs and SLIs focus on the performance of the Kubernetes components and are completely independent from the Amazon EKS Service SLAs which focus on availability of the EKS cluster endpoint.

Kubernetes has a number of features that allow users to extend the system with custom add-ons or drivers, like CSI drivers, admission webhooks, and auto-scalers. These extensions can drastically impact the performance of a Kubernetes cluster in different ways, i.e. an admission webhook with failurePolicy=Ignore could add latency to K8s API requests if the webhook target is unavailable. The Kubernetes Scalability SIG defines scalability using a \"you promise, we promise\" framework:

If you promise to: - correctly configure your cluster - use extensibility features \"reasonably\" - keep the load in the cluster within recommended limits

then we promise that your cluster scales, i.e.: - all the SLOs are satisfied.

"},{"location":"scalability/docs/kubernetes_slos/#kubernetes-slos","title":"Kubernetes SLOs","text":"

The Kubernetes SLOs don\u2019t account for all of the plugins and external limitations that could impact a cluster, such as worker node scaling or admission webhooks. These SLOs focus on Kubernetes components and ensure that Kubernetes actions and resources are operating within expectations. The SLOs help Kubernetes developers ensure that changes to Kubernetes code do not degrade performance for the entire system.

The Kuberntes Scalability SIG defines the following official SLO/SLIs. The Amazon EKS team regularly runs scalability tests on EKS clusters for these SLOs/SLIs to monitor for performance degradation as changes are made and new versions are released.

Objective Definition SLO API request latency (mutating) Latency of processing mutating API calls for single objects for every (resource, verb) pair, measured as 99th percentile over last 5 minutes In default Kubernetes installation, for every (resource, verb) pair, excluding virtual and aggregated resources and Custom Resource Definitions, 99th percentile per cluster-day <= 1s API request latency (read-only) Latency of processing non-streaming read-only API calls for every (resource, scope) pair, measured as 99th percentile over last 5 minutes In default Kubernetes installation, for every (resource, scope) pair, excluding virtual and aggregated resources and Custom Resource Definitions, 99th percentile per cluster-day: (a) <= 1s if scope=resource (b) <= 30s otherwise (if scope=namespace or scope=cluster) Pod startup latency Startup latency of schedulable stateless pods, excluding time to pull images and run init containers, measured from pod creation timestamp to when all its containers are reported as started and observed via watch, measured as 99th percentile over last 5 minutes In default Kubernetes installation, 99th percentile per cluster-day <= 5s"},{"location":"scalability/docs/kubernetes_slos/#api-request-latency","title":"API Request Latency","text":"

The kube-apiserver has --request-timeout defined as 1m0s by default, which means a request can run for up to one minute (60 seconds) before being timed out and cancelled. The SLOs defined for Latency are broken out by the type of request that is being made, which can be mutating or read-only:

"},{"location":"scalability/docs/kubernetes_slos/#mutating","title":"Mutating","text":"

Mutating requests in Kubernetes make changes to a resource, such as creations, deletions, or updates. These requests are expensive because those changes must be written to the etcd backend before the updated object is returned. Etcd is a distributed key-value store that is used for all Kubernetes cluster data.

This latency is measured as the 99th percentile over 5min for (resource, verb) pairs of Kubernetes resources, for example this would measure the latency for Create Pod requests and Update Node requests. The request latency must be <= 1 second to satisfy the SLO.

"},{"location":"scalability/docs/kubernetes_slos/#read-only","title":"Read-only","text":"

Read-only requests retrieve a single resource (such as Get Pod X) or a collection (such as \u201cGet all Pods from Namespace X\u201d). The kube-apiserver maintains a cache of objects, so the requested resources may be returned from cache or they may need to be retrieved from etcd first. These latencies are also measured by the 99th percentile over 5 minutes, however read-only requests can have separate scopes. The SLO defines two different objectives:

  • For requests made for a single resource (i.e. kubectl get pod -n mynamespace my-controller-xxx ), the request latency should remain <= 1 second.
  • For requests that are made for multiple resources in a namespace or a cluster (for example, kubectl get pods -A) the latency should remain <= 30 seconds

The SLO has different target values for different request scopes because requests made for a list of Kubernetes resources expect the details of all objects in the request to be returned within the SLO. On large clusters, or large collections of resources, this can result in large response sizes which can take some time to return. For example, in a cluster running tens of thousands of Pods with each Pod being roughly 1 KiB when encoded in JSON, returning all Pods in the cluster would consist of 10MB or more. Kubernetes clients can help reduce this response size using APIListChunking to retrieve large collections of resources.

"},{"location":"scalability/docs/kubernetes_slos/#pod-startup-latency","title":"Pod Startup Latency","text":"

This SLO is primarily concerned with the time it takes from Pod creation to when the containers in that Pod actually begin execution. To measure this the difference from the creation timestamp recorded on the Pod, and when a WATCH on that Pod reports the containers have started is calculated (excluding time for container image pulls and init container execution). To satisfy the SLO the 99th percentile per cluster-day of this Pod Startup Latency must remain <=5 seconds.

Note that this SLO assumes that the worker nodes already exist in this cluster in a ready state for the Pod to be scheduled on. This SLO does not account for image pulls or init container executions, and also limits the test to \u201cstateless pods\u201d which don\u2019t leverage persistent storage plugins.

"},{"location":"scalability/docs/kubernetes_slos/#kubernetes-sli-metrics","title":"Kubernetes SLI Metrics","text":"

Kubernetes is also improving the Observability around the SLIs by adding Prometheus metrics to Kubernetes components that track these SLIs over time. Using Prometheus Query Language (PromQL) we can build queries that display the SLI performance over time in tools like Prometheus or Grafana dashboards, below are some examples for the SLOs above.

"},{"location":"scalability/docs/kubernetes_slos/#api-server-request-latency","title":"API Server Request Latency","text":"Metric Definition apiserver_request_sli_duration_seconds Response latency distribution (not counting webhook duration and priority & fairness queue wait times) in seconds for each verb, group, version, resource, subresource, scope and component. apiserver_request_duration_seconds Response latency distribution in seconds for each verb, dry run value, group, version, resource, subresource, scope and component.

Note: The apiserver_request_sli_duration_seconds metric is available starting in Kubernetes 1.27.

You can use these metrics to investigate the API Server response times and if there are bottlenecks in the Kubernetes components or other plugins/components. The queries below are based on the community SLO dashboard.

API Request latency SLI (mutating) - this time does not include webhook execution or time waiting in queue. histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~\"CREATE|DELETE|PATCH|POST|PUT\", subresource!~\"proxy|attach|log|exec|portforward\"}[5m])) by (resource, subresource, verb, scope, le)) > 0

API Request latency Total (mutating) - this is the total time the request took on the API server, this time may be longer than the SLI time because it includes webhook execution and API Priority and Fairness wait times. histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~\"CREATE|DELETE|PATCH|POST|PUT\", subresource!~\"proxy|attach|log|exec|portforward\"}[5m])) by (resource, subresource, verb, scope, le)) > 0

In these queries we are excluding the streaming API requests which do not return immediately, such as kubectl port-forward or kubectl exec requests (subresource!~\"proxy|attach|log|exec|portforward\"), and we are filtering for only the Kubernetes verbs that modify objects (verb=~\"CREATE|DELETE|PATCH|POST|PUT\"). We are then calculating the 99th percentile of that latency over the last 5 minutes.

We can use a similar query for the read only API requests, we simply modify the verbs we\u2019re filtering for to include the Read only actions LIST and GET. There are also different SLO thresholds depending on the scope of the request, i.e. getting a single resource or listing a number of resources.

API Request latency SLI (read-only) - this time does not include webhook execution or time waiting in queue. For a single resource (scope=resource, threshold=1s) histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~\"GET\", scope=~\"resource\"}[5m])) by (resource, subresource, verb, scope, le))

For a collection of resources in the same namespace (scope=namespace, threshold=5s) histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~\"LIST\", scope=~\"namespace\"}[5m])) by (resource, subresource, verb, scope, le))

For a collection of resources across the entire cluster (scope=cluster, threshold=30s) histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~\"LIST\", scope=~\"cluster\"}[5m])) by (resource, subresource, verb, scope, le))

API Request latency Total (read-only) - this is the total time the request took on the API server, this time may be longer than the SLI time because it includes webhook execution and wait times. For a single resource (scope=resource, threshold=1s) histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~\"GET\", scope=~\"resource\"}[5m])) by (resource, subresource, verb, scope, le))

For a collection of resources in the same namespace (scope=namespace, threshold=5s) histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~\"LIST\", scope=~\"namespace\"}[5m])) by (resource, subresource, verb, scope, le))

For a collection of resources across the entire cluster (scope=cluster, threshold=30s) histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~\"LIST\", scope=~\"cluster\"}[5m])) by (resource, subresource, verb, scope, le))

The SLI metrics provide insight into how Kubernetes components are performing by excluding the time that requests spend waiting in API Priority and Fairness queues, working through admission webhooks, or other Kubernetes extensions. The total metrics provide a more holistic view as it reflects the time your applications would be waiting for a response from the API server. Comparing these metrics can provide insight into where the delays in request processing are being introduced.

"},{"location":"scalability/docs/kubernetes_slos/#pod-startup-latency_1","title":"Pod Startup Latency","text":"Metric Definition kubelet_pod_start_sli_duration_seconds Duration in seconds to start a pod, excluding time to pull images and run init containers, measured from pod creation timestamp to when all its containers are reported as started and observed via watch kubelet_pod_start_duration_seconds Duration in seconds from kubelet seeing a pod for the first time to the pod starting to run. This does not include the time to schedule the pod or scale out worker node capacity.

Note: kubelet_pod_start_sli_duration_seconds is available starting in Kubernetes 1.27.

Similar to the queries above you can use these metrics to gain insight into how long node scaling, image pulls and init containers are delaying the pod launch compared to Kubelet actions.

Pod startup latency SLI - this is the time from the pod being created to when the application containers reported as running. This includes the time it takes for the worker node capacity to be available and the pod to be scheduled, but this does not include the time it takes to pull images or for the init containers to run. histogram_quantile(0.99, sum(rate(kubelet_pod_start_sli_duration_seconds_bucket[5m])) by (le))

Pod startup latency Total - this is the time it takes the kubelet to start the pod for the first time. This is measured from when the kubelet recieves the pod via WATCH, which does not include the time for worker node scaling or scheduling. This includes the time to pull images and init containers to run. histogram_quantile(0.99, sum(rate(kubelet_pod_start_duration_seconds_bucket[5m])) by (le))

"},{"location":"scalability/docs/kubernetes_slos/#slos-on-your-cluster","title":"SLOs on Your Cluster","text":"

If you are collecting the Prometheus metrics from the Kubernetes resources in your EKS cluster you can gain deeper insights into the performance of the Kubernetes control plane components.

The perf-tests repo includes Grafana dashboards that display the latencies and critical performance metrics for the cluster during tests. The perf-tests configuration leverages the kube-prometheus-stack, an open source project that comes configured to collect Kubernetes metrics, but you can also use Amazon Managed Prometheus and Amazon Managed Grafana.

If you are using the kube-prometheus-stack or similar Prometheus solution you can install the same dashboard to observe the SLOs on your cluster in real time.

  1. You will first need to install the Prometheus Rules that are used in the dashboards with kubectl apply -f prometheus-rules.yaml. You can download a copy of the rules here: https://github.com/kubernetes/perf-tests/blob/master/clusterloader2/pkg/prometheus/manifests/prometheus-rules.yaml
    1. Be sure to check the namespace in the file matches your environment
    2. Verify that the labels match the prometheus.prometheusSpec.ruleSelector helm value if you are using kube-prometheus-stack
  2. You can then install the dashboards in Grafana. The json dashboards and python scripts to generate them are available here: https://github.com/kubernetes/perf-tests/tree/master/clusterloader2/pkg/prometheus/manifests/dashboards
    1. the slo.json dashboard displays the performance of the cluster in relation to the Kubernetes SLOs

Consider that the SLOs are focused on the performance of the Kubernetes components in your clusters, but there are additional metrics you can review which provide different perspectives or insights in to your cluster. Kubernetes community projects like Kube-state-metrics can help you quickly analyze trends in your cluster. Most common plugins and drivers from the Kubernetes community also emit Prometheus metrics, allowing you to investigate things like autoscalers or custom schedulers.

The Observability Best Practices Guide has examples of other Kubernetes metrics you can use to gain further insight.

"},{"location":"scalability/docs/node_efficiency/","title":"Node and Workload Efficiency","text":"

Being efficient with our workloads and nodes reduces complexity/cost while increasing performance and scale. There are many factors to consider when planning this efficiency, and it\u2019s easiest to think in terms of trade offs vs. one best practice setting for each feature. Let\u2019s explore these tradeoffs in depth in the following section.

"},{"location":"scalability/docs/node_efficiency/#node-selection","title":"Node Selection","text":"

Using node sizes that are slightly larger (4-12xlarge) increases the available space that we have for running pods due to the fact it reduces the percentage of the node used for \u201coverhead\u201d such as DaemonSets and Reserves for system components. In the diagram below we see the difference between the usable space on a 2xlarge vs. a 8xlarge system with just a moderate number of DaemonSets.

Note

Since k8s scales horizontally as a general rule, for most applications it does not make sense to take the performance impact of NUMA sizes nodes, thus the recommendation of a range below that node size.

Large nodes sizes allow us to have a higher percentage of usable space per node. However, this model can be taken to to the extreme by packing the node with so many pods that it causes errors or saturates the node. Monitoring node saturation is key to successfully using larger node sizes.

Node selection is rarely a one-size-fits-all proposition. Often it is best to split workloads with dramatically different churn rates into different node groups. Small batch workloads with a high churn rate would be best served by the 4xlarge family of instances, while a large scale application such as Kafka which takes 8 vCPU and has a low churn rate would be better served by the 12xlarge family.

Tip

Another factor to consider with very large node sizes is since CGROUPS do not hide the total number of vCPU from the containerized application. Dynamic runtimes can often spawn an unintentional number of OS threads, creating latency that is difficult to troubleshoot. For these application CPU pinning is recommend. For a deeper exploration of topic please see the following video https://www.youtube.com/watch?v=NqtfDy_KAqg

"},{"location":"scalability/docs/node_efficiency/#node-bin-packing","title":"Node Bin-packing","text":""},{"location":"scalability/docs/node_efficiency/#kubernetes-vs-linux-rules","title":"Kubernetes vs. Linux Rules","text":"

There are two sets of rules we need to be mindful of when dealing with workloads on Kubernetes. The rules of the Kubernetes Scheduler, which uses the request value to schedule pods on a node, and then what happens after the pod is scheduled, which is the realm of Linux, not Kubernetes.

After Kubernetes scheduler is finished, a new set of rules takes over, the Linux Completely Fair Scheduler (CFS). The key take away is that Linux CFS doesn\u2019t have a the concept of a core. We will discuss why thinking in cores can lead to major problems with optimizing workloads for scale.

"},{"location":"scalability/docs/node_efficiency/#thinking-in-cores","title":"Thinking in Cores","text":"

The confusion starts because the Kubernetes scheduler does have the concept of cores. From a Kubernetes scheduler perspective if we looked at a node with 4 NGINX pods, each with a request of one core set, the node would look like this.

However, let\u2019s do a thought experiment on how different this looks from a Linux CFS perspective. The most important thing to remember when using the Linux CFS system is: busy containers (CGROUPS) are the only containers that count toward the share system. In this case, only the first container is busy so it is allowed to use all 4 cores on the node.

Why does this matter? Let\u2019s say we ran our performance testing in a development cluster where an NGINX application was the only busy container on that node. When we move the app to production, the following would happen: the NGINX application wants 4 vCPU of resources however, because all the other pods on the node are busy, our app\u2019s performance is constrained.

This situation would lead us to add more containers unnecessarily because we were not allowing our applications scale to their \u201csweet spot\u201c. Let's explore this important concept of a \u201dsweet spot\u201c in a bit more detail.

"},{"location":"scalability/docs/node_efficiency/#application-right-sizing","title":"Application right sizing","text":"

Each application has a certain point where it can not take anymore traffic. Going above this point can increase processing times and even drop traffic when pushed well beyond this point. This is known as the application\u2019s saturation point. To avoid scaling issues, we should attempt to scale the application before it reaches its saturation point. Let\u2019s call this point the sweet spot.

We need to test each of our applications to understand its sweet spot. There will be no universal guidance here as each application is different. During this testing we are trying to understand the best metric that shows our applications saturation point. Oftentimes, utilization metrics are used to indicate an application is saturated but this can quickly lead to scaling issues (We will explore this topic in detail in a later section). Once we have this \u201csweet spot\u201c we can use it to efficiently scale our workloads.

Conversely, what would happen if we scale up well before the sweet spot and created unnecessary pods? Let\u2019s explore that in the next section.

"},{"location":"scalability/docs/node_efficiency/#pod-sprawl","title":"Pod sprawl","text":"

To see how creating unnecessary pods could quickly get out of hand, let's look at the first example on the left. The correct vertical scale of this container takes up about two vCPUs worth of utilization when handling 100 requests a second. However, If we were to under-provision the requests value by setting requests to half a core, we would now need 4 pods for each one pods we actually needed. Exacerbating this problem further, if our HPA was set at the default of 50% CPU, those pods would scale half empty, creating an 8:1 ratio.

Scaling this problem up we can quickly see how this can get out of hand. A deployment of ten pods whose sweet spot was set incorrectly could quickly spiral to 80 pods and the additional infrastructure needed to run them.

Now that we understand the impact of not allowing applications to operate in their sweet spot, let\u2019s return to the node level and ask why this difference between the Kubernetes scheduler and Linux CFS so important?

When scaling up and down with HPA, we can have a scenario where we have a lot of space to allocate more pods. This would be a bad decision because the node depicted on the left is already at 100% CPU utilization. In a unrealistic but theoretically possible scenario, we could have the other extreme where our node is completely full, yet our CPU utilization is zero.

"},{"location":"scalability/docs/node_efficiency/#setting-requests","title":"Setting Requests","text":"

It would tempting to set the request at the \u201csweet spot\u201d value for that application, however this would cause inefficiencies as pictured in the diagram below. Here we have set the request value to 2 vCPU, however the average utilization of these pods runs only 1 CPU most of the time. This setting would cause us to waste 50% of our CPU cycles, which would be unacceptable.

This bring us to the complex answer to problem. Container utilization cannot be thought of in a vacuum; one must take into account the other applications running on the node. In the following example containers that are bursty in nature are mixed in with two low CPU utilization containers that might be memory constrained. In this way we allow the containers to hit their sweet spot without taxing the node.

The important concept to take away from all this is that using Kubernetes scheduler concept of cores to understand Linux container performance can lead to poor decision making as they are not related.

Tip

Linux CFS has its strong points. This is especially true for I/O based workloads. However, if your application uses full cores without sidecars, and has no I/O requirements, CPU pinning can remove a great deal of complexity from this process and is encouraged with those caveats.

"},{"location":"scalability/docs/node_efficiency/#utilization-vs-saturation","title":"Utilization vs. Saturation","text":"

A common mistake in application scaling is only using CPU utilization for your scaling metric. In complex applications this is almost always a poor indicator that an application is actually saturated with requests. In the example on the left, we see all of our requests are actually hitting the web server, so CPU utilization is tracking well with saturation.

In real world applications, it\u2019s likely that some of those requests will be getting serviced by a database layer or an authentication layer, etc. In this more common case, notice CPU is not tracking with saturation as the request is being serviced by other entities. In this case CPU is a very poor indicator for saturation.

Using the wrong metric in application performance is the number one reason for unnecessary and unpredictable scaling in Kubernetes. Great care must be taken in picking the correct saturation metric for the type of application that you're using. It is important to note that there is not a one size fits all recommendation that can be given. Depending on the language used and the type of application in question, there is a diverse set of metrics for saturation.

We might think this problem is only with CPU Utilization, however other common metrics such as request per second can also fall into the exact same problem as discussed above. Notice the request can also go to DB layers, auth layers, not being directly serviced by our web server, thus it\u2019s a poor metric for true saturation of the web server itself.

Unfortunately there are no easy answers when it comes to picking the right saturation metric. Here are some guidelines to take into consideration:

  • Understand your language runtime - languages with multiple OS threads will react differently than single threaded applications, thus impacting the node differently.
  • Understand the correct vertical scale - how much buffer do you want in your applications vertical scale before scaling a new pod?
  • What metrics truly reflect the saturation of your application - The saturation metric for a Kafka Producer would be quite different than a complex web application.
  • How do all the other applications on the node effect each other - Application performance is not done in a vacuum the other workloads on the node have a major impact.

To close out this section, it would be easy to dismiss the above as overly complex and unnecessary. It can often be the case that we are experiencing an issue but we are unaware of the true nature of the problem because we are looking at the wrong metrics. In the next section we will look at how that could happen.

"},{"location":"scalability/docs/node_efficiency/#node-saturation","title":"Node Saturation","text":"

Now that we have explored application saturation, let\u2019s look at this same concept from a node point of view. Let\u2019s take two CPUs that are 100% utilized to see the difference between utilization vs. saturation.

The vCPU on the left is 100% utilized, however no other tasks are waiting to run on this vCPU, so in a purely theoretical sense, this is quite efficient. Meanwhile, we have 20 single threaded applications waiting to get processed by a vCPU in the second example. All 20 applications now will experience some type of latency while they're waiting their turn to be processed by the vCPU. In other words, the vCPU on the right is saturated.

Not only would we not see this problem if we where just looking at utilization, but we might attribute this latency to something unrelated such as networking which would lead us down the wrong path.

It is important to view saturation metrics, not just utilization metrics when increasing the total number of pods running on a node at any given time as we can easily miss the fact we have over-saturated a node. For this task we can use pressure stall information metrics as seen in the below chart.

PromQL - Stalled I/O

topk(3, ((irate(node_pressure_io_stalled_seconds_total[1m])) * 100))\n

Note

For more on Pressure stall metrics, see https://facebookmicrosites.github.io/psi/docs/overview*

With these metrics we can tell if threads are waiting on CPU, or even if every thread on the box is stalled waiting on resource like memory or I/O. For example, we could see what percentage every thread on the instance was stalled waiting on I/O over the period of 1 min.

topk(3, ((irate(node_pressure_io_stalled_seconds_total[1m])) * 100))\n

Using this metric, we can see in the above chart every thread on the box was stalled 45% of the time waiting on I/O at the high water mark, meaning we were throwing away all of those CPU cycles in that minute. Understanding that this is happening can help us reclaim a significant amount of vCPU time, thus making scaling more efficient.

"},{"location":"scalability/docs/node_efficiency/#hpa-v2","title":"HPA V2","text":"

It is recommended to use the autoscaling/v2 version of the HPA API. The older versions of the HPA API could get stuck scaling in certain edge cases. It was also limited to pods only doubling during each scaling step, which created issues for small deployments that needed to scale rapidly.

Autoscaling/v2 allows us more flexibility to include multiple criteria to scale on and allows us a great deal of flexibility when using custom and external metrics (non K8s metrics).

As an example, we can scaling on the highest of three values (see below). We scale if the average utilization of all the pods are over 50%, if custom metrics the packets per second of the ingress exceed an average of 1,000, or ingress object exceeds 10K request per second.

Note

This is just to show the flexibility of the auto-scaling API, we recommend against overly complex rules that can be difficult to troubleshoot in production.

apiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: php-apache\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: php-apache\n  minReplicas: 1\n  maxReplicas: 10\n  metrics:\n  - type: Resource\n    resource:\n      name: cpu\n      target:\n        type: Utilization\n        averageUtilization: 50\n  - type: Pods\n    pods:\n      metric:\n        name: packets-per-second\n      target:\n        type: AverageValue\n        averageValue: 1k\n  - type: Object\n    object:\n      metric:\n        name: requests-per-second\n      describedObject:\n        apiVersion: networking.k8s.io/v1\n        kind: Ingress\n        name: main-route\n      target:\n        type: Value\n        value: 10k\n

However, we learned the danger of using such metrics for complex web applications. In this case we would be better served by using custom or external metric that accurately reflects the saturation of our application vs. the utilization. HPAv2 allows for this by having the ability to scale according to any metric, however we still need to find and export that metric to Kubernetes for use.

For example, we can look at the active thread queue count in Apache. This often creates a \u201csmoother\u201d scaling profile (more on that term soon). If a thread is active, it doesn\u2019t matter if that thread is waiting on a database layer or servicing a request locally, if all of the applications threads are being used, it\u2019s a great indication that application is saturated.

We can use this thread exhaustion as a signal to create a new pod with a fully available thread pool. This also gives us control over how big a buffer we want in the application to absorb during times of heavy traffic. For example, if we had a total thread pool of 10, scaling at 4 threads used vs. 8 threads used would have a major impact on the buffer we have available when scaling the application. A setting of 4 would make sense for an application that needs to rapidly scale under heavy load, where a setting of 8 would be more efficient with our resources if we had plenty of time to scale due to the number of requests increasing slowly vs. sharply over time.

What do we mean by the term \u201csmooth\u201d when it comes to scaling? Notice the below chart where we are using CPU as a metric. The pods in this deployment are spiking in a short period for from 50 pods, all the way up to 250 pods only to immediately scale down again. This is highly inefficient scaling is the leading cause on churn on clusters.

Notice how after we change to a metric that reflects the correct sweet spot of our application (mid-part of chart), we are able to scale smoothly. Our scaling is now efficient, and our pods are allowed to fully scale with the headroom we provided by adjusting requests settings. Now a smaller group of pods are doing the work the hundreds of pods were doing before. Real world data shows that this is the number one factor in scalability of Kubernetes clusters.

The key takeaway is CPU utilization is only one dimension of both application and node performance. Using CPU utilization as a sole health indicator for our nodes and applications creates problems in scaling, performance and cost which are all tightly linked concepts. The more performant the application and nodes are, the less that you need to scale, which in turn lowers your costs.

Finding and using the correct saturation metrics for scaling your particular application also allows you to monitor and alarm on the true bottlenecks for that application. If this critical step is skipped, reports of performance problems will be difficult, if not impossible, to understand.

"},{"location":"scalability/docs/node_efficiency/#setting-cpu-limits","title":"Setting CPU Limits","text":"

To round out this section on misunderstood topics, we will cover CPU limits. In short, limits are metadata associated with the container that has a counter that resets every 100ms. This helps Linux keep track of how many CPU resources are used node-wide by a specific container in a 100ms period of time.

A common error with setting limits is assuming that the application is single threaded and only running on it\u2019s \u201cassigned\u201c vCPU. In the above section we learned that CFS doesn\u2019t assign cores, and in reality a container running large thread pools will schedule on all available vCPU\u2019s on the box.

If 64 OS threads are running across 64 available cores (from a Linux node perspective) we will make the total bill of used CPU time in a 100ms period quite large after the time running on all of those 64 cores are added up. Since this might only occur during a garbage collection process it can be quite easy to miss something like this. This is why it is necessary to use metrics to ensure we have the correct usage over time before attempting to set a limit.

Fortunately, we have a way to see exactly how much vCPU is being used by all the threads in a application. We will use the metric container_cpu_usage_seconds_total for this purpose.

Since throttling logic happens every 100ms and this metric is a per second metric, we will PromQL to match this 100ms period. If you would like to dive deep into this PromQL statement work please see the following blog.

PromQL query:

topk(3, max by (pod, container)(rate(container_cpu_usage_seconds_total{image!=\"\", instance=\"$instance\"}[$__rate_interval]))) / 10\n

Once we feel we have the right value, we can put the limit in production. It then becomes necessary to see if our application is being throttled due to something unexpected. We can do this by looking at container_cpu_throttled_seconds_total

topk(3, max by (pod, container)(rate(container_cpu_cfs_throttled_seconds_total{image!=``\"\"``, instance=``\"$instance\"``}[$__rate_interval]))) / 10\n

"},{"location":"scalability/docs/node_efficiency/#memory","title":"Memory","text":"

The memory allocation is another example where it is easy to confuse Kubernetes scheduling behavior for Linux CGroup behavior. This is a more nuanced topic as there have been major changes in the way that CGroup v2 handles memory in Linux and Kubernetes has changed its syntax to reflect this; read this blog for further details.

Unlike CPU requests, memory requests go unused after the scheduling process completes. This is because we can not compress memory in CGroup v1 the same way we can with CPU. That leaves us with just memory limits, which are designed to act as a fail safe for memory leaks by terminating the pod completely. This is an all or nothing style proposition, however we have now been given new ways to address this problem.

First, it is important to understand that setting the right amount of memory for containers is not a straightforward as it appears. The file system in Linux will use memory as a cache to improve performance. This cache will grow over time, and it can be hard to know how much memory is just nice to have for the cache but can be reclaimed without a significant impact to application performance. This often results in misinterpreting memory usage.

Having the ability to \u201ccompress\u201d memory was one of the primary drivers behind CGroup v2. For more history on why CGroup V2 was necessary, please see Chris Down\u2019s presentation at LISA21 where he covers why being unable to set the minimum memory correctly was one of the reasons that drove him to create CGroup v2 and pressure stall metrics.

Fortunately, Kubernetes now has the concept of memory.min and memory.high under requests.memory. This gives us the option of aggressive releasing this cached memory for other containers to use. Once the container hits the memory high limit, the kernel can aggressively reclaim that container\u2019s memory up to the value set at memory.min. Thus giving us more flexibility when a node comes under memory pressure.

The key question becomes, what value to set memory.min to? This is where memory pressure stall metrics come into play. We can use these metrics to detect memory \u201cthrashing\u201d at a container level. Then we can use controllers such as fbtax to detect the correct values for memory.min by looking for this memory thrashing, and dynamically set the memory.min value to this setting.

"},{"location":"scalability/docs/node_efficiency/#summary","title":"Summary","text":"

To sum up the section, it is easy to conflate the following concepts:

  • Utilization and Saturation
  • Linux performance rules with Kubernetes Scheduler logic

Great care must be taken to keep these concepts separated. Performance and scale are linked on a deep level. Unnecessary scaling creates performance problems, which in turn creates scaling problems.

"},{"location":"scalability/docs/quotas/","title":"Known Limits and Service Quotas","text":"

Amazon EKS can be used for a variety of workloads and can interact with a wide range of AWS services, and we have seen customer workloads encounter a similar range of AWS service quotas and other issues that hamper scalability.

Your AWS account has default quotas (an upper limit on the number of each AWS resource your team can request). Each AWS service defines their own quota, and quotas are generally region-specific. You can request increases for some quotas (soft limits), and other quotas cannot be increased (hard limits). You should consider these values when architecting your applications. Consider reviewing these service limits periodically and incorporate them during in your application design.

You can review the usage in your account and open a quota increase request at the AWS Service Quotas console, or using the AWS CLI. Refer to the AWS documentation from the respective AWS Service for more details on the Service Quotas and any further restrictions or notices on their increase.

Note

Amazon EKS Service Quotas lists the service quotas and has links to request increases where available.

"},{"location":"scalability/docs/quotas/#other-aws-service-quotas","title":"Other AWS Service Quotas","text":"

We have seen EKS customers impacted by the quotas listed below for other AWS services. Some of these may only apply to specific use cases or configurations, however you may consider if your solution will encounter any of these as it scales. The Quotas are organized by Service and each Quota has an ID in the format of L-XXXXXXXX you can use to look it up in the AWS Service Quotas console

Service Quota (L-xxxxx) Impact ID (L-xxxxx) default IAM Roles per account Can limit the number of clusters or IRSA roles in an account. L-FE177D64 1,000 IAM OpenId connect providers per account Can limit the number of Clusters per account, OpenID Connect is used by IRSA L-858F3967 100 IAM Role trust policy length Can limit the number of of clusters an IAM role is associated with for IRSA L-C07B4B0D 2,048 VPC Security groups per network interface Can limit the control or connectivity of the networking for your cluster L-2AFB9258 5 VPC IPv4 CIDR blocks per VPC Can limit the number of EKS Worker Nodes L-83CA0A9D 5 VPC Routes per route table Can limit the control or connectivity of the networking for your cluster L-93826ACB 50 VPC Active VPC peering connections per VPC Can limit the control or connectivity of the networking for your cluster L-7E9ECCDB 50 VPC Inbound or outbound rules per security group. Can limit the control or connectivity of the networking for your cluster, some controllers in EKS create new rules L-0EA8095F 50 VPC VPCs per Region Can limit the number of Clusters per account or the control or connectivity of the networking for your cluster L-F678F1CE 5 VPC Internet gateways per Region Can limit the number of Clusters per account or the control or connectivity of the networking for your cluster L-A4707A72 5 VPC Network interfaces per Region Can limit the number of EKS Worker nodes, or Impact EKS control plane scaling/update activities. L-DF5E4CA3 5,000 VPC Network Address Usage Can limit the number of Clusters per account or the control or connectivity of the networking for your cluster L-BB24F6E5 64,000 VPC Peered Network Address Usage Can limit the number of Clusters per account or the control or connectivity of the networking for your cluster L-CD17FD4B 128,000 ELB Listeners per Network Load Balancer Can limit the control of traffic ingress to the cluster. L-57A373D6 50 ELB Target Groups per Region Can limit the control of traffic ingress to the cluster. L-B22855CB 3,000 ELB Targets per Application Load Balancer Can limit the control of traffic ingress to the cluster. L-7E6692B2 1,000 ELB Targets per Network Load Balancer Can limit the control of traffic ingress to the cluster. L-EEF1AD04 3,000 ELB Targets per Availability Zone per Network Load Balancer Can limit the control of traffic ingress to the cluster. L-B211E961 500 ELB Targets per Target Group per Region Can limit the control of traffic ingress to the cluster. L-A0D0B863 1,000 ELB Application Load Balancers per Region Can limit the control of traffic ingress to the cluster. L-53DA6B97 50 ELB Classic Load Balancers per Region Can limit the control of traffic ingress to the cluster. L-E9E9831D 20 ELB Network Load Balancers per Region Can limit the control of traffic ingress to the cluster. L-69A177A2 50 EC2 Running On-Demand Standard (A, C, D, H, I, M, R, T, Z) instances (as a maximum vCPU count) Can limit the number of EKS Worker Nodes L-1216C47A 5 EC2 All Standard (A, C, D, H, I, M, R, T, Z) Spot Instance Requests (as a maximum vCPU count) Can limit the number of EKS Worker Nodes L-34B43A08 5 EC2 EC2-VPC Elastic IPs Can limit the number of NAT GWs (and thus VPCs), which may limit the number of clusters in a region L-0263D0A3 5 EBS Snapshots per Region Can limit the backup strategy for stateful workloads L-309BACF6 100,000 EBS Storage for General Purpose SSD (gp3) volumes, in TiB Can limit the number of EKS Worker Nodes, or PersistentVolume storage L-7A658B76 50 EBS Storage for General Purpose SSD (gp2) volumes, in TiB Can limit the number of EKS Worker Nodes, or PersistentVolume storage L-D18FCD1D 50 ECR Registered repositories Can limit the number of workloads in your clusters L-CFEB8E8D 10,000 ECR Images per repository Can limit the number of workloads in your clusters L-03A36CE1 10,000 SecretsManager Secrets per Region Can limit the number of workloads in your clusters L-2F66C23C 500,000"},{"location":"scalability/docs/quotas/#aws-request-throttling","title":"AWS Request Throttling","text":"

AWS services also implement request throttling to ensure that they remain performant and available for all customers. Simliar to Service Quotas, each AWS service maintains their own request throttling thresholds. Consider reviewing the respective AWS Service documentation if your workloads will need to quickly issue a large number of API calls or if you notice request throttling errors in your application.

EC2 API requests around provisioning EC2 network interfaces or IP addresses can encounter request throttling in large clusters or when clusters scale drastically. The table below shows some of the API actions that we have seen customers encounter request throttling from. You can review the EC2 rate limit defaults and the steps to request a rate limit increase in the EC2 documentation on Rate Throttling.

Mutating Actions Read-only Actions AssignPrivateIpAddresses DescribeDhcpOptions AttachNetworkInterface DescribeInstances CreateNetworkInterface DescribeNetworkInterfaces DeleteNetworkInterface DescribeSecurityGroups DeleteTags DescribeTags DetachNetworkInterface DescribeVpcs ModifyNetworkInterfaceAttribute DescribeVolumes UnassignPrivateIpAddresses"},{"location":"scalability/docs/quotas/#other-known-limits","title":"Other Known Limits","text":"
  • Route 53 DNS resolvers are limited to 1024 Packets per second. This limit can be encountered when DNS traffic from a large cluster is funneled through a small number of CoreDNS Pod replicas. Scaling CoreDNS and optimizing DNS behavior can avoid timeouts on DNS lookups.

    • Route 53 also has a fairly low rate limit of 5 requests per second to the Route 53 API. If you have a large number of domains to update with a project like External DNS you may see rate throttling and delays in updating domains.
  • Some Nitro instance types have a volume attachment limit of 28 that is shared between Amazon EBS volumes, network interfaces, and NVMe instance store volumes. If your workloads are mounting numerous EBS volumes you may encounter limits to the pod density you can achieve with these instance types

  • There is a maximum number of connections that can be tracked per Ec2 instance. If your workloads are handling a large number of connections you may see communication failures or errors because this maximum has been hit. You can use the conntrack_allowance_available and conntrack_allowance_exceeded network performance metrics to monitor the number of tracked connections on your EKS worker nodes.

  • In EKS environment, etcd storage limit is 8 GiB as per upstream guidance. Please monitor metric etcd_db_total_size_in_bytes to track etcd db size. You can refer to alert rules etcdBackendQuotaLowSpace and etcdExcessiveDatabaseGrowth to setup this monitoring.

"},{"location":"scalability/docs/scaling_theory/","title":"Kubernetes Scaling Theory","text":""},{"location":"scalability/docs/scaling_theory/#nodes-vs-churn-rate","title":"Nodes vs. Churn Rate","text":"

Often when we discuss the scalability of Kubernetes, we do so in terms of how many nodes there are in a single cluster. Interestingly, this is seldom the most useful metric for understanding scalability. For example, a 5,000 node cluster with a large but fixed number of pods would not put a great deal of stress on the control plane after the initial setup. However, if we took a 1,000 node cluster and tried creating 10,000 short lived jobs in less than a minute, it would put a great deal of sustained pressure on the control plane.

Simply using the number of nodes to understand scaling can be misleading. It\u2019s better to think in terms of the rate of change that occurs within a specific period of time (let\u2019s use a 5 minute interval for this discussion, as this is what Prometheus queries typically use by default). Let\u2019s explore why framing the problem in terms of the rate of change can give us a better idea of what to tune to achieve our desired scale.

"},{"location":"scalability/docs/scaling_theory/#thinking-in-queries-per-second","title":"Thinking in Queries Per Second","text":"

Kubernetes has a number of protection mechanisms for each component - the Kubelet, Scheduler, Kube Controller Manager, and API server - to prevent overwhelming the next link in the Kubernetes chain. For example, the Kubelet has a flag to throttle calls to the API server at a certain rate. These protection mechanisms are generally, but not always, expressed in terms of queries allowed on a per second basis or QPS.

Great care must be taken when changing these QPS settings. Removing one bottleneck, such as the queries per second on a Kubelet will have an impact on other down stream components. This can and will overwhelm the system above a certain rate, so understanding and monitoring each part of the service chain is key to successfully scaling workloads on Kubernetes.

Note

The API server has a more complex system with introduction of API Priority and Fairness which we will discuss separately.

Note

Caution, some metrics seem like the right fit but are in fact measuring something else. As an example, kubelet_http_inflight_requests relates to just the metrics server in Kubelet, not the number of requests from Kubelet to apiserver requests. This could cause us to misconfigure the QPS flag on the Kubelet. A query on audit logs for a particular Kubelet would be a more reliable way to check metrics.

"},{"location":"scalability/docs/scaling_theory/#scaling-distributed-components","title":"Scaling Distributed Components","text":"

Since EKS is a managed service, let\u2019s split the Kubernetes components into two categories: AWS managed components which include etcd, Kube Controller Manager, and the Scheduler (on the left part of diagram), and customer configurable components such as the Kubelet, Container Runtime, and the various operators that call AWS APIs such as the Networking and Storage drivers (on the right part of diagram). We leave the API server in the middle even though it is AWS managed, as the settings for API Priority and Fairness can be configured by customers.

"},{"location":"scalability/docs/scaling_theory/#upstream-and-downstream-bottlenecks","title":"Upstream and Downstream Bottlenecks","text":"

As we monitor each service, it\u2019s important to look at metrics in both directions to look for bottlenecks. Let\u2019s learn how to do this by using Kubelet as an example. Kubelet talks both to the API server and the container runtime; how and what do we need to monitor to detect whether either component is experiencing an issue?

"},{"location":"scalability/docs/scaling_theory/#how-many-pods-per-node","title":"How many Pods per Node","text":"

When we look at scaling numbers, such as how many pods can run on a node, we could take the 110 pods per node that upstream supports at face value.

Note

https://kubernetes.io/docs/setup/best-practices/cluster-large/

However, your workload is likely more complex than what was tested in a scalability test in Upstream. To ensure we can service the number of pods we want to run in production, let\u2019s make sure that the Kubelet is \u201ckeeping up\u201d with the Containerd runtime.

To oversimplify, the Kubelet is getting the status of the pods from the container runtime (in our case Containerd). What if we had too many pods changing status too quickly? If the rate of change is too high, requests [to the container runtime] can timeout.

Note

Kubernetes is constantly evolving, this subsystem is currently undergoing changes. https://github.com/kubernetes/enhancements/issues/3386

In the graph above, we see a flat line indicating we have just hit the timeout value for the pod lifecycle event generation duration metric. If you would like to see this in your own cluster you could use the following PromQL syntax.

increase(kubelet_pleg_relist_duration_seconds_bucket{instance=\"$instance\"}[$__rate_interval])\n

If we witness this timeout behavior, we know we pushed the node over the limit it was capable of. We need to fix the cause of the timeout before proceeding further. This could be achieved by reducing the number of pods per node, or looking for errors that might be causing a high volume of retries (thus effecting the churn rate). The important take-away is that metrics are the best way to understand if a node is able to handle the churn rate of the pods assigned vs. using a fixed number.

"},{"location":"scalability/docs/scaling_theory/#scale-by-metrics","title":"Scale by Metrics","text":"

While the concept of using metrics to optimize systems is an old one, it\u2019s often overlooked as people begin their Kubernetes journey. Instead of focusing on specific numbers (i.e. 110 pods per node), we focus our efforts on finding the metrics that help us find bottlenecks in our system. Understanding the right thresholds for these metrics can give us a high degree of confidence our system is optimally configured.

"},{"location":"scalability/docs/scaling_theory/#the-impact-of-changes","title":"The Impact of Changes","text":"

A common pattern that could get us into trouble is focusing on the first metric or log error that looks suspect. When we saw that the Kubelet was timing out earlier, we could try random things, such as increasing the per second rate that the Kubelet is allowed to send, etc. However, it is wise to look at the whole picture of everything downstream of the error we find first. Make each change with purpose and backed by data.

Downstream of the Kubelet would be the Containerd runtime (pod errors), DaemonSets such as the storage driver (CSI) and the network driver (CNI) that talk to the EC2 API, etc.

Let\u2019s continue our earlier example of the Kubelet not keeping up with the runtime. There are a number of points where we could bin pack a node so densely that it triggers errors.

When designing the right node size for our workloads these are easy-to-overlook signals that might be putting unnecessary pressure on the system thus limiting both our scale and performance.

"},{"location":"scalability/docs/scaling_theory/#the-cost-of-unnecessary-errors","title":"The Cost of Unnecessary Errors","text":"

Kubernetes controllers excel at retrying when error conditions arise, however this comes at a cost. These retries can increase the pressure on components such as the Kube Controller Manager. It is an important tenant of scale testing to monitor for such errors.

When fewer errors are occurring, it is easier spot issues in the system. By periodically ensuring that our clusters are error free before major operations (such as upgrades) we can simplify troubleshooting logs when unforeseen events happen.

"},{"location":"scalability/docs/scaling_theory/#expanding-our-view","title":"Expanding Our View","text":"

In large scale clusters with 1,000\u2019s of nodes we don\u2019t want to look for bottlenecks individually. In PromQL we can find the highest values in a data set using a function called topk; K being a variable we place the number of items we want. Here we use three nodes to get an idea whether all of the Kubelets in the cluster are saturated. We have been looking at latency up to this point, now let\u2019s see if the Kubelet is discarding events.

topk(3, increase(kubelet_pleg_discard_events{}[$__rate_interval]))\n

Breaking this statement down.

  • We use the Grafana variable $__rate_interval to ensure it gets the four samples it needs. This bypasses a complex topic in monitoring with a simple variable.
  • topk give us just the top results and the number 3 limits those results to three. This is a useful function for cluster wide metrics.
  • {} tell us there are no filters, normally you would put the job name of whatever the scraping rule, however since these names vary we will leave it blank.
"},{"location":"scalability/docs/scaling_theory/#splitting-the-problem-in-half","title":"Splitting the Problem in Half","text":"

To address a bottleneck in the system, we will take the approach of finding a metric that shows us there is a problem upstream or downstream as this allows us to split the problem in half. It will also be a core tenet of how we display our metrics data.

A good place to start with this process is the API server, as it allow us to see if there\u2019s a problem with a client application or the Control Plane.

"},{"location":"scalability/docs/workloads/","title":"Workloads","text":"

Workloads have an impact on how large your cluster can scale. Workloads that use the Kubernetes APIs heavily will limit the total amount of workloads you can have in a single cluster, but there are some defaults you can change to help reduce the load.

Workloads in a Kubernetes cluster have access to features that integrate with the Kubernetes API (e.g. Secrets and ServiceAccounts), but these features are not always required and should be disabled if they\u2019re not being used. Limiting workload access and dependence on the Kubernetes control plane will increase the number of workloads you can run in the cluster and improve the security of your clusters by removing unnecessary access to workloads and implementing least privilege practices. Please read the security best practices for more information.

"},{"location":"scalability/docs/workloads/#use-ipv6-for-pod-networking","title":"Use IPv6 for pod networking","text":"

You cannot transition a VPC from IPv4 to IPv6 so enabling IPv6 before provisioning a cluster is important. If you enable IPv6 in a VPC it does not mean you have to use it and if your pods and services use IPv6 you can still route traffic to and from IPv4 addresses. Please see the EKS networking best practices for more information.

Using IPv6 in your cluster avoids some of the most common cluster and workload scaling limits. IPv6 avoids IP address exhaustion where pods and nodes cannot be created because no IP address is available. It also has per node performance improvements because pods receive IP addresses faster by reducing the number of ENI attachments per node. You can achieve similar node performance by using IPv4 prefix mode in the VPC CNI, but you still need to make sure you have enough IP addresses available in the VPC.

"},{"location":"scalability/docs/workloads/#limit-number-of-services-per-namespace","title":"Limit number of services per namespace","text":"

The maximum number of services in a namespaces is 5,000 and the maximum number of services in a cluster is 10,000. To help organize workloads and services, increase performance, and to avoid cascading impact for namespace scoped resources we recommend limiting the number of services per namespace to 500.

The number of IP tables rules that are created per node with kube-proxy grows with the total number of services in the cluster. Generating thousands of IP tables rules and routing packets through those rules have a performance impact on the nodes and add network latency.

Create Kubernetes namespaces that encompass a single application environment so long as the number of services per namespace is under 500. This will keep service discovery small enough to avoid service discovery limits and can also help you avoid service naming collisions. Applications environments (e.g. dev, test, prod) should use separate EKS clusters instead of namespaces.

"},{"location":"scalability/docs/workloads/#understand-elastic-load-balancer-quotas","title":"Understand Elastic Load Balancer Quotas","text":"

When creating your services consider what type of load balancing you will use (e.g. Network Load Balancer (NLB) or Application Load Balancer (ALB)). Each load balancer type provides different functionality and have different quotas. Some of the default quotas can be adjusted, but there are some quota maximums which cannot be changed. To view your account quotas and usage view the Service Quotas dashboard in the AWS console.

For example, the default ALB targets is 1000. If you have a service with more than 1000 endpoints you will need to increase the quota or split the service across multiple ALBs or use Kubernetes Ingress. The default NLB targets is 3000, but is limited to 500 targets per AZ. If your cluster runs more than 500 pods for an NLB service you will need to use multiple AZs or request a quota limit increase.

An alternative to using a load balancer coupled to a service is to use an ingress controller. The AWS Load Balancer controller can create ALBs for ingress resources, but you may consider running a dedicated controller in your cluster. An in-cluster ingress controller allows you to expose multiple Kubernetes services from a single load balancer by running a reverse proxy inside your cluster. Controllers have different features such as support for the Gateway API which may have benefits depending on how many and how large your workloads are.

"},{"location":"scalability/docs/workloads/#use-route-53-global-accelerator-or-cloudfront","title":"Use Route 53, Global Accelerator, or CloudFront","text":"

To make a service using multiple load balancers available as a single endpoint you need to use Amazon CloudFront, AWS Global Accelerator, or Amazon Route 53 to expose all of the load balancers as a single, customer facing endpoint. Each options has different benefits and can be used separately or together depending on your needs.

Route 53 can expose multiple load balancers under a common name and can send traffic to each of them based on the weight assigned. You can read more about DNS weights in the documentation and you can read how to implement them with the Kubernetes external DNS controller in the AWS Load Balancer Controller documentation.

Global Accelerator can route workloads to the nearest region based on request IP address. This may be useful for workloads that are deployed to multiple regions, but it does not improve routing to a single cluster in a single region. Using Route 53 in combination with the Global Accelerator has additional benefits such as health checking and automatic failover if an AZ is not available. You can see an example of using Global Accelerator with Route 53 in this blog post.

CloudFront can be use with Route 53 and Global Accelerator or by itself to route traffic to multiple destinations. CloudFront caches assets being served from the origin sources which may reduce bandwidth requirements depending on what you are serving.

"},{"location":"scalability/docs/workloads/#use-endpointslices-instead-of-endpoints","title":"Use EndpointSlices instead of Endpoints","text":"

When discovering pods that match a service label you should use EndpointSlices instead of Endpoints. Endpoints were a simple way to expose services at small scales but large services that automatically scale or have updates causes a lot of traffic on the Kubernetes control plane. EndpointSlices have automatic grouping which enable things like topology aware hints.

Not all controllers use EndpointSlices by default. You should verify your controller settings and enable it if needed. For the AWS Load Balancer Controller you should enable the --enable-endpoint-slices optional flag to use EndpointSlices.

"},{"location":"scalability/docs/workloads/#use-immutable-and-external-secrets-if-possible","title":"Use immutable and external secrets if possible","text":"

The kubelet keeps a cache of the current keys and values for the Secrets that are used in volumes for pods on that node. The kubelet sets a watch on the Secrets to detect changes. As the cluster scales, the growing number of watches can negatively impact the API server performance.

There are two strategies to reduce the number of watches on Secrets:

  • For applications that don\u2019t need access to Kubernetes resources, you can disable auto-mounting service account secrets by setting automountServiceAccountToken: false
  • If your application\u2019s secrets are static and will not be modified in the future, mark the secret as immutable. The kubelet does not maintain an API watch for immutable secrets.

To disable automatically mounting a service account to pods you can use the following setting in your workload. You can override these settings if specific workloads need a service account.

apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: app\nautomountServiceAccountToken: true\n

Monitor the number of secrets in the cluster before it exceeds the limit of 10,000. You can see a total count of secrets in a cluster with the following command. You should monitor this limit through your cluster monitoring tooling.

kubectl get secrets -A | wc -l\n

You should set up monitoring to alert a cluster admin before this limit is reached. Consider using external secrets management options such as AWS Key Management Service (AWS KMS) or Hashicorp Vault with the Secrets Store CSI driver.

"},{"location":"scalability/docs/workloads/#limit-deployment-history","title":"Limit Deployment history","text":"

Pods can be slow when creating, updating, or deleting because old objects are still tracked in the cluster. You can reduce the revisionHistoryLimit of deployments to cleanup older ReplicaSets which will lower to total amount of objects tracked by the Kubernetes Controller Manager. The default history limit for Deployments in 10.

If your cluster creates a lot of job objects through CronJobs or other mechanisms you should use the ttlSecondsAfterFinished setting to automatically clean up old pods in the cluster. This will remove successfully executed jobs from the job history after a specified amount of time.

"},{"location":"scalability/docs/workloads/#disable-enableservicelinks-by-default","title":"Disable enableServiceLinks by default","text":"

When a Pod runs on a Node, the kubelet adds a set of environment variables for each active Service. Linux processes have a maximum size for their environment which can be reached if you have too many services in your namespace. The number of services per namespace should not exceed 5,000. After this, the number of service environment variables outgrows shell limits, causing Pods to crash on startup.

There are other reasons pods should not use service environment variables for service discovery. Environment variable name clashes, leaking service names, and total environment size are a few. You should use CoreDNS for discovering service endpoints.

"},{"location":"scalability/docs/workloads/#limit-dynamic-admission-webhooks-per-resource","title":"Limit dynamic admission webhooks per resource","text":"

Dynamic Admission Webhooks include admission webhooks and mutating webhooks. They are API endpoints not part of the Kubernetes Control Plane that are called in sequence when a resource is sent to the Kubernetes API. Each webhook has a default timeout of 10 seconds and can increase the amount of time an API request takes if you have multiple webhooks or any of them timeout.

Make sure your webhooks are highly available\u2014especially during an AZ incident\u2014and the failurePolicy is set properly to reject the resource or ignore the failure. Do not call webhooks when not needed by allowing --dry-run kubectl commands to bypass the webhook.

apiVersion: admission.k8s.io/v1\nkind: AdmissionReview\nrequest:\n  dryRun: False\n

Mutating webhooks can modify resources in frequent succession. If you have 5 mutating webhooks and deploy 50 resources etcd will store all versions of each resource until compaction runs\u2014every 5 minutes\u2014to remove old versions of modified resources. In this scenario when etcd removes superseded resources there will be 200 resource version removed from etcd and depending on the size of the resources may use considerable space on the etcd host until defragmentation runs every 15 minutes.

This defragmentation may cause pauses in etcd which could have other affects on the Kubernetes API and controllers. You should avoid frequent modification of large resources or modifying hundreds of resources in quick succession.

"},{"location":"scalability/docs/workloads/#compare-workloads-across-multiple-clusters","title":"Compare workloads across multiple clusters","text":"

If you have two clusters that should have similar performance but do not, try comparing the metrics to identify the reason.

For example, comparing cluster latency is a common issue. This is usually caused by difference in the volume of API requests. You can run the following CloudWatch LogInsight query to understand the difference.

filter @logStream like \"kube-apiserver-audit\"\n| stats count(*) as cnt by objectRef.apiGroup, objectRef.apiVersion, objectRef.resource, userAgent, verb, responseStatus.code\n| sort cnt desc\n| limit 1000\n

You can add additional filters to narrow it down. e.g. focusing on all list request from foo.

filter @logStream like \"kube-apiserver-audit\"\n| filter verb = \"list\"\n| filter user.username like \"foo\"\n| stats count(*) as cnt by objectRef.apiGroup, objectRef.apiVersion, objectRef.resource, responseStatus.code\n| sort cnt desc\n| limit 1000\n
"},{"location":"security/docs/","title":"Amazon EKS Best Practices Guide for Security","text":"

This guide provides advice about protecting information, systems, and assets that are reliant on EKS while delivering business value through risk assessments and mitigation strategies. The guidance herein is part of a series of best practices guides that AWS is publishing to help customers implement EKS in accordance with best practices. Guides for Performance, Operational Excellence, Cost Optimization, and Reliability will be available in the coming months.

"},{"location":"security/docs/#how-to-use-this-guide","title":"How to use this guide","text":"

This guide is meant for security practitioners who are responsible for implementing and monitoring the effectiveness of security controls for EKS clusters and the workloads they support. The guide is organized into different topic areas for easier consumption. Each topic starts with a brief overview, followed by a list of recommendations and best practices for securing your EKS clusters. The topics do not need to be read in a particular order.

"},{"location":"security/docs/#understanding-the-shared-responsibility-model","title":"Understanding the Shared Responsibility Model","text":"

Security and compliance are considered shared responsibilities when using a managed service like EKS. Generally speaking, AWS is responsible for security \"of\" the cloud whereas you, the customer, are responsible for security \"in\" the cloud. With EKS, AWS is responsible for managing of the EKS managed Kubernetes control plane. This includes the Kubernetes control plane nodes, the ETCD database, and other infrastructure necessary for AWS to deliver a secure and reliable service. As a consumer of EKS, you are largely responsible for the topics in this guide, e.g. IAM, pod security, runtime security, network security, and so forth.

When it comes to infrastructure security, AWS will assume additional responsibilities as you move from self-managed workers, to managed node groups, to Fargate. For example, with Fargate, AWS becomes responsible for securing the underlying instance/runtime used to run your Pods.

AWS will also assume responsibility of keeping the EKS optimized AMI up to date with Kubernetes patch versions and security patches. Customers using Managed Node Groups (MNG) are responsible for upgrading their Nodegroups to the latest AMI via EKS API, CLI, Cloudformation or AWS Console. Also unlike Fargate, MNGs will not automatically scale your infrastructure/cluster. That can be handled by the cluster-autoscaler or other technologies such as Karpenter, native AWS autoscaling, SpotInst's Ocean, or Atlassian's Escalator.

Before designing your system, it is important to know where the line of demarcation is between your responsibilities and the provider of the service (AWS).

For additional information about the shared responsibility model, see https://aws.amazon.com/compliance/shared-responsibility-model/

"},{"location":"security/docs/#introduction","title":"Introduction","text":"

There are several security best practice areas that are pertinent when using a managed Kubernetes service like EKS:

  • Identity and Access Management
  • Pod Security
  • Runtime Security
  • Network Security
  • Multi-tenancy
  • Multi Account for Multi-tenancy
  • Detective Controls
  • Infrastructure Security
  • Data Encryption and Secrets Management
  • Regulatory Compliance
  • Incident Response and Forensics
  • Image Security

As part of designing any system, you need to think about its security implications and the practices that can affect your security posture. For example, you need to control who can perform actions against a set of resources. You also need the ability to quickly identify security incidents, protect your systems and services from unauthorized access, and maintain the confidentiality and integrity of data through data protection. Having a well-defined and rehearsed set of processes for responding to security incidents will improve your security posture too. These tools and techniques are important because they support objectives such as preventing financial loss or complying with regulatory obligations.

AWS helps organizations achieve their security and compliance goals by offering a rich set of security services that have evolved based on feedback from a broad set of security conscious customers. By offering a highly secure foundation, customers can spend less time on \u201cundifferentiated heavy lifting\u201d and more time on achieving their business objectives.

"},{"location":"security/docs/#feedback","title":"Feedback","text":"

This guide is being released on GitHub so as to collect direct feedback and suggestions from the broader EKS/Kubernetes community. If you have a best practice that you feel we ought to include in the guide, please file an issue or submit a PR in the GitHub repository. Our intention is to update the guide periodically as new features are added to the service or when a new best practice evolves.

"},{"location":"security/docs/#further-reading","title":"Further Reading","text":"

Kubernetes Security Whitepaper, sponsored by the Security Audit Working Group, this Whitepaper describes key aspects of the Kubernetes attack surface and security architecture with the aim of helping security practitioners make sound design and implementation decisions.

The CNCF published also a white paper on cloud native security. The paper examines how the technology landscape has evolved and advocates for the adoption of security practices that align with DevOps processes and agile methodologies.

"},{"location":"security/docs/#tools-and-resources","title":"Tools and resources","text":"

Amazon EKS Security Immersion Workshop

"},{"location":"security/docs/compliance/","title":"Compliance","text":"

Compliance is a shared responsibility between AWS and the consumers of its services. Generally speaking, AWS is responsible for \u201csecurity of the cloud\u201d whereas its users are responsible for \u201csecurity in the cloud.\u201d The line that delineates what AWS and its users are responsible for will vary depending on the service. For example, with Fargate, AWS is responsible for managing the physical security of its data centers, the hardware, the virtual infrastructure (Amazon EC2), and the container runtime (Docker). Users of Fargate are responsible for securing the container image and their application. Knowing who is responsible for what is an important consideration when running workloads that must adhere to compliance standards.

The following table shows the compliance programs with which the different container services conform.

Compliance Program Amazon ECS Orchestrator Amazon EKS Orchestrator ECS Fargate Amazon ECR PCI DSS Level 1 1 1 1 1 HIPAA Eligible 1 1 1 1 SOC I 1 1 1 1 SOC II 1 1 1 1 SOC III 1 1 1 1 ISO 27001:2013 1 1 1 1 ISO 9001:2015 1 1 1 1 ISO 27017:2015 1 1 1 1 ISO 27018:2019 1 1 1 1 IRAP 1 1 1 1 FedRAMP Moderate (East/West) 1 1 0 1 FedRAMP High (GovCloud) 1 1 0 1 DOD CC SRG 1 DISA Review (IL5) 0 1 HIPAA BAA 1 1 1 1 MTCS 1 1 0 1 C5 1 1 0 1 K-ISMS 1 1 0 1 ENS High 1 1 0 1 OSPAR 1 1 0 1 HITRUST CSF 1 1 1 1

Compliance status changes over time. For the latest status, always refer to https://aws.amazon.com/compliance/services-in-scope/.

For further information about cloud accreditation models and best practices, see the AWS whitepaper, Accreditation Models for Secure Cloud Adoption

"},{"location":"security/docs/compliance/#shifting-left","title":"Shifting Left","text":"

The concept of shifting left involves catching policy violations and errors earlier in the software development lifecycle. From a security perspective, this can be very beneficial. A developer, for example, can fix issues with their configuration before their application is deployed to the cluster. Catching mistakes like this earlier will help prevent configurations that violate your policies from being deployed.

"},{"location":"security/docs/compliance/#policy-as-code","title":"Policy as Code","text":"

Policy can be thought of as a set of rules for governing behaviors, i.e. behaviors that are allowed or those that are prohibited. For example, you may have a policy that says that all Dockerfiles should include a USER directive that causes the container to run as a non-root user. As a document, a policy like this can be hard to discover and enforce. It may also become outdated as your requirements change. With Policy as Code (PaC) solutions, you can automate security, compliance, and privacy controls that detect, prevent, reduce, and counteract known and persistent threats. Furthermore, they give you mechanism to codify your policies and manage them as you do other code artifacts. The benefit of this approach is that you can reuse your DevOps and GitOps strategies to manage and consistently apply policies across fleets of Kubernetes clusters. Please refer to Pod Security for information about PaC options and the future of PSPs.

"},{"location":"security/docs/compliance/#use-policy-as-code-tools-in-pipelines-to-detect-violations-before-deployment","title":"Use policy-as-code tools in pipelines to detect violations before deployment","text":"
  • OPA is an open source policy engine that's part of the CNCF. It's used for making policy decisions and can be run a variety of different ways, e.g. as a language library or a service. OPA policies are written in a Domain Specific Language (DSL) called Rego. While it is often run as part of a Kubernetes Dynamic Admission Controller as the Gatekeeper project, OPA can also be incorporated into your CI/CD pipeline. This allows developers to get feedback about their configuration earlier in the release cycle which can subsequently help them resolve issues before they get to production. A collection of common OPA policies can be found in the GitHub repository for this project.
  • Conftest is built on top of OPA and it provides a developer focused experience for testing Kubernetes configuration.
  • Kyverno is a policy engine designed for Kubernetes. With Kyverno, policies are managed as Kubernetes resources and no new language is required to write policies. This allows using familiar tools such as kubectl, git, and kustomize to manage policies. Kyverno policies can validate, mutate, and generate Kubernetes resources plus ensure OCI image supply chain security. The Kyverno CLI can be used to test policies and validate resources as part of a CI/CD pipeline. All the Kyverno community policies can be found on the Kyverno website, and for examples using the Kyverno CLI to write tests in pipelines, see the policies repository.
"},{"location":"security/docs/compliance/#tools-and-resources","title":"Tools and resources","text":"
  • Amazon EKS Security Immersion Workshop - Regulatory Compliance
  • kube-bench
  • docker-bench-security
  • AWS Inspector
  • Kubernetes Security Review A 3rd party security assessment of Kubernetes 1.13.4 (2019)
  • NeuVector by SUSE open source, zero-trust container security platform, provides compliance reporting and custom compliance checks
"},{"location":"security/docs/data/","title":"Data encryption and secrets management","text":""},{"location":"security/docs/data/#encryption-at-rest","title":"Encryption at rest","text":"

There are three different AWS-native storage options you can use with Kubernetes: EBS, EFS, and FSx for Lustre. All three offer encryption at rest using a service managed key or a customer master key (CMK). For EBS you can use the in-tree storage driver or the EBS CSI driver. Both include parameters for encrypting volumes and supplying a CMK. For EFS, you can use the EFS CSI driver, however, unlike EBS, the EFS CSI driver does not support dynamic provisioning. If you want to use EFS with EKS, you will need to provision and configure at-rest encryption for the file system prior to creating a PV. For further information about EFS file encryption, please refer to Encrypting Data at Rest. Besides offering at-rest encryption, EFS and FSx for Lustre include an option for encrypting data in transit. FSx for Lustre does this by default. For EFS, you can add transport encryption by adding the tls parameter to mountOptions in your PV as in this example:

apiVersion: v1\nkind: PersistentVolume\nmetadata:\n  name: efs-pv\nspec:\n  capacity:\n    storage: 5Gi\n  volumeMode: Filesystem\n  accessModes:\n    - ReadWriteOnce\n  persistentVolumeReclaimPolicy: Retain\n  storageClassName: efs-sc\n  mountOptions:\n    - tls\n  csi:\n    driver: efs.csi.aws.com\n    volumeHandle: <file_system_id>\n

The FSx CSI driver supports dynamic provisioning of Lustre file systems. It encrypts data with a service managed key by default, although there is an option to provide your own CMK as in this example:

kind: StorageClass\napiVersion: storage.k8s.io/v1\nmetadata:\n  name: fsx-sc\nprovisioner: fsx.csi.aws.com\nparameters:\n  subnetId: subnet-056da83524edbe641\n  securityGroupIds: sg-086f61ea73388fb6b\n  deploymentType: PERSISTENT_1\n  kmsKeyId: <kms_arn>\n

Attention

As of May 28, 2020 all data written to the ephemeral volume in EKS Fargate pods is encrypted by default using an industry-standard AES-256 cryptographic algorithm. No modifications to your application are necessary as encryption and decryption are handled seamlessly by the service.

"},{"location":"security/docs/data/#encrypt-data-at-rest","title":"Encrypt data at rest","text":"

Encrypting data at rest is considered a best practice. If you're unsure whether encryption is necessary, encrypt your data.

"},{"location":"security/docs/data/#rotate-your-cmks-periodically","title":"Rotate your CMKs periodically","text":"

Configure KMS to automatically rotate your CMKs. This will rotate your keys once a year while saving old keys indefinitely so that your data can still be decrypted. For additional information see Rotating customer master keys

"},{"location":"security/docs/data/#use-efs-access-points-to-simplify-access-to-shared-datasets","title":"Use EFS access points to simplify access to shared datasets","text":"

If you have shared datasets with different POSIX file permissions or want to restrict access to part of the shared file system by creating different mount points, consider using EFS access points. To learn more about working with access points, see https://docs.aws.amazon.com/efs/latest/ug/efs-access-points.html. Today, if you want to use an access point (AP) you'll need to reference the AP in the PV's volumeHandle parameter.

Attention

As of March 23, 2021 the EFS CSI driver supports dynamic provisioning of EFS Access Points. Access points are application-specific entry points into an EFS file system that make it easier to share a file system between multiple pods. Each EFS file system can have up to 120 PVs. See Introducing Amazon EFS CSI dynamic provisioning for additional information.

"},{"location":"security/docs/data/#secrets-management","title":"Secrets management","text":"

Kubernetes secrets are used to store sensitive information, such as user certificates, passwords, or API keys. They are persisted in etcd as base64 encoded strings. On EKS, the EBS volumes for etcd nodes are encrypted with EBS encryption. A pod can retrieve a Kubernetes secrets objects by referencing the secret in the podSpec. These secrets can either be mapped to an environment variable or mounted as volume. For additional information on creating secrets, see https://kubernetes.io/docs/concepts/configuration/secret/.

Caution

Secrets in a particular namespace can be referenced by all pods in the secret's namespace.

Caution

The node authorizer allows the Kubelet to read all of the secrets mounted to the node.

"},{"location":"security/docs/data/#use-aws-kms-for-envelope-encryption-of-kubernetes-secrets","title":"Use AWS KMS for envelope encryption of Kubernetes secrets","text":"

This allows you to encrypt your secrets with a unique data encryption key (DEK). The DEK is then encrypted using a key encryption key (KEK) from AWS KMS which can be automatically rotated on a recurring schedule. With the KMS plugin for Kubernetes, all Kubernetes secrets are stored in etcd in ciphertext instead of plain text and can only be decrypted by the Kubernetes API server. For additional details, see using EKS encryption provider support for defense in depth

"},{"location":"security/docs/data/#audit-the-use-of-kubernetes-secrets","title":"Audit the use of Kubernetes Secrets","text":"

On EKS, turn on audit logging and create a CloudWatch metrics filter and alarm to alert you when a secret is used (optional). The following is an example of a metrics filter for the Kubernetes audit log, {($.verb=\"get\") && ($.objectRef.resource=\"secret\")}. You can also use the following queries with CloudWatch Log Insights:

fields @timestamp, @message\n| sort @timestamp desc\n| limit 100\n| stats count(*) by objectRef.name as secret\n| filter verb=\"get\" and objectRef.resource=\"secrets\"\n

The above query will display the number of times a secret has been accessed within a specific timeframe.

fields @timestamp, @message\n| sort @timestamp desc\n| limit 100\n| filter verb=\"get\" and objectRef.resource=\"secrets\"\n| display objectRef.namespace, objectRef.name, user.username, responseStatus.code\n

This query will display the secret, along with the namespace and username of the user who attempted to access the secret and the response code.

"},{"location":"security/docs/data/#rotate-your-secrets-periodically","title":"Rotate your secrets periodically","text":"

Kubernetes doesn't automatically rotate secrets. If you have to rotate secrets, consider using an external secret store, e.g. Vault or AWS Secrets Manager.

"},{"location":"security/docs/data/#use-separate-namespaces-as-a-way-to-isolate-secrets-from-different-applications","title":"Use separate namespaces as a way to isolate secrets from different applications","text":"

If you have secrets that cannot be shared between applications in a namespace, create a separate namespace for those applications.

"},{"location":"security/docs/data/#use-volume-mounts-instead-of-environment-variables","title":"Use volume mounts instead of environment variables","text":"

The values of environment variables can unintentionally appear in logs. Secrets mounted as volumes are instantiated as tmpfs volumes (a RAM backed file system) that are automatically removed from the node when the pod is deleted.

"},{"location":"security/docs/data/#use-an-external-secrets-provider","title":"Use an external secrets provider","text":"

There are several viable alternatives to using Kubernetes secrets, including AWS Secrets Manager and Hashicorp's Vault. These services offer features such as fine grained access controls, strong encryption, and automatic rotation of secrets that are not available with Kubernetes Secrets. Bitnami's Sealed Secrets is another approach that uses asymmetric encryption to create \"sealed secrets\". A public key is used to encrypt the secret while the private key used to decrypt the secret is kept within the cluster, allowing you to safely store sealed secrets in source control systems like Git. See Managing secrets deployment in Kubernetes using Sealed Secrets for further information.

As the use of external secrets stores has grown, so has need for integrating them with Kubernetes. The Secret Store CSI Driver is a community project that uses the CSI driver model to fetch secrets from external secret stores. Currently, the Driver has support for AWS Secrets Manager, Azure, Vault, and GCP. The AWS provider supports both AWS Secrets Manager and AWS Parameter Store. It can also be configured to rotate secrets when they expire and can synchronize AWS Secrets Manager secrets to Kubernetes Secrets. Synchronization of secrets can be useful when you need to reference a secret as an environment variable instead of reading them from a volume.

Note

When the secret store CSI driver has to fetch a secret, it assumes the IRSA role assigned to the pod that references a secret. The code for this operation can be found here.

For additional information about the AWS Secrets & Configuration Provider (ASCP) refer to the following resources:

  • How to use AWS Secrets Configuration Provider with Kubernetes Secret Store CSI Driver
  • Integrating Secrets Manager secrets with Kubernetes Secrets Store CSI Driver

external-secrets is yet another way to use an external secret store with Kubernetes. Like the CSI Driver, external-secrets works against a variety of different backends, including AWS Secrets Manager. The difference is, rather than retrieving secrets from the external secret store, external-secrets copies secrets from these backends to Kubernetes as Secrets. This lets you manage secrets using your preferred secret store and interact with secrets in a Kubernetes-native way.

"},{"location":"security/docs/data/#tools-and-resources","title":"Tools and resources","text":"
  • Amazon EKS Security Immersion Workshop - Data Encryption and Secrets Management
"},{"location":"security/docs/detective/","title":"Auditing and logging","text":"

Collecting and analyzing [audit] logs is useful for a variety of different reasons. Logs can help with root cause analysis and attribution, i.e. ascribing a change to a particular user. When enough logs have been collected, they can be used to detect anomalous behaviors too. On EKS, the audit logs are sent to Amazon Cloudwatch Logs. The audit policy for EKS is as follows:

apiVersion: audit.k8s.io/v1beta1\nkind: Policy\nrules:\n  # Log full request and response for changes to aws-auth ConfigMap in kube-system namespace\n  - level: RequestResponse\n    namespaces: [\"kube-system\"]\n    verbs: [\"update\", \"patch\", \"delete\"]\n    resources:\n      - group: \"\" # core\n        resources: [\"configmaps\"]\n        resourceNames: [\"aws-auth\"]\n    omitStages:\n      - \"RequestReceived\"\n  # Do not log watch operations performed by kube-proxy on endpoints and services\n  - level: None\n    users: [\"system:kube-proxy\"]\n    verbs: [\"watch\"]\n    resources:\n      - group: \"\" # core\n        resources: [\"endpoints\", \"services\", \"services/status\"]\n  # Do not log get operations performed by kubelet on nodes and their statuses\n  - level: None\n    users: [\"kubelet\"] # legacy kubelet identity\n    verbs: [\"get\"]\n    resources:\n      - group: \"\" # core\n        resources: [\"nodes\", \"nodes/status\"]\n  # Do not log get operations performed by the system:nodes group on nodes and their statuses\n  - level: None\n    userGroups: [\"system:nodes\"]\n    verbs: [\"get\"]\n    resources:\n      - group: \"\" # core\n        resources: [\"nodes\", \"nodes/status\"]\n  # Do not log get and update operations performed by controller manager, scheduler, and endpoint-controller on endpoints in kube-system namespace\n  - level: None\n    users:\n      - system:kube-controller-manager\n      - system:kube-scheduler\n      - system:serviceaccount:kube-system:endpoint-controller\n    verbs: [\"get\", \"update\"]\n    namespaces: [\"kube-system\"]\n    resources:\n      - group: \"\" # core\n        resources: [\"endpoints\"]\n  # Do not log get operations performed by apiserver on namespaces and their statuses/finalizations\n  - level: None\n    users: [\"system:apiserver\"]\n    verbs: [\"get\"]\n    resources:\n      - group: \"\" # core\n        resources: [\"namespaces\", \"namespaces/status\", \"namespaces/finalize\"]\n  # Do not log get and list operations performed by controller manager on metrics.k8s.io resources\n  - level: None\n    users:\n      - system:kube-controller-manager\n    verbs: [\"get\", \"list\"]\n    resources:\n      - group: \"metrics.k8s.io\"\n  # Do not log access to health, version, and swagger non-resource URLs\n  - level: None\n    nonResourceURLs:\n      - /healthz*\n      - /version\n      - /swagger*\n  # Do not log events resources\n  - level: None\n    resources:\n      - group: \"\" # core\n        resources: [\"events\"]\n  # Log request for updates/patches to nodes and pods statuses by kubelet and node problem detector\n  - level: Request\n    users: [\"kubelet\", \"system:node-problem-detector\", \"system:serviceaccount:kube-system:node-problem-detector\"]\n    verbs: [\"update\", \"patch\"]\n    resources:\n      - group: \"\" # core\n        resources: [\"nodes/status\", \"pods/status\"]\n    omitStages:\n      - \"RequestReceived\"\n  # Log request for updates/patches to nodes and pods statuses by system:nodes group\n  - level: Request\n    userGroups: [\"system:nodes\"]\n    verbs: [\"update\", \"patch\"]\n    resources:\n      - group: \"\" # core\n        resources: [\"nodes/status\", \"pods/status\"]\n    omitStages:\n      - \"RequestReceived\"\n  # Log delete collection requests by namespace-controller in kube-system namespace\n  - level: Request\n    users: [\"system:serviceaccount:kube-system:namespace-controller\"]\n    verbs: [\"deletecollection\"]\n    omitStages:\n      - \"RequestReceived\"\n  # Log metadata for secrets, configmaps, and tokenreviews to protect sensitive data\n  - level: Metadata\n    resources:\n      - group: \"\" # core\n        resources: [\"secrets\", \"configmaps\"]\n      - group: authentication.k8s.io\n        resources: [\"tokenreviews\"]\n    omitStages:\n      - \"RequestReceived\"\n  # Log requests for serviceaccounts/token resources\n  - level: Request\n    resources:\n      - group: \"\" # core\n        resources: [\"serviceaccounts/token\"]\n  # Log get, list, and watch requests for various resource groups\n  - level: Request\n    verbs: [\"get\", \"list\", \"watch\"]\n    resources: \n      - group: \"\" # core\n      - group: \"admissionregistration.k8s.io\"\n      - group: \"apiextensions.k8s.io\"\n      - group: \"apiregistration.k8s.io\"\n      - group: \"apps\"\n      - group: \"authentication.k8s.io\"\n      - group: \"authorization.k8s.io\"\n      - group: \"autoscaling\"\n      - group: \"batch\"\n      - group: \"certificates.k8s.io\"\n      - group: \"extensions\"\n      - group: \"metrics.k8s.io\"\n      - group: \"networking.k8s.io\"\n      - group: \"policy\"\n      - group: \"rbac.authorization.k8s.io\"\n      - group: \"scheduling.k8s.io\"\n      - group: \"settings.k8s.io\"\n      - group: \"storage.k8s.io\"\n    omitStages:\n      - \"RequestReceived\"\n  # Default logging level for known APIs to log request and response\n  - level: RequestResponse\n    resources: \n      - group: \"\" # core\n      - group: \"admissionregistration.k8s.io\"\n      - group: \"apiextensions.k8s.io\"\n      - group: \"apiregistration.k8s.io\"\n      - group: \"apps\"\n      - group: \"authentication.k8s.io\"\n      - group: \"authorization.k8s.io\"\n      - group: \"autoscaling\"\n      - group: \"batch\"\n      - group: \"certificates.k8s.io\"\n      - group: \"extensions\"\n      - group: \"metrics.k8s.io\"\n      - group: \"networking.k8s.io\"\n      - group: \"policy\"\n      - group: \"rbac.authorization.k8s.io\"\n      - group: \"scheduling.k8s.io\"\n      - group: \"settings.k8s.io\"\n      - group: \"storage.k8s.io\"\n    omitStages:\n      - \"RequestReceived\"\n  # Default logging level for all other requests to log metadata only\n  - level: Metadata\n    omitStages:\n      - \"RequestReceived\"\n
"},{"location":"security/docs/detective/#recommendations","title":"Recommendations","text":""},{"location":"security/docs/detective/#enable-audit-logs","title":"Enable audit logs","text":"

The audit logs are part of the EKS managed Kubernetes control plane logs that are managed by EKS. Instructions for enabling/disabling the control plane logs, which includes the logs for the Kubernetes API server, the controller manager, and the scheduler, along with the audit log, can be found here, https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html#enabling-control-plane-log-export.

Info

When you enable control plane logging, you will incur costs for storing the logs in CloudWatch. This raises a broader issue about the ongoing cost of security. Ultimately you will have to weigh those costs against the cost of a security breach, e.g. financial loss, damage to your reputation, etc. You may find that you can adequately secure your environment by implementing only some of the recommendations in this guide.

Warning

The maximum size for a CloudWatch Logs entry is 256KB whereas the maximum Kubernetes API request size is 1.5MiB. Log entries greater than 256KB will either be truncated or only include the request metadata.

"},{"location":"security/docs/detective/#utilize-audit-metadata","title":"Utilize audit metadata","text":"

Kubernetes audit logs include two annotations that indicate whether or not a request was authorized authorization.k8s.io/decision and the reason for the decision authorization.k8s.io/reason. Use these attributes to ascertain why a particular API call was allowed.

"},{"location":"security/docs/detective/#create-alarms-for-suspicious-events","title":"Create alarms for suspicious events","text":"

Create an alarm to automatically alert you where there is an increase in 403 Forbidden and 401 Unauthorized responses, and then use attributes like host, sourceIPs, and k8s_user.username to find out where those requests are coming from.

"},{"location":"security/docs/detective/#analyze-logs-with-log-insights","title":"Analyze logs with Log Insights","text":"

Use CloudWatch Log Insights to monitor changes to RBAC objects, e.g. Roles, RoleBindings, ClusterRoles, and ClusterRoleBindings. A few sample queries appear below:

Lists updates to the aws-auth ConfigMap:

fields @timestamp, @message\n| filter @logStream like \"kube-apiserver-audit\"\n| filter verb in [\"update\", \"patch\"]\n| filter objectRef.resource = \"configmaps\" and objectRef.name = \"aws-auth\" and objectRef.namespace = \"kube-system\"\n| sort @timestamp desc\n

Lists creation of new or changes to validation webhooks:

fields @timestamp, @message\n| filter @logStream like \"kube-apiserver-audit\"\n| filter verb in [\"create\", \"update\", \"patch\"] and responseStatus.code = 201\n| filter objectRef.resource = \"validatingwebhookconfigurations\"\n| sort @timestamp desc\n

Lists create, update, delete operations to Roles:

fields @timestamp, @message\n| sort @timestamp desc\n| limit 100\n| filter objectRef.resource=\"roles\" and verb in [\"create\", \"update\", \"patch\", \"delete\"]\n

Lists create, update, delete operations to RoleBindings:

fields @timestamp, @message\n| sort @timestamp desc\n| limit 100\n| filter objectRef.resource=\"rolebindings\" and verb in [\"create\", \"update\", \"patch\", \"delete\"]\n

Lists create, update, delete operations to ClusterRoles:

fields @timestamp, @message\n| sort @timestamp desc\n| limit 100\n| filter objectRef.resource=\"clusterroles\" and verb in [\"create\", \"update\", \"patch\", \"delete\"]\n

Lists create, update, delete operations to ClusterRoleBindings:

fields @timestamp, @message\n| sort @timestamp desc\n| limit 100\n| filter objectRef.resource=\"clusterrolebindings\" and verb in [\"create\", \"update\", \"patch\", \"delete\"]\n

Plots unauthorized read operations against Secrets:

fields @timestamp, @message\n| sort @timestamp desc\n| limit 100\n| filter objectRef.resource=\"secrets\" and verb in [\"get\", \"watch\", \"list\"] and responseStatus.code=\"401\"\n| stats count() by bin(1m)\n

List of failed anonymous requests:

fields @timestamp, @message, sourceIPs.0\n| sort @timestamp desc\n| limit 100\n| filter user.username=\"system:anonymous\" and responseStatus.code in [\"401\", \"403\"]\n
"},{"location":"security/docs/detective/#audit-your-cloudtrail-logs","title":"Audit your CloudTrail logs","text":"

AWS APIs called by pods that are utilizing IAM Roles for Service Accounts (IRSA) are automatically logged to CloudTrail along with the name of the service account. If the name of a service account that wasn't explicitly authorized to call an API appears in the log, it may be an indication that the IAM role's trust policy was misconfigured. Generally speaking, Cloudtrail is a great way to ascribe AWS API calls to specific IAM principals.

"},{"location":"security/docs/detective/#use-cloudtrail-insights-to-unearth-suspicious-activity","title":"Use CloudTrail Insights to unearth suspicious activity","text":"

CloudTrail insights automatically analyzes write management events from CloudTrail trails and alerts you of unusual activity. This can help you identify when there's an increase in call volume on write APIs in your AWS account, including from pods that use IRSA to assume an IAM role. See Announcing CloudTrail Insights: Identify and Response to Unusual API Activity for further information.

"},{"location":"security/docs/detective/#additional-resources","title":"Additional resources","text":"

As the volume of logs increases, parsing and filtering them with Log Insights or another log analysis tool may become ineffective. As an alternative, you might want to consider running Sysdig Falco and ekscloudwatch. Falco analyzes audit logs and flags anomalies or abuse over an extended period of time. The ekscloudwatch project forwards audit log events from CloudWatch to Falco for analysis. Falco provides a set of default audit rules along with the ability to add your own.

Yet another option might be to store the audit logs in S3 and use the SageMaker Random Cut Forest algorithm to anomalous behaviors that warrant further investigation.

"},{"location":"security/docs/detective/#tools-and-resources","title":"Tools and resources","text":"

The following commercial and open source projects can be used to assess your cluster's alignment with established best practices:

  • Amazon EKS Security Immersion Workshop - Detective Controls
  • kubeaudit
  • kube-scan Assigns a risk score to the workloads running in your cluster in accordance with the Kubernetes Common Configuration Scoring System framework
  • kubesec.io
  • polaris
  • Starboard
  • Snyk
  • Kubescape Kubescape is an open source kubernetes security tool that scans clusters, YAML files, and Helm charts. It detects misconfigurations according to multiple frameworks (including NSA-CISA and MITRE ATT&CK\u00ae.)
"},{"location":"security/docs/hosts/","title":"Protecting the infrastructure (hosts)","text":"

Inasmuch as it's important to secure your container images, it's equally important to safeguard the infrastructure that runs them. This section explores different ways to mitigate risks from attacks launched directly against the host. These guidelines should be used in conjunction with those outlined in the Runtime Security section.

"},{"location":"security/docs/hosts/#recommendations","title":"Recommendations","text":""},{"location":"security/docs/hosts/#use-an-os-optimized-for-running-containers","title":"Use an OS optimized for running containers","text":"

Consider using Flatcar Linux, Project Atomic, RancherOS, and Bottlerocket, a special purpose OS from AWS designed for running Linux containers. It includes a reduced attack surface, a disk image that is verified on boot, and enforced permission boundaries using SELinux.

Alternately, use the EKS optimized AMI for your Kubernetes worker nodes. The EKS optimized AMI is released regularly and contains a minimal set of OS packages and binaries necessary to run your containerized workloads.

Please refer Amazon EKS AMI RHEL Build Specification for a sample configuration script which can be used for building a custom Amazon EKS AMI running on Red Hat Enterprise Linux using Hashicorp Packer. This script can be further leveraged to build STIG compliant EKS custom AMIs.

"},{"location":"security/docs/hosts/#keep-your-worker-node-os-updated","title":"Keep your worker node OS updated","text":"

Regardless of whether you use a container-optimized host OS like Bottlerocket or a larger, but still minimalist, Amazon Machine Image like the EKS optimized AMIs, it is best practice to keep these host OS images up to date with the latest security patches.

For the EKS optimized AMIs, regularly check the CHANGELOG and/or release notes channel and automate the rollout of updated worker node images into your cluster.

"},{"location":"security/docs/hosts/#treat-your-infrastructure-as-immutable-and-automate-the-replacement-of-your-worker-nodes","title":"Treat your infrastructure as immutable and automate the replacement of your worker nodes","text":"

Rather than performing in-place upgrades, replace your workers when a new patch or update becomes available. This can be approached a couple of ways. You can either add instances to an existing autoscaling group using the latest AMI as you sequentially cordon and drain nodes until all of the nodes in the group have been replaced with the latest AMI. Alternatively, you can add instances to a new node group while you sequentially cordon and drain nodes from the old node group until all of the nodes have been replaced. EKS managed node groups uses the first approach and will display a message in the console to upgrade your workers when a new AMI becomes available. eksctl also has a mechanism for creating node groups with the latest AMI and for gracefully cordoning and draining pods from nodes groups before the instances are terminated. If you decide to use a different method for replacing your worker nodes, it is strongly recommended that you automate the process to minimize human oversight as you will likely need to replace workers regularly as new updates/patches are released and when the control plane is upgraded.

With EKS Fargate, AWS will automatically update the underlying infrastructure as updates become available. Oftentimes this can be done seamlessly, but there may be times when an update will cause your pod to be rescheduled. Hence, we recommend that you create deployments with multiple replicas when running your application as a Fargate pod.

"},{"location":"security/docs/hosts/#periodically-run-kube-bench-to-verify-compliance-with-cis-benchmarks-for-kubernetes","title":"Periodically run kube-bench to verify compliance with CIS benchmarks for Kubernetes","text":"

kube-bench is an open source project from Aqua that evaluates your cluster against the CIS benchmarks for Kubernetes. The benchmark describes the best practices for securing unmanaged Kubernetes clusters. The CIS Kubernetes Benchmark encompasses the control plane and the data plane. Since Amazon EKS provides a fully managed control plane, not all of the recommendations from the CIS Kubernetes Benchmark are applicable. To ensure this scope reflects how Amazon EKS is implemented, AWS created the CIS Amazon EKS Benchmark. The EKS benchmark inherits from CIS Kubernetes Benchmark with additional inputs from the community with specific configuration considerations for EKS clusters.

When running kube-bench against an EKS cluster, follow these instructions from Aqua Security. For further information see Introducing The CIS Amazon EKS Benchmark.

"},{"location":"security/docs/hosts/#minimize-access-to-worker-nodes","title":"Minimize access to worker nodes","text":"

Instead of enabling SSH access, use SSM Session Manager when you need to remote into a host. Unlike SSH keys which can be lost, copied, or shared, Session Manager allows you to control access to EC2 instances using IAM. Moreover, it provides an audit trail and log of the commands that were run on the instance.

As of August 19th, 2020 Managed Node Groups support custom AMIs and EC2 Launch Templates. This allows you to embed the SSM agent into the AMI or install it as the worker node is being bootstrapped. If you rather not modify the Optimized AMI or the ASG's launch template, you can install the SSM agent with a DaemonSet as in this example.

"},{"location":"security/docs/hosts/#minimal-iam-policy-for-ssm-based-ssh-access","title":"Minimal IAM policy for SSM based SSH Access","text":"

The AmazonSSMManagedInstanceCore AWS managed policy contains a number of permissions that are not required for SSM Session Manager / SSM RunCommand if you're just looking to avoid SSH access. Of concern specifically is the * permissions for ssm:GetParameter(s) which would allow for the role to access all parameters in Parameter Store (including SecureStrings with the AWS managed KMS key configured).

The following IAM policy contains the minimal set of permissions to enable node access via SSM Systems Manager.

{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"EnableAccessViaSSMSessionManager\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ssmmessages:OpenDataChannel\",\n        \"ssmmessages:OpenControlChannel\",\n        \"ssmmessages:CreateDataChannel\",\n        \"ssmmessages:CreateControlChannel\",\n        \"ssm:UpdateInstanceInformation\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"EnableSSMRunCommand\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ssm:UpdateInstanceInformation\",\n        \"ec2messages:SendReply\",\n        \"ec2messages:GetMessages\",\n        \"ec2messages:GetEndpoint\",\n        \"ec2messages:FailMessage\",\n        \"ec2messages:DeleteMessage\",\n        \"ec2messages:AcknowledgeMessage\"\n      ],\n      \"Resource\": \"*\"\n    }\n  ]\n}\n

With this policy in place and the Session Manager plugin installed, you can then run

aws ssm start-session --target [INSTANCE_ID_OF_EKS_NODE]\n

to access the node.

Note

You may also want to consider adding permissions to enable Session Manager logging.

"},{"location":"security/docs/hosts/#deploy-workers-onto-private-subnets","title":"Deploy workers onto private subnets","text":"

By deploying workers onto private subnets, you minimize their exposure to the Internet where attacks often originate. Beginning April 22, 2020, the assignment of public IP addresses to nodes in a managed node groups will be controlled by the subnet they are deployed onto. Prior to this, nodes in a Managed Node Group were automatically assigned a public IP. If you choose to deploy your worker nodes on to public subnets, implement restrictive AWS security group rules to limit their exposure.

"},{"location":"security/docs/hosts/#run-amazon-inspector-to-assess-hosts-for-exposure-vulnerabilities-and-deviations-from-best-practices","title":"Run Amazon Inspector to assess hosts for exposure, vulnerabilities, and deviations from best practices","text":"

You can use Amazon Inspector to check for unintended network access to your nodes and for vulnerabilities on the underlying Amazon EC2 instances.

Amazon Inspector can provide common vulnerabilities and exposures (CVE) data for your Amazon EC2 instances only if the Amazon EC2 Systems Manager (SSM) agent is installed and enabled. This agent is preinstalled on several Amazon Machine Images (AMIs) including EKS optimized Amazon Linux AMIs. Regardless of SSM agent status, all of your Amazon EC2 instances are scanned for network reachability issues. For more information about configuring scans for Amazon EC2, see Scanning Amazon EC2 instances.

Attention

Inspector cannot be run on the infrastructure used to run Fargate pods.

"},{"location":"security/docs/hosts/#alternatives","title":"Alternatives","text":""},{"location":"security/docs/hosts/#run-selinux","title":"Run SELinux","text":"

Info

Available on Red Hat Enterprise Linux (RHEL), CentOS, Bottlerocket, and Amazon Linux 2023

SELinux provides an additional layer of security to keep containers isolated from each other and from the host. SELinux allows administrators to enforce mandatory access controls (MAC) for every user, application, process, and file. Think of it as a backstop that restricts the operations that can be performed against to specific resources based on a set of labels. On EKS, SELinux can be used to prevent containers from accessing each other's resources.

Container SELinux policies are defined in the container-selinux package. Docker CE requires this package (along with its dependencies) so that the processes and files created by Docker (or other container runtimes) run with limited system access. Containers leverage the container_t label which is an alias to svirt_lxc_net_t. These policies effectively prevent containers from accessing certain features of the host.

When you configure SELinux for Docker, Docker automatically labels workloads container_t as a type and gives each container a unique MCS level. This will isolate containers from one another. If you need looser restrictions, you can create your own profile in SElinux which grants a container permissions to specific areas of the file system. This is similar to PSPs in that you can create different profiles for different containers/pods. For example, you can have a profile for general workloads with a set of restrictive controls and another for things that require privileged access.

SELinux for Containers has a set of options that can be configured to modify the default restrictions. The following SELinux Booleans can be enabled or disabled based on your needs:

Boolean Default Description container_connect_any off Allow containers to access privileged ports on the host. For example, if you have a container that needs to map ports to 443 or 80 on the host. container_manage_cgroup off Allow containers to manage cgroup configuration. For example, a container running systemd will need this to be enabled. container_use_cephfs off Allow containers to use a ceph file system.

By default, containers are allowed to read/execute under /usr and read most content from /etc. The files under /var/lib/docker and /var/lib/containers have the label container_var_lib_t. To view a full list of default, labels see the container.fc file.

docker container run -it \\\n  -v /var/lib/docker/image/overlay2/repositories.json:/host/repositories.json \\\n  centos:7 cat /host/repositories.json\n# cat: /host/repositories.json: Permission denied\n\ndocker container run -it \\\n  -v /etc/passwd:/host/etc/passwd \\\n  centos:7 cat /host/etc/passwd\n# cat: /host/etc/passwd: Permission denied\n

Files labeled with container_file_t are the only files that are writable by containers. If you want a volume mount to be writeable, you will needed to specify :z or :Z at the end.

  • :z will re-label the files so that the container can read/write
  • :Z will re-label the files so that only the container can read/write
ls -Z /var/lib/misc\n# -rw-r--r--. root root system_u:object_r:var_lib_t:s0   postfix.aliasesdb-stamp\n\ndocker container run -it \\\n  -v /var/lib/misc:/host/var/lib/misc:z \\\n  centos:7 echo \"Relabeled!\"\n\nls -Z /var/lib/misc\n#-rw-r--r--. root root system_u:object_r:container_file_t:s0 postfix.aliasesdb-stamp\n
docker container run -it \\\n  -v /var/log:/host/var/log:Z \\\n  fluentbit:latest\n

In Kubernetes, relabeling is slightly different. Rather than having Docker automatically relabel the files, you can specify a custom MCS label to run the pod. Volumes that support relabeling will automatically be relabeled so that they are accessible. Pods with a matching MCS label will be able to access the volume. If you need strict isolation, set a different MCS label for each pod.

securityContext:\n  seLinuxOptions:\n    # Provide a unique MCS label per container\n    # You can specify user, role, and type also\n    # enforcement based on type and level (svert)\n    level: s0:c144:c154\n

In this example s0:c144:c154 corresponds to an MCS label assigned to a file that the container is allowed to access.

On EKS you could create policies that allow for privileged containers to run, like FluentD and create an SELinux policy to allow it to read from /var/log on the host without needing to relabel the host directory. Pods with the same label will be able to access the same host volumes.

We have implemented sample AMIs for Amazon EKS that have SELinux configured on CentOS 7 and RHEL 7. These AMIs were developed to demonstrate sample implementations that meet requirements of highly regulated customers, such as STIG, CJIS, and C2S.

Caution

SELinux will ignore containers where the type is unconfined.

"},{"location":"security/docs/hosts/#tools-and-resources","title":"Tools and resources","text":"
  • SELinux Kubernetes RBAC and Shipping Security Policies for On-prem Applications
  • Iterative Hardening of Kubernetes
  • Audit2Allow
  • SEAlert
  • Generate SELinux policies for containers with Udica describes a tool that looks at container spec files for Linux capabilities, ports, and mount points, and generates a set of SELinux rules that allow the container to run properly
  • AMI Hardening playbooks for hardening the OS to meet different regulatory requirements
  • Keiko Upgrade Manager an open source project from Intuit that orchestrates the rotation of worker nodes.
  • Sysdig Secure
  • eksctl
"},{"location":"security/docs/iam/","title":"Identity and Access Management","text":"

Identity and Access Management (IAM) is an AWS service that performs two essential functions: Authentication and Authorization. Authentication involves the verification of a identity whereas authorization governs the actions that can be performed by AWS resources. Within AWS, a resource can be another AWS service, e.g. EC2, or an AWS principal such as an IAM User or Role. The rules governing the actions that a resource is allowed to perform are expressed as IAM policies.

"},{"location":"security/docs/iam/#controlling-access-to-eks-clusters","title":"Controlling Access to EKS Clusters","text":"

The Kubernetes project supports a variety of different strategies to authenticate requests to the kube-apiserver service, e.g. Bearer Tokens, X.509 certificates, OIDC, etc. EKS currently has native support for webhook token authentication, service account tokens, and as of February 21, 2021, OIDC authentication.

The webhook authentication strategy calls a webhook that verifies bearer tokens. On EKS, these bearer tokens are generated by the AWS CLI or the aws-iam-authenticator client when you run kubectl commands. As you execute commands, the token is passed to the kube-apiserver which forwards it to the authentication webhook. If the request is well-formed, the webhook calls a pre-signed URL embedded in the token's body. This URL validates the request's signature and returns information about the user, e.g. the user's account, Arn, and UserId to the kube-apiserver.

To manually generate a authentication token, type the following command in a terminal window:

aws eks get-token --cluster-name <cluster_name>\n

You can also get a token programmatically. Below is an example written in Go:

package main\n\nimport (\n  \"fmt\"\n  \"log\"\n  \"sigs.k8s.io/aws-iam-authenticator/pkg/token\"\n)\n\nfunc main()  {\n  g, _ := token.NewGenerator(false, false)\n  tk, err := g.Get(\"<cluster_name>\")\n  if err != nil {\n    log.Fatal(err)\n  }\n  fmt.Println(tk)\n}\n

The output should resemble this:

{\n  \"kind\": \"ExecCredential\",\n  \"apiVersion\": \"client.authentication.k8s.io/v1alpha1\",\n  \"spec\": {},\n  \"status\": {\n    \"expirationTimestamp\": \"2020-02-19T16:08:27Z\",\n    \"token\": \"k8s-aws-v1.aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFKTkdSSUxLTlNSQzJXNVFBJTJGMjAyMDAyMTklMkZ1cy1lYXN0LTElMkZzdHMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDIwMDIxOVQxNTU0MjdaJlgtQW16LUV4cGlyZXM9NjAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JTNCeC1rOHMtYXdzLWlkJlgtQW16LVNpZ25hdHVyZT0yMjBmOGYzNTg1ZTMyMGRkYjVlNjgzYTVjOWE0MDUzMDFhZDc2NTQ2ZjI0ZjI4MTExZmRhZDA5Y2Y2NDhhMzkz\"\n  }\n}\n

Each token starts with k8s-aws-v1. followed by a base64 encoded string. The string, when decoded, should resemble to something similar to this:

https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXJPFRILKNSRC2W5QA%2F20200219%2Fus-xxxx-1%2Fsts%2Faws4_request&X-Amz-Date=20200219T155427Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=XXXf8f3285e320ddb5e683a5c9a405301ad76546f24f28111fdad09cf648a393\n

The token consists of a pre-signed URL that includes an Amazon credential and signature. For additional details see https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html.

The token has a time to live (TTL) of 15 minutes after which a new token will need to be generated. This is handled automatically when you use a client like kubectl, however, if you're using the Kubernetes dashboard, you will need to generate a new token and re-authenticate each time the token expires.

Once the user's identity has been authenticated by the AWS IAM service, the kube-apiserver reads the aws-auth ConfigMap in the kube-system Namespace to determine the RBAC group to associate with the user. The aws-auth ConfigMap is used to create a static mapping between IAM principals, i.e. IAM Users and Roles, and Kubernetes RBAC groups. RBAC groups can be referenced in Kubernetes RoleBindings or ClusterRoleBindings. They are similar to IAM Roles in that they define a set of actions (verbs) that can be performed against a collection of Kubernetes resources (objects).

"},{"location":"security/docs/iam/#cluster-access-manager","title":"Cluster Access Manager","text":"

Cluster Access Manager, now the preferred way to manage access of AWS IAM principals to Amazon EKS clusters, is a functionality of the AWS API and is an opt-in feature for EKS v1.23 and later clusters (new or existing). It simplifies identity mapping between AWS IAM and Kubernetes RBACs, eliminating the need to switch between AWS and Kubernetes APIs or editing the aws-auth ConfigMap for access management, reducing operational overhead, and helping address misconfigurations. The tool also enables cluster administrators to revoke or refine cluster-admin permissions automatically granted to the AWS IAM principal used to create the cluster.

This API relies on two concepts:

  • Access Entries: A cluster identity directly linked to an AWS IAM principal (user or role) allowed to authenticate to an Amazon EKS cluster.
  • Access Policies: Are Amazon EKS specific policies that provides the authorization for an Access Entry to perform actions in the Amazon EKS cluster.

At launch Amazon EKS supports only predefined and AWS managed policies. Access policies are not IAM entities and are defined and managed by Amazon EKS.

Cluster Access Manager allows the combination of upstream RBAC with Access Policies supporting allow and pass (but not deny) on Kubernetes AuthZ decisions regarding API server requests. A deny decision will happen when both, the upstream RBAC and Amazon EKS authorizers can't determine the outcome of a request evaluation.

With this feature, Amazon EKS supports three modes of authentication:

  1. CONFIG_MAP to continue using aws-auth configMap exclusively.
  2. API_AND_CONFIG_MAP to source authenticated IAM principals from both EKS Access Entry APIs and the aws-auth configMap, prioritizing the Access Entries. Ideal to migrate existing aws-auth permissions to Access Entries.
  3. API to exclusively rely on EKS Access Entry APIs. This is the new recommended approach.

To get started, cluster administrators can create or update Amazon EKS clusters, setting the preferred authentication to API_AND_CONFIG_MAP or API method and define Access Entries to grant access the desired AWS IAM principals.

$ aws eks create-cluster \\\n    --name <CLUSTER_NAME> \\\n    --role-arn <CLUSTER_ROLE_ARN> \\\n    --resources-vpc-config subnetIds=<value>,endpointPublicAccess=true,endpointPrivateAccess=true \\\n    --logging '{\"clusterLogging\":[{\"types\":[\"api\",\"audit\",\"authenticator\",\"controllerManager\",\"scheduler\"],\"enabled\":true}]}' \\\n    --access-config authenticationMode=API_AND_CONFIG_MAP,bootstrapClusterCreatorAdminPermissions=false\n

The above command is an example to create an Amazon EKS cluster already without the admin permissions of the cluster creator.

It is possible to update Amazon EKS clusters configuration to enable API authenticationMode using the update-cluster-config command, to do that on existing clusters using CONFIG_MAP you will have to first update to API_AND_CONFIG_MAP and then to API. These operations cannot be reverted, meaning that's not possible to switch from API to API_AND_CONFIG_MAP or CONFIG_MAP, and also from API_AND_CONFIG_MAP to CONFIG_MAP.

$ aws eks update-cluster-config \\\n    --name <CLUSTER_NAME> \\\n    --access-config authenticationMode=API\n

The API support commands to add and revoke access to the cluster, as well as validate the existing Access Policies and Access Entries for the specified cluster. The default policies are created to match Kubernetes RBACs as follows.

EKS Access Policy Kubernetes RBAC AmazonEKSClusterAdminPolicy cluster-admin AmazonEKSAdminPolicy admin AmazonEKSEditPolicy edit AmazonEKSViewPolicy view
$ aws eks list-access-policies\n{\n    \"accessPolicies\": [\n        {\n            \"name\": \"AmazonEKSAdminPolicy\",\n            \"arn\": \"arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy\"\n        },\n        {\n            \"name\": \"AmazonEKSClusterAdminPolicy\",\n            \"arn\": \"arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy\"\n        },\n        {\n            \"name\": \"AmazonEKSEditPolicy\",\n            \"arn\": \"arn:aws:eks::aws:cluster-access-policy/AmazonEKSEditPolicy\"\n        },\n        {\n            \"name\": \"AmazonEKSViewPolicy\",\n            \"arn\": \"arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy\"\n        }\n    ]\n}\n\n$ aws eks list-access-entries --cluster-name <CLUSTER_NAME>\n\n{\n    \"accessEntries\": []\n}\n

No Access Entries are available when the cluster is created without the cluster creator admin permission, which is the only entry created by default.

"},{"location":"security/docs/iam/#the-aws-auth-configmap-deprecated","title":"The aws-auth ConfigMap (deprecated)","text":"

One way Kubernetes integration with AWS authentication can be done is via the aws-auth ConfigMap, which resides in the kube-system Namespace. It is responsible for mapping the AWS IAM Identities (Users, Groups, and Roles) authentication, to Kubernetes role-based access control (RBAC) authorization. The aws-auth ConfigMap is automatically created in your Amazon EKS cluster during its provisioning phase. It was initially created to allow nodes to join your cluster, but as mentioned you can also use this ConfigMap to add RBACs access to IAM principals.

To check your cluster's aws-auth ConfigMap, you can use the following command.

kubectl -n kube-system get configmap aws-auth -o yaml\n

This is a sample of a default configuration of the aws-auth ConfigMap.

apiVersion: v1\ndata:\n  mapRoles: |\n    - groups:\n      - system:bootstrappers\n      - system:nodes\n      - system:node-proxier\n      rolearn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/kube-system-<SELF_GENERATED_UUID>\n      username: system:node:{{SessionName}}\nkind: ConfigMap\nmetadata:\n  creationTimestamp: \"2023-10-22T18:19:30Z\"\n  name: aws-auth\n  namespace: kube-system\n

The main session of this ConfigMap, is under data in the mapRoles block, which is basically composed by 3 parameters.

  • groups: The Kubernetes group(s) to map the IAM Role to. This can be a default group, or a custom group specified in a clusterrolebinding or rolebinding. In the above example we have just system groups declared.
  • rolearn: The ARN of the AWS IAM Role be mapped to the Kubernetes group(s) add, using the following format arn:<PARTITION>:iam::<AWS_ACCOUNT_ID>:role/role-name.
  • username: The username within Kubernetes to map to the AWS IAM role. This can be any custom name.

It is also possible to map permissions for AWS IAM Users, defining a new configuration block for mapUsers, under data in the aws-auth ConfigMap, replacing the rolearn parameter for userarn, however as a Best Practice it's always recommended to user mapRoles instead.

To manage permissions, you can edit the aws-auth ConfigMap adding or removing access to your Amazon EKS cluster. Although it's possible to edit the aws-auth ConfigMap manually, it's recommended using tools like eksctl, since this is a very senstitive configuration, and an inaccurate configuration can lock you outside your Amazon EKS Cluster. Check the subsection Use tools to make changes to the aws-auth ConfigMap below for more details.

"},{"location":"security/docs/iam/#cluster-access-recommendations","title":"Cluster Access Recommendations","text":""},{"location":"security/docs/iam/#make-the-eks-cluster-endpoint-private","title":"Make the EKS Cluster Endpoint private","text":"

By default when you provision an EKS cluster, the API cluster endpoint is set to public, i.e. it can be accessed from the Internet. Despite being accessible from the Internet, the endpoint is still considered secure because it requires all API requests to be authenticated by IAM and then authorized by Kubernetes RBAC. That said, if your corporate security policy mandates that you restrict access to the API from the Internet or prevents you from routing traffic outside the cluster VPC, you can:

  • Configure the EKS cluster endpoint to be private. See Modifying Cluster Endpoint Access for further information on this topic.
  • Leave the cluster endpoint public and specify which CIDR blocks can communicate with the cluster endpoint. The blocks are effectively a whitelisted set of public IP addresses that are allowed to access the cluster endpoint.
  • Configure public access with a set of whitelisted CIDR blocks and set private endpoint access to enabled. This will allow public access from a specific range of public IPs while forcing all network traffic between the kubelets (workers) and the Kubernetes API through the cross-account ENIs that get provisioned into the cluster VPC when the control plane is provisioned.
"},{"location":"security/docs/iam/#dont-use-a-service-account-token-for-authentication","title":"Don't use a service account token for authentication","text":"

A service account token is a long-lived, static credential. If it is compromised, lost, or stolen, an attacker may be able to perform all the actions associated with that token until the service account is deleted. At times, you may need to grant an exception for applications that have to consume the Kubernetes API from outside the cluster, e.g. a CI/CD pipeline application. If such applications run on AWS infrastructure, like EC2 instances, consider using an instance profile and mapping that to a Kubernetes RBAC role.

"},{"location":"security/docs/iam/#employ-least-privileged-access-to-aws-resources","title":"Employ least privileged access to AWS Resources","text":"

An IAM User does not need to be assigned privileges to AWS resources to access the Kubernetes API. If you need to grant an IAM user access to an EKS cluster, create an entry in the aws-auth ConfigMap for that user that maps to a specific Kubernetes RBAC group.

"},{"location":"security/docs/iam/#remove-the-cluster-admin-permissions-from-the-cluster-creator-principal","title":"Remove the cluster-admin permissions from the cluster creator principal","text":"

By default Amazon EKS clusters are created with a permanent cluster-admin permission bound to the cluster creator principal. With the Cluster Access Manager API, it's possible to create clusters without this permission setting the --access-config bootstrapClusterCreatorAdminPermissions to false, when using API_AND_CONFIG_MAP or API authentication mode. Revoke this access considered a best practice to avoid any unwanted changes to the cluster configuration. The process to revoke this access, follows the same process to revoke any other access to the cluster.

The API gives you flexibility to only disassociate an IAM principal from an Access Policy, in this case the AmazonEKSClusterAdminPolicy.

$ aws eks list-associated-access-policies \\\n    --cluster-name <CLUSTER_NAME> \\\n    --principal-arn <IAM_PRINCIPAL_ARN>\n\n$ aws eks disassociate-access-policy --cluster-name <CLUSTER_NAME> \\\n    --principal-arn <IAM_PRINCIPAL_ARN. \\\n    --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy\n

Or completely removing the Access Entry associated with the cluster-admin permission.

$ aws eks list-access-entries --cluster-name <CLUSTER_NAME>\n\n{\n    \"accessEntries\": []\n}\n\n$ aws eks delete-access-entry --cluster-name <CLUSTER_NAME> \\\n  --principal-arn <IAM_PRINCIPAL_ARN>\n

This access can be granted again if needed during an incident, emergency or break glass scenario where the cluster is otherwise inaccessible.

If the cluster still configured with the CONFIG_MAP authentication method, all additional users should be granted access to the cluster through the aws-auth ConfigMap, and after aws-auth ConfigMap is configured, the role assigned to the entity that created the cluster, can be deleted and only recreated in case of an incident, emergency or break glass scenario, or where the aws-auth ConfigMap is corrupted and the cluster is otherwise inaccessible. This can be particularly useful in production clusters.

"},{"location":"security/docs/iam/#use-iam-roles-when-multiple-users-need-identical-access-to-the-cluster","title":"Use IAM Roles when multiple users need identical access to the cluster","text":"

Rather than creating an entry for each individual IAM User, allow those users to assume an IAM Role and map that role to a Kubernetes RBAC group. This will be easier to maintain, especially as the number of users that require access grows.

Attention

When accessing the EKS cluster with the IAM entity mapped by aws-auth ConfigMap, the username described is recorded in the user field of the Kubernetes audit log. If you're using an IAM role, the actual users who assume that role aren't recorded and can't be audited.

If still using the aws-auth configMap as the authentication method, when assigning K8s RBAC permissions to an IAM role, you should include {{SessionName}} in your username. That way, the audit log will record the session name so you can track who the actual user assume this role along with the CloudTrail log.

- rolearn: arn:aws:iam::XXXXXXXXXXXX:role/testRole\n  username: testRole:{{SessionName}}\n  groups:\n    - system:masters\n

In Kubernetes 1.20 and above, this change is no longer required, since user.extra.sessionName.0 was added to the Kubernetes audit log.

"},{"location":"security/docs/iam/#employ-least-privileged-access-when-creating-rolebindings-and-clusterrolebindings","title":"Employ least privileged access when creating RoleBindings and ClusterRoleBindings","text":"

Like the earlier point about granting access to AWS Resources, RoleBindings and ClusterRoleBindings should only include the set of permissions necessary to perform a specific function. Avoid using [\"*\"] in your Roles and ClusterRoles unless it's absolutely necessary. If you're unsure what permissions to assign, consider using a tool like audit2rbac to automatically generate Roles and binding based on the observed API calls in the Kubernetes Audit Log.

"},{"location":"security/docs/iam/#create-cluster-using-an-automated-process","title":"Create cluster using an automated process","text":"

As seen in earlier steps, when creating an Amazon EKS cluster, if not using the using API_AND_CONFIG_MAP or API authentication mode, and not opting out to delegate cluster-admin permissions to the cluster creator, the IAM entity user or role, such as a\u00a0federated user\u00a0that creates the cluster, is automatically granted\u00a0system:masters\u00a0permissions in the cluster's RBAC configuration. Even being a best practice to remove this permission, as described here if using the CONFIG_MAP authentication method, relying on aws-auth ConfigMap, this access cannot be revoked. Therefore it is a good idea to create the cluster with an infrastructure automation pipeline tied to dedicated IAM role, with no permissions to be assumed by other users or entities and regularly audit this role's permissions, policies, and who has access to trigger the pipeline. Also, this role should not be used to perform routine actions on the cluster, and be exclusively used to cluster level actions triggered by the pipeline, via SCM code changes for example.

"},{"location":"security/docs/iam/#create-the-cluster-with-a-dedicated-iam-role","title":"Create the cluster with a dedicated IAM role","text":"

When you create an Amazon EKS cluster, the IAM entity user or role, such as a\u00a0federated user\u00a0that creates the cluster, is automatically granted\u00a0system:masters\u00a0permissions in the cluster's RBAC configuration. This access cannot be removed and is not managed through the aws-auth ConfigMap. Therefore it is a good idea to create the cluster with a dedicated IAM role and regularly audit who can assume this role. This role should not be used to perform routine actions on the cluster, and instead additional users should be granted access to the cluster through the aws-auth ConfigMap for this purpose. After the aws-auth ConfigMap is configured, the role should be secured and only used in temporary elevated privilege mode / break glass for scenarios where the cluster is otherwise inaccessible. This can be particularly useful in clusters which do not have direct user access configured.

"},{"location":"security/docs/iam/#regularly-audit-access-to-the-cluster","title":"Regularly audit access to the cluster","text":"

Who requires access is likely to change over time. Plan to periodically audit the aws-auth ConfigMap to see who has been granted access and the rights they've been assigned. You can also use open source tooling like kubectl-who-can, or rbac-lookup to examine the roles bound to a particular service account, user, or group. We'll explore this topic further when we get to the section on auditing.

"},{"location":"security/docs/iam/#if-relying-on-aws-auth-configmap-use-tools-to-make-changes","title":"If relying on aws-auth configMap use tools to make changes","text":"

An improperly formatted aws-auth ConfigMap may cause you to lose access to the cluster. If you need to make changes to the ConfigMap, use a tool.

eksctl The eksctl CLI includes a command for adding identity mappings to the aws-auth ConfigMap.

View CLI Help:

$ eksctl create iamidentitymapping --help\n...\n

Check the identities mapped to your Amazon EKS Cluster.

$ eksctl get iamidentitymapping --cluster $CLUSTER_NAME --region $AWS_REGION\nARN                                                                   USERNAME                        GROUPS                                                  ACCOUNT\narn:aws:iam::788355785855:role/kube-system-<SELF_GENERATED_UUID>      system:node:{{SessionName}}     system:bootstrappers,system:nodes,system:node-proxier  \n

Make an IAM Role a Cluster Admin:

$ eksctl create iamidentitymapping --cluster  <CLUSTER_NAME> --region=<region> --arn arn:aws:iam::123456:role/testing --group system:masters --username admin\n...\n

For more information, review eksctl docs

aws-auth by keikoproj

aws-auth by keikoproj includes both a cli and a go library.

Download and view help CLI help:

$ go get github.com/keikoproj/aws-auth\n...\n$ aws-auth help\n...\n

Alternatively, install aws-auth with the krew plugin manager for kubectl.

$ kubectl krew install aws-auth\n...\n$ kubectl aws-auth\n...\n

Review the aws-auth docs on GitHub for more information, including the go library.

AWS IAM Authenticator CLI

The aws-iam-authenticator project includes a CLI for updating the ConfigMap.

Download a release on GitHub.

Add cluster permissions to an IAM Role:

$ ./aws-iam-authenticator add role --rolearn arn:aws:iam::185309785115:role/lil-dev-role-cluster --username lil-dev-user --groups system:masters --kubeconfig ~/.kube/config\n...\n
"},{"location":"security/docs/iam/#alternative-approaches-to-authentication-and-access-management","title":"Alternative Approaches to Authentication and Access Management","text":"

While IAM is the preferred way to authenticate users who need access to an EKS cluster, it is possible to use an OIDC identity provider such as GitHub using an authentication proxy and Kubernetes impersonation. Posts for two such solutions have been published on the AWS Open Source blog:

  • Authenticating to EKS Using GitHub Credentials with Teleport
  • Consistent OIDC authentication across multiple EKS clusters using kube-oidc-proxy

Attention

EKS natively supports OIDC authentication without using a proxy. For further information, please read the launch blog, Introducing OIDC identity provider authentication for Amazon EKS. For an example showing how to configure EKS with Dex, a popular open source OIDC provider with connectors for a variety of different authention methods, see Using Dex & dex-k8s-authenticator to authenticate to Amazon EKS. As described in the blogs, the username/group of users authenticated by an OIDC provider will appear in the Kubernetes audit log.

You can also use AWS SSO to federate AWS with an external identity provider, e.g. Azure AD. If you decide to use this, the AWS CLI v2.0 includes an option to create a named profile that makes it easy to associate an SSO session with your current CLI session and assume an IAM role. Know that you must assume a role prior to running kubectl as the IAM role is used to determine the user's Kubernetes RBAC group.

"},{"location":"security/docs/iam/#identities-and-credentials-for-eks-pods","title":"Identities and Credentials for EKS pods","text":"

Certain applications that run within a Kubernetes cluster need permission to call the Kubernetes API to function properly. For example, the AWS Load Balancer Controller needs to be able to list a Service's Endpoints. The controller also needs to be able to invoke AWS APIs to provision and configure an ALB. In this section we will explore the best practices for assigning rights and privileges to Pods.

"},{"location":"security/docs/iam/#kubernetes-service-accounts","title":"Kubernetes Service Accounts","text":"

A service account is a special type of object that allows you to assign a Kubernetes RBAC role to a pod. A default service account is created automatically for each Namespace within a cluster. When you deploy a pod into a Namespace without referencing a specific service account, the default service account for that Namespace will automatically get assigned to the Pod and the Secret, i.e. the service account (JWT) token for that service account, will get mounted to the pod as a volume at /var/run/secrets/kubernetes.io/serviceaccount. Decoding the service account token in that directory will reveal the following metadata:

{\n  \"iss\": \"kubernetes/serviceaccount\",\n  \"kubernetes.io/serviceaccount/namespace\": \"default\",\n  \"kubernetes.io/serviceaccount/secret.name\": \"default-token-5pv4z\",\n  \"kubernetes.io/serviceaccount/service-account.name\": \"default\",\n  \"kubernetes.io/serviceaccount/service-account.uid\": \"3b36ddb5-438c-11ea-9438-063a49b60fba\",\n  \"sub\": \"system:serviceaccount:default:default\"\n}\n

The default service account has the following permissions to the Kubernetes API.

apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  annotations:\n    rbac.authorization.kubernetes.io/autoupdate: \"true\"\n  creationTimestamp: \"2020-01-30T18:13:25Z\"\n  labels:\n    kubernetes.io/bootstrapping: rbac-defaults\n  name: system:discovery\n  resourceVersion: \"43\"\n  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/system%3Adiscovery\n  uid: 350d2ab8-438c-11ea-9438-063a49b60fba\nrules:\n- nonResourceURLs:\n  - /api\n  - /api/*\n  - /apis\n  - /apis/*\n  - /healthz\n  - /openapi\n  - /openapi/*\n  - /version\n  - /version/\n  verbs:\n  - get\n

This role authorizes unauthenticated and authenticated users to read API information and is deemed safe to be publicly accessible.

When an application running within a Pod calls the Kubernetes APIs, the Pod needs to be assigned a service account that explicitly grants it permission to call those APIs. Similar to guidelines for user access, the Role or ClusterRole bound to a service account should be restricted to the API resources and methods that the application needs to function and nothing else. To use a non-default service account simply set the spec.serviceAccountName field of a Pod to the name of the service account you wish to use. For additional information about creating service accounts, see https://kubernetes.io/docs/reference/access-authn-authz/rbac/#service-account-permissions.

Note

Prior to Kubernetes 1.24, Kubernetes would automatically create a secret for each a service account. This secret was mounted to the pod at /var/run/secrets/kubernetes.io/serviceaccount and would be used by the pod to authenticate to the Kubernetes API server. In Kubernetes 1.24, a service account token is dynamically generated when the pod runs and is only valid for an hour by default. A secret for the service account will not be created. If you have an application that runs outside the cluster that needs to authenticate to the Kubernetes API, e.g. Jenkins, you will need to create a secret of type kubernetes.io/service-account-token along with an annotation that references the service account such as metadata.annotations.kubernetes.io/service-account.name: <SERVICE_ACCOUNT_NAME>. Secrets created in this way do not expire.

"},{"location":"security/docs/iam/#iam-roles-for-service-accounts-irsa","title":"IAM Roles for Service Accounts (IRSA)","text":"

IRSA is a feature that allows you to assign an IAM role to a Kubernetes service account. It works by leveraging a Kubernetes feature known as Service Account Token Volume Projection. When Pods are configured with a Service Account that references an IAM Role, the Kubernetes API server will call the public OIDC discovery endpoint for the cluster on startup. The endpoint cryptographically signs the OIDC token issued by Kubernetes and the resulting token mounted as a volume. This signed token allows the Pod to call the AWS APIs associated IAM role. When an AWS API is invoked, the AWS SDKs calls sts:AssumeRoleWithWebIdentity. After validating the token's signature, IAM exchanges the Kubernetes issued token for a temporary AWS role credential.

When using IRSA, it is important to reuse AWS SDK sessions to avoid unneeded calls to AWS STS.

Decoding the (JWT) token for IRSA will produce output similar to the example you see below:

{\n  \"aud\": [\n    \"sts.amazonaws.com\"\n  ],\n  \"exp\": 1582306514,\n  \"iat\": 1582220114,\n  \"iss\": \"https://oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128\",\n  \"kubernetes.io\": {\n    \"namespace\": \"default\",\n    \"pod\": {\n      \"name\": \"alpine-57b5664646-rf966\",\n      \"uid\": \"5a20f883-5407-11ea-a85c-0e62b7a4a436\"\n    },\n    \"serviceaccount\": {\n      \"name\": \"s3-read-only\",\n      \"uid\": \"a720ba5c-5406-11ea-9438-063a49b60fba\"\n    }\n  },\n  \"nbf\": 1582220114,\n  \"sub\": \"system:serviceaccount:default:s3-read-only\"\n}\n

This particular token grants the Pod view-only privileges to S3 by assuming an IAM role. When the application attempts to read from S3, the token is exchanged for a temporary set of IAM credentials that resembles this:

{\n    \"AssumedRoleUser\": {\n        \"AssumedRoleId\": \"AROA36C6WWEJULFUYMPB6:abc\",\n        \"Arn\": \"arn:aws:sts::123456789012:assumed-role/eksctl-winterfell-addon-iamserviceaccount-de-Role1-1D61LT75JH3MB/abc\"\n    },\n    \"Audience\": \"sts.amazonaws.com\",\n    \"Provider\": \"arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128\",\n    \"SubjectFromWebIdentityToken\": \"system:serviceaccount:default:s3-read-only\",\n    \"Credentials\": {\n        \"SecretAccessKey\": \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\",\n        \"SessionToken\": \"FwoGZXIvYXdzEGMaDMLxAZkuLpmSwYXShiL9A1S0X87VBC1mHCrRe/pB2oes+l1eXxUYnPJyC9ayOoXMvqXQsomq0xs6OqZ3vaa5Iw1HIyA4Cv1suLaOCoU3hNvOIJ6C94H1vU0siQYk7DIq9Av5RZe+uE2FnOctNBvYLd3i0IZo1ajjc00yRK3v24VRq9nQpoPLuqyH2jzlhCEjXuPScPbi5KEVs9fNcOTtgzbVf7IG2gNiwNs5aCpN4Bv/Zv2A6zp5xGz9cWj2f0aD9v66vX4bexOs5t/YYhwuwAvkkJPSIGvxja0xRThnceHyFHKtj0H+bi/PWAtlI8YJcDX69cM30JAHDdQH+ltm/4scFptW1hlvMaP+WReCAaCrsHrAT+yka7ttw5YlUyvZ8EPog+j6fwHlxmrXM9h1BqdikomyJU00gm1++FJelfP+1zAwcyrxCnbRl3ARFrAt8hIlrT6Vyu8WvWtLxcI8KcLcJQb/LgkW+sCTGlYcY8z3zkigJMbYn07ewTL5Ss7LazTJJa758I7PZan/v3xQHd5DEc5WBneiV3iOznDFgup0VAMkIviVjVCkszaPSVEdK2NU7jtrh6Jfm7bU/3P6ZG+CkyDLIa8MBn9KPXeJd/y+jTk5Ii+fIwO/+mDpGNUribg6TPxhzZ8b/XdZO1kS1gVgqjXyVC+M+BRBh6C4H21w/eMzjCtDIpoxt5rGKL6Nu/IFMipoC4fgx6LIIHwtGYMG7SWQi7OsMAkiwZRg0n68/RqWgLzBt/4pfjSRYuk=\",\n        \"Expiration\": \"2020-02-20T18:49:50Z\",\n        \"AccessKeyId\": \"ASIAIOSFODNN7EXAMPLE\"\n    }\n}\n

A mutating webhook that runs as part of the EKS control plane injects the AWS Role ARN and the path to a web identity token file into the Pod as environment variables. These values can also be supplied manually.

AWS_ROLE_ARN=arn:aws:iam::AWS_ACCOUNT_ID:role/IAM_ROLE_NAME\nAWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token\n

The kubelet will automatically rotate the projected token when it is older than 80% of its total TTL, or after 24 hours. The AWS SDKs are responsible for reloading the token when it rotates. For further information about IRSA, see https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html.

"},{"location":"security/docs/iam/#eks-pod-identities","title":"EKS Pod Identities","text":"

EKS Pod Identities is a feature launched at re:Invent 2023 that allows you to assign an IAM role to a kubernetes service account, without the need to configure an Open Id Connect (OIDC) identity provider(IDP) for each cluster in your AWS account. To use EKS Pod Identity, you must deploy an agent which runs as a DaemonSet pod on every eligible worker node. This agent is made available to you as an EKS Add-on and is a pre-requisite to use EKS Pod Identity feature. Your applications must use a supported version of the AWS SDK to use this feature.

When EKS Pod Identities are configured for a Pod, EKS will mount and refresh a pod identity token at /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token. This token will be used by the AWS SDK to communicate with the EKS Pod Identity Agent, which uses the pod identity token and the agent's IAM role to create temporary credentials for your pods by calling the AssumeRoleForPodIdentity API. The pod identity token delivered to your pods is a JWT issued from your EKS cluster and cryptographically signed, with appropriate JWT claims for use with EKS Pod Identities.

To learn more about EKS Pod Identities, please see this blog.

You do not have to make any modifications to your application code to use EKS Pod Identities. Supported AWS SDK versions will automatically discover credentials made available with EKS Pod Identities by using the credential provider chain. Like IRSA, EKS pod identities sets variables within your pods to direct them how to find AWS credentials.

"},{"location":"security/docs/iam/#working-with-iam-roles-for-eks-pod-identities","title":"Working with IAM roles for EKS Pod Identities","text":"
  • EKS Pod Identities can only directly assume an IAM role that belongs to the same AWS account as the EKS cluster. To access an IAM role in another AWS account, you must assume that role by configuring a profile in your SDK configuration, or in your application's code.
  • When EKS Pod Identities are being configured for Service Accounts, the person or process configuring the Pod Identity Association must have the iam:PassRole entitlement for that role.
  • Each Service Account may only have one IAM role associated with it through EKS Pod Identities, however you can associate the same IAM role with multiple service accounts.
  • IAM roles used with EKS Pod Identities must allow the pods.eks.amazonaws.com Service Principal to assume them, and set session tags. The following is an example role trust policy which allows EKS Pod Identities to use an IAM role:
{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Service\": \"pods.eks.amazonaws.com\"\n      },\n      \"Action\": [\n        \"sts:AssumeRole\",\n        \"sts:TagSession\"\n      ],\n      \"Condition\": {\n        \"StringEquals\": {\n          \"aws:SourceOrgId\": \"${aws:ResourceOrgId}\"\n        }\n      }\n    }\n  ]\n}\n

AWS recommends using condition keys like aws:SourceOrgId to help protect against the cross-service confused deputy problem. In the above example role trust policy, the ResourceOrgId is a variable equal to the AWS Organizations Organization ID of the AWS Organization that the AWS account belongs to. EKS will pass in a value for aws:SourceOrgId equal to that when assuming a role with EKS Pod Identities.

"},{"location":"security/docs/iam/#abac-and-eks-pod-identities","title":"ABAC and EKS Pod Identities","text":"

When EKS Pod Identities assumes an IAM role, it sets the following session tags:

EKS Pod Identities Session Tag Value kubernetes-namespace The namespace the pod associated with EKS Pod Identities runs in. kubernetes-service-account The name of the kubernetes service account associated with EKS Pod Identities eks-cluster-arn The ARN of the EKS cluster, e.g. arn:${Partition}:eks:${Region}:${Account}:cluster/${ClusterName}. The cluster ARN is unique, but if a cluster is deleted and recreated in the same region with the same name, within the same AWS account, it will have the same ARN. eks-cluster-name The name of the EKS cluster. Please note that EKS cluster names can be same within your AWS account, and EKS clusters in other AWS accounts. kubernetes-pod-name The name of the pod in EKS. kubernetes-pod-uid The UID of the pod in EKS.

These session tags allow you to use Attribute Based Access Control(ABAC) to grant access to your AWS resources to only specific kubernetes service accounts. When doing so, it is very important to understand that kubernetes service accounts are only unique within a namespace, and kubernetes namespaces are only unique within an EKS cluster. These session tags can be accessed in AWS policies by using the aws:PrincipalTag/<tag-key> global condition key, such as aws:PrincipalTag/eks-cluster-arn

For example, if you wanted to grant access to only a specific service account to access an AWS resource in your account with an IAM or resource policy, you would need to check eks-cluster-arn and kubernetes-namespace tags as well as the kubernetes-service-account to ensure that only that service accounts from the intended cluster have access to that resource as other clusters could have identical kubernetes-service-accounts and kubernetes-namespaces.

This example S3 Bucket policy only grants access to objects in the S3 bucket it's attached to, only if kubernetes-service-account, kubernetes-namespace, eks-cluster-arn all meet their expected values, where the EKS cluster is hosted in the AWS account 111122223333.

{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": \"arn:aws:iam::111122223333:root\"\n            },\n            \"Action\": \"s3:*\",\n            \"Resource\":            [\n                \"arn:aws:s3:::ExampleBucket/*\"\n            ],\n            \"Condition\": {\n                \"StringEquals\": {\n                    \"aws:PrincipalTag/kubernetes-service-account\": \"s3objectservice\",\n                    \"aws:PrincipalTag/eks-cluster-arn\": \"arn:aws:eks:us-west-2:111122223333:cluster/ProductionCluster\",\n                    \"aws:PrincipalTag/kubernetes-namespace\": \"s3datanamespace\"\n                }\n            }\n        }\n    ]\n}\n
"},{"location":"security/docs/iam/#eks-pod-identities-compared-to-irsa","title":"EKS Pod Identities compared to IRSA","text":"

Both EKS Pod Identities and IRSA are preferred ways to deliver temporary AWS credentials to your EKS pods. Unless you have specific usecases for IRSA, we recommend you use EKS Pod Identities when using EKS. This table helps compare the two features.

# EKS Pod Identities IRSA Requires permission to create an OIDC IDP in your AWS accounts? No Yes Requires unique IDP setup per cluster No Yes Sets relevant session tags for use with ABAC Yes No Requires an iam:PassRole Check? Yes No Uses AWS STS Quota from your AWS account? No Yes Can access other AWS accounts Indirectly with role chaining Directly with sts:AssumeRoleWithWebIdentity Compatible with AWS SDKs Yes Yes Requires Pod Identity Agent Daemonset on nodes? Yes No"},{"location":"security/docs/iam/#identities-and-credentials-for-eks-pods-recommendations","title":"Identities and Credentials for EKS pods Recommendations","text":""},{"location":"security/docs/iam/#update-the-aws-node-daemonset-to-use-irsa","title":"Update the aws-node daemonset to use IRSA","text":"

At present, the aws-node daemonset is configured to use a role assigned to the EC2 instances to assign IPs to pods. This role includes several AWS managed policies, e.g. AmazonEKS_CNI_Policy and EC2ContainerRegistryReadOnly that effectively allow all pods running on a node to attach/detach ENIs, assign/unassign IP addresses, or pull images from ECR. Since this presents a risk to your cluster, it is recommended that you update the aws-node daemonset to use IRSA. A script for doing this can be found in the repository for this guide.

The aws-node daemonset supports EKS Pod Identities in versions v1.15.5 and later.

"},{"location":"security/docs/iam/#restrict-access-to-the-instance-profile-assigned-to-the-worker-node","title":"Restrict access to the instance profile assigned to the worker node","text":"

When you use IRSA or EKS Pod Identities, it updates the credential chain of the pod to use IRSA or EKS Pod Identities first, however, the pod can still inherit the rights of the instance profile assigned to the worker node. When using IRSA or EKS Pod Identities, it is strongly recommended that you block access instance metadata to help ensure that your applications only have the permissions they require, and not their nodes.

Caution

Blocking access to instance metadata will prevent pods that do not use IRSA or EKS Pod Identities from inheriting the role assigned to the worker node.

You can block access to instance metadata by requiring the instance to use IMDSv2 only and updating the hop count to 1 as in the example below. You can also include these settings in the node group's launch template. Do not disable instance metadata as this will prevent components like the node termination handler and other things that rely on instance metadata from working properly.

$ aws ec2 modify-instance-metadata-options --instance-id <value> --http-tokens required --http-put-response-hop-limit 1\n...\n

If you are using Terraform to create launch templates for use with Managed Node Groups, add the metadata block to configure the hop count as seen in this code snippet:

resource \"aws_launch_template\" \"foo\" {\n  name = \"foo\"\n  ...\n    metadata_options {\n    http_endpoint               = \"enabled\"\n    http_tokens                 = \"required\"\n    http_put_response_hop_limit = 1\n    instance_metadata_tags      = \"enabled\"\n  }\n  ...\n

You can also block a pod's access to EC2 metadata by manipulating iptables on the node. For further information about this method, see Limiting access to the instance metadata service.

If you have an application that is using an older version of the AWS SDK that doesn't support IRSA or EKS Pod Identities, you should update the SDK version.

"},{"location":"security/docs/iam/#scope-the-iam-role-trust-policy-for-irsa-roles-to-the-service-account-name-namespace-and-cluster","title":"Scope the IAM Role trust policy for IRSA Roles to the service account name, namespace, and cluster","text":"

The trust policy can be scoped to a Namespace or a specific service account within a Namespace. When using IRSA it's best to make the role trust policy as explicit as possible by including the service account name. This will effectively prevent other Pods within the same Namespace from assuming the role. The CLI eksctl will do this automatically when you use it to create service accounts/IAM roles. See https://eksctl.io/usage/iamserviceaccounts/ for further information.

When working with IAM directly, this is adding condition into the role's trust policy that uses conditions to ensure the :sub claim are the namespace and service account you expect. As an example, before we had an IRSA token with a sub claim of \"system:serviceaccount:default:s3-read-only\" . This is the default namespace and the service account is s3-read-only. You would use a condition like the following to ensure that only your service account in a given namespace from your cluster can assume that role:

  \"Condition\": {\n      \"StringEquals\": {\n          \"oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128:aud\": \"sts.amazonaws.com\",\n          \"oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128:sub\": \"system:serviceaccount:default:s3-read-only\"\n      }\n  }\n
"},{"location":"security/docs/iam/#use-one-iam-role-per-application","title":"Use one IAM role per application","text":"

With both IRSA and EKS Pod Identity, it is a best practice to give each application its own IAM role. This gives you improved isolation as you can modify one application without impacting another, and allows you to apply the principal of least privilege by only granting an application the permissions it needs.

When using ABAC with EKS Pod Identity, you may use a common IAM role across multiple service accounts and rely on their session attributes for access control. This is especially useful when operating at scale, as ABAC allows you to operate with fewer IAM roles.

"},{"location":"security/docs/iam/#when-your-application-needs-access-to-imds-use-imdsv2-and-increase-the-hop-limit-on-ec2-instances-to-2","title":"When your application needs access to IMDS, use IMDSv2 and increase the hop limit on EC2 instances to 2","text":"

IMDSv2 requires you use a PUT request to get a session token. The initial PUT request has to include a TTL for the session token. Newer versions of the AWS SDKs will handle this and the renewal of said token automatically. It's also important to be aware that the default hop limit on EC2 instances is intentionally set to 1 to prevent IP forwarding. As a consequence, Pods that request a session token that are run on EC2 instances may eventually time out and fallback to using the IMDSv1 data flow. EKS adds support IMDSv2 by enabling both v1 and v2 and changing the hop limit to 2 on nodes provisioned by eksctl or with the official CloudFormation templates.

"},{"location":"security/docs/iam/#disable-auto-mounting-of-service-account-tokens","title":"Disable auto-mounting of service account tokens","text":"

If your application doesn't need to call the Kubernetes API set the automountServiceAccountToken attribute to false in the PodSpec for your application or patch the default service account in each namespace so that it's no longer mounted to pods automatically. For example:

kubectl patch serviceaccount default -p $'automountServiceAccountToken: false'\n
"},{"location":"security/docs/iam/#use-dedicated-service-accounts-for-each-application","title":"Use dedicated service accounts for each application","text":"

Each application should have its own dedicated service account. This applies to service accounts for the Kubernetes API as well as IRSA and EKS Pod Identity.

Attention

If you employ a blue/green approach to cluster upgrades instead of performing an in-place cluster upgrade when using IRSA, you will need to update the trust policy of each of the IRSA IAM roles with the OIDC endpoint of the new cluster. A blue/green cluster upgrade is where you create a cluster running a newer version of Kubernetes alongside the old cluster and use a load balancer or a service mesh to seamlessly shift traffic from services running on the old cluster to the new cluster. When using blue/green cluster upgrades with EKS Pod Identity, you would create pod identity associations between the IAM roles and service accounts in the new cluster. And update the IAM role trust policy if you have a sourceArn condition.

"},{"location":"security/docs/iam/#run-the-application-as-a-non-root-user","title":"Run the application as a non-root user","text":"

Containers run as root by default. While this allows them to read the web identity token file, running a container as root is not considered a best practice. As an alternative, consider adding the spec.securityContext.runAsUser attribute to the PodSpec. The value of runAsUser is arbitrary value.

In the following example, all processes within the Pod will run under the user ID specified in the runAsUser field.

apiVersion: v1\nkind: Pod\nmetadata:\n  name: security-context-demo\nspec:\n  securityContext:\n    runAsUser: 1000\n    runAsGroup: 3000\n  containers:\n  - name: sec-ctx-demo\n    image: busybox\n    command: [ \"sh\", \"-c\", \"sleep 1h\" ]\n

When you run a container as a non-root user, it prevents the container from reading the IRSA service account token because the token is assigned 0600 [root] permissions by default. If you update the securityContext for your container to include fsgroup=65534 [Nobody] it will allow the container to read the token.

spec:\n  securityContext:\n    fsGroup: 65534\n

In Kubernetes 1.19 and above, this change is no longer required and applications can read the IRSA service account token without adding them to the Nobody group.

"},{"location":"security/docs/iam/#grant-least-privileged-access-to-applications","title":"Grant least privileged access to applications","text":"

Action Hero is a utility that you can run alongside your application to identify the AWS API calls and corresponding IAM permissions your application needs to function properly. It is similar to IAM Access Advisor in that it helps you gradually limit the scope of IAM roles assigned to applications. Consult the documentation on granting least privileged access to AWS resources for further information.

Consider setting a permissions boundary on IAM roles used with IRSA and Pod Identities. You can use the permissions boundary to ensure that the roles used by IRSA or Pod Identities can not exceed a maximum level of permissions. For an example guide on getting started with permissions boundaries with an example permissions boundary policy, please see this github repo.

"},{"location":"security/docs/iam/#review-and-revoke-unnecessary-anonymous-access-to-your-eks-cluster","title":"Review and revoke unnecessary anonymous access to your EKS cluster","text":"

Ideally anonymous access should be disabled for all API actions. Anonymous access is granted by creating a RoleBinding or ClusterRoleBinding for the Kubernetes built-in user system:anonymous. You can use the rbac-lookup tool to identify permissions that system:anonymous user has on your cluster:

./rbac-lookup | grep -P 'system:(anonymous)|(unauthenticated)'\nsystem:anonymous               cluster-wide        ClusterRole/system:discovery\nsystem:unauthenticated         cluster-wide        ClusterRole/system:discovery\nsystem:unauthenticated         cluster-wide        ClusterRole/system:public-info-viewer\n

Any role or ClusterRole other than system:public-info-viewer should not be bound to system:anonymous user or system:unauthenticated group.

There may be some legitimate reasons to enable anonymous access on specific APIs. If this is the case for your cluster ensure that only those specific APIs are accessible by anonymous user and exposing those APIs without authentication doesn\u2019t make your cluster vulnerable.

Prior to Kubernetes/EKS Version 1.14, system:unauthenticated group was associated to system:discovery and system:basic-user ClusterRoles by default. Note that even if you have updated your cluster to version 1.14 or higher, these permissions may still be enabled on your cluster, since cluster updates do not revoke these permissions. To check which ClusterRoles have \"system:unauthenticated\" except system:public-info-viewer you can run the following command (requires jq util):

kubectl get ClusterRoleBinding -o json | jq -r '.items[] | select(.subjects[]?.name ==\"system:unauthenticated\") | select(.metadata.name != \"system:public-info-viewer\") | .metadata.name'\n

And \"system:unauthenticated\" can be removed from all the roles except \"system:public-info-viewer\" using:

kubectl get ClusterRoleBinding -o json | jq -r '.items[] | select(.subjects[]?.name ==\"system:unauthenticated\") | select(.metadata.name != \"system:public-info-viewer\") | del(.subjects[] | select(.name ==\"system:unauthenticated\"))' | kubectl apply -f -\n

Alternatively, you can check and remove it manually by kubectl describe and kubectl edit. To check if system:unauthenticated group has system:discovery permissions on your cluster run the following command:

kubectl describe clusterrolebindings system:discovery\n\nName:         system:discovery\nLabels:       kubernetes.io/bootstrapping=rbac-defaults\nAnnotations:  rbac.authorization.kubernetes.io/autoupdate: true\nRole:\n  Kind:  ClusterRole\n  Name:  system:discovery\nSubjects:\n  Kind   Name                    Namespace\n  ----   ----                    ---------\n  Group  system:authenticated\n  Group  system:unauthenticated\n

To check if system:unauthenticated group has system:basic-user permission on your cluster run the following command:

kubectl describe clusterrolebindings system:basic-user\n\nName:         system:basic-user\nLabels:       kubernetes.io/bootstrapping=rbac-defaults\nAnnotations:  rbac.authorization.kubernetes.io/autoupdate: true\nRole:\n  Kind:  ClusterRole\n  Name:  system:basic-user\nSubjects:\n  Kind   Name                    Namespace\n  ----   ----                    ---------\n  Group  system:authenticated\n  Group  system:unauthenticated\n

If system:unauthenticated group is bound to system:discovery and/or system:basic-user ClusterRoles on your cluster, you should disassociate these roles from system:unauthenticated group. Edit system:discovery ClusterRoleBinding using the following command:

kubectl edit clusterrolebindings system:discovery\n

The above command will open the current definition of system:discovery ClusterRoleBinding in an editor as shown below:

# Please edit the object below. Lines beginning with a '#' will be ignored,\n# and an empty file will abort the edit. If an error occurs while saving this file will be\n# reopened with the relevant failures.\n#\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  annotations:\n    rbac.authorization.kubernetes.io/autoupdate: \"true\"\n  creationTimestamp: \"2021-06-17T20:50:49Z\"\n  labels:\n    kubernetes.io/bootstrapping: rbac-defaults\n  name: system:discovery\n  resourceVersion: \"24502985\"\n  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterrolebindings/system%3Adiscovery\n  uid: b7936268-5043-431a-a0e1-171a423abeb6\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:discovery\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: Group\n  name: system:authenticated\n- apiGroup: rbac.authorization.k8s.io\n  kind: Group\n  name: system:unauthenticated\n

Delete the entry for system:unauthenticated group from the \u201csubjects\u201d section in the above editor screen.

Repeat the same steps for system:basic-user ClusterRoleBinding.

"},{"location":"security/docs/iam/#reuse-aws-sdk-sessions-with-irsa","title":"Reuse AWS SDK sessions with IRSA","text":"

When you use IRSA, applications written using the AWS SDK use the token delivered to your pods to call sts:AssumeRoleWithWebIdentity to generate temporary AWS credentials. This is different from other AWS compute services, where the compute service delivers temporary AWS credentials directly to the AWS compute resource, such as a lambda function. This means that every time an AWS SDK session is initialized, a call to AWS STS for AssumeRoleWithWebIdentity is made. If your application scales rapidly and initializes many AWS SDK sessions, you may experience throttling from AWS STS as your code will be making many calls for AssumeRoleWithWebIdentity.

To avoid this scenario, we recommend reusing AWS SDK sessions within your application so that unnecessary calls to AssumeRoleWithWebIdentity are not made.

In the following example code, a session is created using the boto3 python SDK, and that same session is used to create clients and interact with both Amazon S3 and Amazon SQS. AssumeRoleWithWebIdentity is only called once, and the AWS SDK will refresh the credentials of my_session when they expire automatically.

import boto3\n\n# Create your own session\nmy_session = boto3.session.Session()\n\n# Now we can create low-level clients from our session\nsqs = my_session.client('sqs')\ns3 = my_session.client('s3')\n\ns3response = s3.list_buckets()\nsqsresponse = sqs.list_queues()\n\n\n#print the response from the S3 and SQS APIs\nprint(\"s3 response:\")\nprint(s3response)\nprint(\"---\")\nprint(\"sqs response:\")\nprint(sqsresponse)\n

If you're migrating an application from another AWS compute service, such as EC2, to EKS with IRSA, this is a particularly important detail. On other compute services initializing an AWS SDK session does not call AWS STS unless you instruct it to.

"},{"location":"security/docs/iam/#alternative-approaches","title":"Alternative approaches","text":"

While IRSA and EKS Pod Identities are the preferred ways to assign an AWS identity to a pod, they require that you include recent version of the AWS SDKs in your application. For a complete listing of the SDKs that currently support IRSA, see https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html, for EKS Pod Identities, see https://docs.aws.amazon.com/eks/latest/userguide/pod-id-minimum-sdk.html. If you have an application that you can't immediately update with a compatible SDK, there are several community-built solutions available for assigning IAM roles to Kubernetes pods, including kube2iam and kiam. Although AWS doesn't endorse, condone, nor support the use of these solutions, they are frequently used by the community at large to achieve similar results as IRSA and EKS Pod Identities.

If you need to use one of these non-aws provided solutions, please exercise due diligence and ensure you understand security implications of doing so.

"},{"location":"security/docs/iam/#tools-and-resources","title":"Tools and Resources","text":"
  • Amazon EKS Security Immersion Workshop - Identity and Access Management
  • Terraform EKS Blueprints Pattern - Fully Private Amazon EKS Cluster
  • Terraform EKS Blueprints Pattern - IAM Identity Center Single Sign-On for Amazon EKS Cluster
  • Terraform EKS Blueprints Pattern - Okta Single Sign-On for Amazon EKS Cluster
  • audit2rbac
  • rbac.dev A list of additional resources, including blogs and tools, for Kubernetes RBAC
  • Action Hero
  • kube2iam
  • kiam
"},{"location":"security/docs/image/","title":"Image security","text":"

You should consider the container image as your first line of defense against an attack. An insecure, poorly constructed image can allow an attacker to escape the bounds of the container and gain access to the host. Once on the host, an attacker can gain access to sensitive information or move laterally within the cluster or with your AWS account. The following best practices will help mitigate risk of this happening.

"},{"location":"security/docs/image/#recommendations","title":"Recommendations","text":""},{"location":"security/docs/image/#create-minimal-images","title":"Create minimal images","text":"

Start by removing all extraneous binaries from the container image. If you\u2019re using an unfamiliar image from Dockerhub, inspect the image using an application like Dive which can show you the contents of each of the container\u2019s layers. Remove all binaries with the SETUID and SETGID bits as they can be used to escalate privilege and consider removing all shells and utilities like nc and curl that can be used for nefarious purposes. You can find the files with SETUID and SETGID bits with the following command:

find / -perm /6000 -type f -exec ls -ld {} \\;\n

To remove the special permissions from these files, add the following directive to your container image:

RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} \\; || true\n

Colloquially, this is known as de-fanging your image.

"},{"location":"security/docs/image/#use-multi-stage-builds","title":"Use multi-stage builds","text":"

Using multi-stage builds is a way to create minimal images. Oftentimes, multi-stage builds are used to automate parts of the Continuous Integration cycle. For example, multi-stage builds can be used to lint your source code or perform static code analysis. This affords developers an opportunity to get near immediate feedback instead of waiting for a pipeline to execute. Multi-stage builds are attractive from a security standpoint because they allow you to minimize the size of the final image pushed to your container registry. Container images devoid of build tools and other extraneous binaries improves your security posture by reducing the attack surface of the image. For additional information about multi-stage builds, see Docker's multi-stage builds documentation.

"},{"location":"security/docs/image/#create-software-bill-of-materials-sboms-for-your-container-image","title":"Create Software Bill of Materials (SBOMs) for your container image","text":"

A \u201csoftware bill of materials\u201d (SBOM) is a nested inventory of the software artifacts that make up your container image. SBOM is a key building block in software security and software supply chain risk management. Generating, storing SBOMS in a central repository and scanning SBOMs for vulnerabilities helps address the following concerns:

  • Visibility: understand what components make up your container image. Storing in a central repository allows SBOMs to be audited and scanned anytime, even post deployment to detect and respond to new vulnerabilities such as zero day vulnerabilities.
  • Provenance Verification: assurance that existing assumptions of where and how an artifact originates from are true and that the artifact or its accompanying metadata have not been tampered with during the build or delivery processes.
  • Trustworthiness: assurance that a given artifact and its contents can be trusted to do what it is purported to do, i.e. is suitable for a purpose. This involves judgement on whether the code is safe to execute and making informed decisions about the risks associated with executing the code. Trustworthiness is assured by creating an attested pipeline execution report along with attested SBOM and attested CVE scan report to assure the consumers of the image that this image is in-fact created through secure means (pipeline) with secure components.
  • Dependency Trust Verification: recursive checking of an artifact\u2019s dependency tree for trustworthiness and provenance of the artifacts it uses. Drift in SBOMs can help detect malicious activity including unauthorized, untrusted dependencies, infiltration attempts.

The following tools can be used to generate SBOM:

  • Amazon Inspector can be used to create and export SBOMs.
  • Syft from Anchore can also be used for SBOM generation. For quicker vulnerability scans, the SBOM generated for a container image can be used as an input to scan. The SBOM and scan report are then attested and attached to the image before pushing the image to a central OCI repository such as Amazon ECR for review and audit purposes.

Learn more about securing your software supply chain by reviewing CNCF Software Supply Chain Best Practices guide.

"},{"location":"security/docs/image/#scan-images-for-vulnerabilities-regularly","title":"Scan images for vulnerabilities regularly","text":"

Like their virtual machine counterparts, container images can contain binaries and application libraries with vulnerabilities or develop vulnerabilities over time. The best way to safeguard against exploits is by regularly scanning your images with an image scanner. Images that are stored in Amazon ECR can be scanned on push or on-demand (once during a 24 hour period). ECR currently supports two types of scanning - Basic and Enhanced. Basic scanning leverages Clair an open source image scanning solution for no cost. Enhanced scanning uses Amazon Inspector to provide automatic continuous scans for additional cost. After an image is scanned, the results are logged to the event stream for ECR in EventBridge. You can also see the results of a scan from within the ECR console. Images with a HIGH or CRITICAL vulnerability should be deleted or rebuilt. If an image that has been deployed develops a vulnerability, it should be replaced as soon as possible.

Knowing where images with vulnerabilities have been deployed is essential to keeping your environment secure. While you could conceivably build an image tracking solution yourself, there are already several commercial offerings that provide this and other advanced capabilities out of the box, including:

  • Grype
  • Palo Alto - Prisma Cloud (twistcli)
  • Aqua
  • Kubei
  • Trivy
  • Snyk

A Kubernetes validation webhook could also be used to validate that images are free of critical vulnerabilities. Validation webhooks are invoked prior to the Kubernetes API. They are typically used to reject requests that don't comply with the validation criteria defined in the webhook. This is an example of a serverless webhook that calls the ECR describeImageScanFindings API to determine whether a pod is pulling an image with critical vulnerabilities. If vulnerabilities are found, the pod is rejected and a message with list of CVEs is returned as an Event.

"},{"location":"security/docs/image/#use-attestations-to-validate-artifact-integrity","title":"Use attestations to validate artifact integrity","text":"

An attestation is a cryptographically signed \u201cstatement\u201d that claims something - a \u201cpredicate\u201d e.g. a pipeline run or the SBOM or the vulnerability scan report is true about another thing - a \u201csubject\u201d i.e. the container image.

Attestations help users to validate that an artifact comes from a trusted source in the software supply chain. As an example, we may use a container image without knowing all the software components or dependencies that are included in that image. However, if we trust whatever the producer of the container image says about what software is present, we can use the producer\u2019s attestation to rely on that artifact. This means that we can proceed to use the artifact safely in our workflow in place of having done the analysis ourself.

  • Attestations can be created using AWS Signer or Sigstore cosign.
  • Kubernetes admission controllers such as Kyverno can be used to verify attestations.
  • Refer to this workshop to learn more about software supply chain management best practices on AWS using open source tools with topics including creating and attaching attestations to a container image.
"},{"location":"security/docs/image/#create-iam-policies-for-ecr-repositories","title":"Create IAM policies for ECR repositories","text":"

Nowadays, it is not uncommon for an organization to have multiple development teams operating independently within a shared AWS account. If these teams don't need to share assets, you may want to create a set of IAM policies that restrict access to the repositories each team can interact with. A good way to implement this is by using ECR namespaces. Namespaces are a way to group similar repositories together. For example, all of the registries for team A can be prefaced with the team-a/ while those for team B can use the team-b/ prefix. The policy to restrict access might look like the following:

{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"AllowPushPull\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ecr:GetDownloadUrlForLayer\",\n        \"ecr:BatchGetImage\",\n        \"ecr:BatchCheckLayerAvailability\",\n        \"ecr:PutImage\",\n        \"ecr:InitiateLayerUpload\",\n        \"ecr:UploadLayerPart\",\n        \"ecr:CompleteLayerUpload\"\n      ],\n      \"Resource\": [\n        \"arn:aws:ecr:<region>:<account_id>:repository/team-a/*\"\n      ]\n    }\n  ]\n}\n
"},{"location":"security/docs/image/#consider-using-ecr-private-endpoints","title":"Consider using ECR private endpoints","text":"

The ECR API has a public endpoint. Consequently, ECR registries can be accessed from the Internet so long as the request has been authenticated and authorized by IAM. For those who need to operate in a sandboxed environment where the cluster VPC lacks an Internet Gateway (IGW), you can configure a private endpoint for ECR. Creating a private endpoint enables you to privately access the ECR API through a private IP address instead of routing traffic across the Internet. For additional information on this topic, see Amazon ECR interface VPC endpoints.

"},{"location":"security/docs/image/#implement-endpoint-policies-for-ecr","title":"Implement endpoint policies for ECR","text":"

The default endpoint policy for allows access to all ECR repositories within a region. This might allow an attacker/insider to exfiltrate data by packaging it as a container image and pushing it to a registry in another AWS account. Mitigating this risk involves creating an endpoint policy that limits API access to ECR repositories. For example, the following policy allows all AWS principles in your account to perform all actions against your and only your ECR repositories:

{\n  \"Statement\": [\n    {\n      \"Sid\": \"LimitECRAccess\",\n      \"Principal\": \"*\",\n      \"Action\": \"*\",\n      \"Effect\": \"Allow\",\n      \"Resource\": \"arn:aws:ecr:<region>:<account_id>:repository/*\"\n    }\n  ]\n}\n

You can enhance this further by setting a condition that uses the new PrincipalOrgID attribute which will prevent pushing/pulling of images by an IAM principle that is not part of your AWS Organization. See, aws:PrincipalOrgID for additional details. We recommended applying the same policy to both the com.amazonaws.<region>.ecr.dkr and the com.amazonaws.<region>.ecr.api endpoints. Since EKS pulls images for kube-proxy, coredns, and aws-node from ECR, you will need to add the account ID of the registry, e.g. 602401143452.dkr.ecr.us-west-2.amazonaws.com/* to the list of resources in the endpoint policy or alter the policy to allow pulls from \"*\" and restrict pushes to your account ID. The table below reveals the mapping between the AWS accounts where EKS images are vended from and cluster region.

Account Number Region 602401143452 All commercial regions except for those listed below --- --- 800184023465 ap-east-1 - Asia Pacific (Hong Kong) 558608220178 me-south-1 - Middle East (Bahrain) 918309763551 cn-north-1 - China (Beijing) 961992271922 cn-northwest-1 - China (Ningxia)

For further information about using endpoint policies, see Using VPC endpoint policies to control Amazon ECR access.

"},{"location":"security/docs/image/#implement-lifecycle-policies-for-ecr","title":"Implement lifecycle policies for ECR","text":"

The NIST Application Container Security Guide warns about the risk of \"stale images in registries\", noting that over time old images with vulnerable, out-of-date software packages should be removed to prevent accidental deployment and exposure. Each ECR repository can have a lifecycle policy that sets rules for when images expire. The AWS official documentation describes how to set up test rules, evaluate them and then apply them. There are several lifecycle policy examples in the official docs that show different ways of filtering the images in a repository:

  • Filtering by image age or count
  • Filtering by tagged or untagged images
  • Filtering by image tags, either in multiple rules or a single rule
Warning

If the image for long running application is purged from ECR, it can cause an image pull errors when the application is redeployed or scaled horizontally. When using image lifecycle policies, be sure you have good CI/CD practices in place to keep deployments and the images that they reference up to date and always create [image] expiry rules that account for how often you do releases/deployments.

"},{"location":"security/docs/image/#create-a-set-of-curated-images","title":"Create a set of curated images","text":"

Rather than allowing developers to create their own images, consider creating a set of vetted images for the different application stacks in your organization. By doing so, developers can forego learning how to compose Dockerfiles and concentrate on writing code. As changes are merged into Master, a CI/CD pipeline can automatically compile the asset, store it in an artifact repository and copy the artifact into the appropriate image before pushing it to a Docker registry like ECR. At the very least you should create a set of base images from which developers to create their own Dockerfiles. Ideally, you want to avoid pulling images from Dockerhub because 1/ you don't always know what is in the image and 2/ about a fifth of the top 1000 images have vulnerabilities. A list of those images and their vulnerabilities can be found here.

"},{"location":"security/docs/image/#add-the-user-directive-to-your-dockerfiles-to-run-as-a-non-root-user","title":"Add the USER directive to your Dockerfiles to run as a non-root user","text":"

As was mentioned in the pod security section, you should avoid running container as root. While you can configure this as part of the podSpec, it is a good habit to use the USER directive to your Dockerfiles. The USER directive sets the UID to use when running RUN, ENTRYPOINT, or CMD instruction that appears after the USER directive.

"},{"location":"security/docs/image/#lint-your-dockerfiles","title":"Lint your Dockerfiles","text":"

Linting can be used to verify that your Dockerfiles are adhering to a set of predefined guidelines, e.g. the inclusion of the USER directive or the requirement that all images be tagged. dockerfile_lint is an open source project from RedHat that verifies common best practices and includes a rule engine that you can use to build your own rules for linting Dockerfiles. It can be incorporated into a CI pipeline, in that builds with Dockerfiles that violate a rule will automatically fail.

"},{"location":"security/docs/image/#build-images-from-scratch","title":"Build images from Scratch","text":"

Reducing the attack surface of your container images should be primary aim when building images. The ideal way to do this is by creating minimal images that are devoid of binaries that can be used to exploit vulnerabilities. Fortunately, Docker has a mechanism to create images from scratch. With languages like Go, you can create a static linked binary and reference it in your Dockerfile as in this example:

############################\n# STEP 1 build executable binary\n############################\nFROM golang:alpine AS builder# Install git.\n# Git is required for fetching the dependencies.\nRUN apk update && apk add --no-cache gitWORKDIR $GOPATH/src/mypackage/myapp/COPY . . # Fetch dependencies.\n# Using go get.\nRUN go get -d -v# Build the binary.\nRUN go build -o /go/bin/hello\n\n############################\n# STEP 2 build a small image\n############################\nFROM scratch# Copy our static executable.\nCOPY --from=builder /go/bin/hello /go/bin/hello# Run the hello binary.\nENTRYPOINT [\"/go/bin/hello\"]\n

This creates a container image that consists of your application and nothing else, making it extremely secure.

"},{"location":"security/docs/image/#use-immutable-tags-with-ecr","title":"Use immutable tags with ECR","text":"

Immutable tags force you to update the image tag on each push to the image repository. This can thwart an attacker from overwriting an image with a malicious version without changing the image's tags. Additionally, it gives you a way to easily and uniquely identify an image.

"},{"location":"security/docs/image/#sign-your-images-sboms-pipeline-runs-and-vulnerability-reports","title":"Sign your images, SBOMs, pipeline runs and vulnerability reports","text":"

When Docker was first introduced, there was no cryptographic model for verifying container images. With v2, Docker added digests to the image manifest. This allowed an image\u2019s configuration to be hashed and for the hash to be used to generate an ID for the image. When image signing is enabled, the Docker engine verifies the manifest\u2019s signature, ensuring that the content was produced from a trusted source and no tampering has occurred. After each layer is downloaded, the engine verifies the digest of the layer, ensuring that the content matches the content specified in the manifest. Image signing effectively allows you to create a secure supply chain, through the verification of digital signatures associated with the image.

We can use AWS Signer or Sigstore Cosign, to sign container images, create attestations for SBOMs, vulnerability scan reports and pipeline run reports. These attestations assure the trustworthiness and integrity of the image, that it is in fact created by the trusted pipeline without any interference or tampering, and that it contains only the software components that are documented (in the SBOM) that is verified and trusted by the image publisher. These attestations can be attached to the container image and pushed to the repository.

In the next section we will see how to use the attested artifacts for audits and admissions controller verification.

"},{"location":"security/docs/image/#image-integrity-verification-using-kubernetes-admission-controller","title":"Image integrity verification using Kubernetes admission controller","text":"

We can verify image signatures, attested artifacts in an automated way before deploying the image to target Kubernetes cluster using dynamic admission controller and admit deployments only when the security metadata of the artifacts comply with the admission controller policies.

For example we can write a policy that cryptographically verifies the signature of an image, an attested SBOM, attested pipeline run report, or attested CVE scan report. We can write conditions in the policy to check data in the report, e.g. a CVE scan should not have any critical CVEs. Deployment is allowed only for images that satisfy these conditions and all other deployments will be rejected by the admissions controller.

Examples of admission controller include:

  • Kyverno
  • OPA Gatekeeper
  • Portieris
  • Ratify
  • Kritis
  • Grafeas tutorial
  • Voucher
"},{"location":"security/docs/image/#update-the-packages-in-your-container-images","title":"Update the packages in your container images","text":"

You should include RUN apt-get update && apt-get upgrade in your Dockerfiles to upgrade the packages in your images. Although upgrading requires you to run as root, this occurs during image build phase. The application doesn't need to run as root. You can install the updates and then switch to a different user with the USER directive. If your base image runs as a non-root user, switch to root and back; don't solely rely on the maintainers of the base image to install the latest security updates.

Run apt-get clean to delete the installer files from /var/cache/apt/archives/. You can also run rm -rf /var/lib/apt/lists/* after installing packages. This removes the index files or the lists of packages that are available to install. Be aware that these commands may be different for each package manager. For example:

RUN apt-get update && apt-get install -y \\\n    curl \\\n    git \\\n    libsqlite3-dev \\\n    && apt-get clean && rm -rf /var/lib/apt/lists/*\n
"},{"location":"security/docs/image/#tools-and-resources","title":"Tools and resources","text":"
  • Amazon EKS Security Immersion Workshop - Image Security
  • docker-slim Build secure minimal images
  • dockle Verifies that your Dockerfile aligns with best practices for creating secure images
  • dockerfile-lint Rule based linter for Dockerfiles
  • hadolint A smart dockerfile linter
  • Gatekeeper and OPA A policy based admission controller
  • Kyverno A Kubernetes-native policy engine
  • in-toto Allows the user to verify if a step in the supply chain was intended to be performed, and if the step was performed by the right actor
  • Notary A project for signing container images
  • Notary v2
  • Grafeas An open artifact metadata API to audit and govern your software supply chain
  • NeuVector by SUSE open source, zero-trust container security platform, provides container, image and registry scanning for vulnerabilities, secrets and compliance.
"},{"location":"security/docs/incidents/","title":"Incident response and forensics","text":"

Your ability to react quickly to an incident can help minimize damage caused from a breach. Having a reliable alerting system that can warn you of suspicious behavior is the first step in a good incident response plan. When an incident does arise, you have to quickly decide whether to destroy and replace the effected container, or isolate and inspect the container. If you choose to isolate the container as part of a forensic investigation and root cause analysis, then the following set of activities should be followed:

"},{"location":"security/docs/incidents/#sample-incident-response-plan","title":"Sample incident response plan","text":""},{"location":"security/docs/incidents/#identify-the-offending-pod-and-worker-node","title":"Identify the offending Pod and worker node","text":"

Your first course of action should be to isolate the damage. Start by identifying where the breach occurred and isolate that Pod and its node from the rest of the infrastructure.

"},{"location":"security/docs/incidents/#identify-the-offending-pods-and-worker-nodes-using-workload-name","title":"Identify the offending Pods and worker nodes using workload name","text":"

If you know the name and namespace of the offending pod, you can identify the worker node running the pod as follows:

kubectl get pods <name> --namespace <namespace> -o=jsonpath='{.spec.nodeName}{\"\\n\"}'   \n

If a Workload Resource such as a Deployment has been compromised, it is likely that all the pods that are part of the workload resource are compromised. Use the following command to list all the pods of the Workload Resource and the nodes they are running on:

selector=$(kubectl get deployments <name> \\\n --namespace <namespace> -o json | jq -j \\\n'.spec.selector.matchLabels | to_entries | .[] | \"\\(.key)=\\(.value)\"')\n\nkubectl get pods --namespace <namespace> --selector=$selector \\\n-o json | jq -r '.items[] | \"\\(.metadata.name) \\(.spec.nodeName)\"'\n

The above command is for deployments. You can run the same command for other workload resources such as replicasets,, statefulsets, etc.

"},{"location":"security/docs/incidents/#identify-the-offending-pods-and-worker-nodes-using-service-account-name","title":"Identify the offending Pods and worker nodes using service account name","text":"

In some cases, you may identify that a service account is compromised. It is likely that pods using the identified service account are compromised. You can identify all the pods using the service account and nodes they are running on with the following command:

kubectl get pods -o json --namespace <namespace> | \\\n    jq -r '.items[] |\n    select(.spec.serviceAccount == \"<service account name>\") |\n    \"\\(.metadata.name) \\(.spec.nodeName)\"'\n
"},{"location":"security/docs/incidents/#identify-pods-with-vulnerable-or-compromised-images-and-worker-nodes","title":"Identify Pods with vulnerable or compromised images and worker nodes","text":"

In some cases, you may discover that a container image being used in pods on your cluster is malicious or compromised. A container image is malicious or compromised, if it was found to contain malware, is a known bad image or has a CVE that has been exploited. You should consider all the pods using the container image compromised. You can identify the pods using the image and nodes they are running on with the following command:

IMAGE=<Name of the malicious/compromised image>\n\nkubectl get pods -o json --all-namespaces | \\\n    jq -r --arg image \"$IMAGE\" '.items[] | \n    select(.spec.containers[] | .image == $image) | \n    \"\\(.metadata.name) \\(.metadata.namespace) \\(.spec.nodeName)\"'\n
"},{"location":"security/docs/incidents/#isolate-the-pod-by-creating-a-network-policy-that-denies-all-ingress-and-egress-traffic-to-the-pod","title":"Isolate the Pod by creating a Network Policy that denies all ingress and egress traffic to the pod","text":"

A deny all traffic rule may help stop an attack that is already underway by severing all connections to the pod. The following Network Policy will apply to a pod with the label app=web.

apiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: default-deny\nspec:\n  podSelector:\n    matchLabels: \n      app: web\n  policyTypes:\n  - Ingress\n  - Egress\n

Attention

A Network Policy may prove ineffective if an attacker has gained access to underlying host. If you suspect that has happened, you can use AWS Security Groups to isolate a compromised host from other hosts. When changing a host's security group, be aware that it will impact all containers running on that host.

"},{"location":"security/docs/incidents/#revoke-temporary-security-credentials-assigned-to-the-pod-or-worker-node-if-necessary","title":"Revoke temporary security credentials assigned to the pod or worker node if necessary","text":"

If the worker node has been assigned an IAM role that allows Pods to gain access to other AWS resources, remove those roles from the instance to prevent further damage from the attack. Similarly, if the Pod has been assigned an IAM role, evaluate whether you can safely remove the IAM policies from the role without impacting other workloads.

"},{"location":"security/docs/incidents/#cordon-the-worker-node","title":"Cordon the worker node","text":"

By cordoning the impacted worker node, you're informing the scheduler to avoid scheduling pods onto the affected node. This will allow you to remove the node for forensic study without disrupting other workloads.

Info

This guidance is not applicable to Fargate where each Fargate pod run in its own sandboxed environment. Instead of cordoning, sequester the affected Fargate pods by applying a network policy that denies all ingress and egress traffic.

"},{"location":"security/docs/incidents/#enable-termination-protection-on-impacted-worker-node","title":"Enable termination protection on impacted worker node","text":"

An attacker may attempt to erase their misdeeds by terminating an affected node. Enabling termination protection can prevent this from happening. Instance scale-in protection will protect the node from a scale-in event.

Warning

You cannot enable termination protection on a Spot instance.

"},{"location":"security/docs/incidents/#label-the-offending-podnode-with-a-label-indicating-that-it-is-part-of-an-active-investigation","title":"Label the offending Pod/Node with a label indicating that it is part of an active investigation","text":"

This will serve as a warning to cluster administrators not to tamper with the affected Pods/Nodes until the investigation is complete.

"},{"location":"security/docs/incidents/#capture-volatile-artifacts-on-the-worker-node","title":"Capture volatile artifacts on the worker node","text":"
  • Capture the operating system memory. This will capture the Docker daemon (or other container runtime) and its subprocesses per container. This can be accomplished using tools like LiME and Volatility, or through higher-level tools such as Automated Forensics Orchestrator for Amazon EC2 that build on top of them.
  • Perform a netstat tree dump of the processes running and the open ports. This will capture the docker daemon and its subprocess per container.
  • Run commands to save container-level state before evidence is altered. You can use capabilities of the container runtime to capture information about currently running containers. For example, with Docker, you could do the following:
  • docker top CONTAINER for processes running.
  • docker logs CONTAINER for daemon level held logs.
  • docker inspect CONTAINER for various information about the container.

    The same could be achieved with containerd using the nerdctl CLI, in place of docker (e.g. nerdctl inspect). Some additional commands are available depending on the container runtime. For example, Docker has docker diff to see changes to the container filesystem or docker checkpoint to save all container state including volatile memory (RAM). See this Kubernetes blog post for discussion of similar capabilities with containerd or CRI-O runtimes.

  • Pause the container for forensic capture.

  • Snapshot the instance's EBS volumes.
"},{"location":"security/docs/incidents/#redeploy-compromised-pod-or-workload-resource","title":"Redeploy compromised Pod or Workload Resource","text":"

Once you have gathered data for forensic analysis, you can redeploy the compromised pod or workload resource.

First roll out the fix for the vulnerability that was compromised and start new replacement pods. Then delete the vulnerable pods.

If the vulnerable pods are managed by a higher-level Kubernetes workload resource (for example, a Deployment or DaemonSet), deleting them will schedule new ones. So vulnerable pods will be launched again. In that case you should deploy a new replacement workload resource after fixing the vulnerability. Then you should delete the vulnerable workload.

"},{"location":"security/docs/incidents/#recommendations","title":"Recommendations","text":""},{"location":"security/docs/incidents/#review-the-aws-security-incident-response-whitepaper","title":"Review the AWS Security Incident Response Whitepaper","text":"

While this section gives a brief overview along with a few recommendations for handling suspected security breaches, the topic is exhaustively covered in the white paper, AWS Security Incident Response.

"},{"location":"security/docs/incidents/#practice-security-game-days","title":"Practice security game days","text":"

Divide your security practitioners into 2 teams: red and blue. The red team will be focused on probing different systems for vulnerabilities while the blue team will be responsible for defending against them. If you don't have enough security practitioners to create separate teams, consider hiring an outside entity that has knowledge of Kubernetes exploits.

Kubesploit is a penetration testing framework from CyberArk that you can use to conduct game days. Unlike other tools which scan your cluster for vulnerabilities, kubesploit simulates a real-world attack. This gives your blue team an opportunity to practice its response to an attack and gauge its effectiveness.

"},{"location":"security/docs/incidents/#run-penetration-tests-against-your-cluster","title":"Run penetration tests against your cluster","text":"

Periodically attacking your own cluster can help you discover vulnerabilities and misconfigurations. Before getting started, follow the penetration test guidelines before conducting a test against your cluster.

"},{"location":"security/docs/incidents/#tools-and-resources","title":"Tools and resources","text":"
  • kube-hunter, a penetration testing tool for Kubernetes.
  • Gremlin, a chaos engineering toolkit that you can use to simulate attacks against your applications and infrastructure.
  • Attacking and Defending Kubernetes Installations
  • kubesploit
  • NeuVector by SUSE open source, zero-trust container security platform, provides vulnerability- and risk reporting as well as security event notification
  • Advanced Persistent Threats
  • Kubernetes Practical Attack and Defense
  • Compromising Kubernetes Cluster by Exploiting RBAC Permissions
"},{"location":"security/docs/multiaccount/","title":"Multi Account Strategy","text":"

AWS recommends using a multi account strategy and AWS organizations to help isolate and manage your business applications and data. There are many benefits to using a multi account strategy:

  • Increased AWS API service quotas. Quotas are applied to AWS accounts, and using multiple accounts for your workloads increases the overall quota available to your workloads.
  • Simpler Identity and Access Management (IAM) policies. Granting workloads and the operators that support them access to only their own AWS accounts means less time crafting fine-grained IAM policies to achieve the principle of least privilege.
  • Improved Isolation of AWS resources. By design, all resources provisioned within an account are logically isolated from resources provisioned in other accounts. This isolation boundary provides you with a way to limit the risks of an application-related issue, misconfiguration, or malicious actions. If an issue occurs within one account, impacts to workloads contained in other accounts can be either reduced or eliminated.
  • More benefits, as described in the AWS Multi Account Strategy Whitepaper

The following sections will explain how to implement a multi account strategy for your EKS workloads using either a centralized, or de-centralized EKS cluster approach.

"},{"location":"security/docs/multiaccount/#planning-for-a-multi-workload-account-strategy-for-multi-tenant-clusters","title":"Planning for a Multi Workload Account Strategy for Multi Tenant Clusters","text":"

In a multi account AWS strategy, resources that belong to a given workload such as S3 buckets, ElastiCache clusters and DynamoDB Tables are all created in an AWS account that contains all the resources for that workload. These are referred to as a workload account, and the EKS cluster is deployed into an account referred to as the cluster account. Cluster accounts will be explored in the next section. Deploying resources into a dedicated workload account is similar to deploying kubernetes resources into a dedicated namespace.

Workload accounts can then be further broken down by software development lifecycle or other requirements if appropriate. For example a given workload can have a production account, a development account, or accounts for hosting instances of that workload in a specific region. More information is available in this AWS whitepaper.

You can adopt the following approaches when implementing EKS Multi account strategy:

"},{"location":"security/docs/multiaccount/#centralized-eks-cluster","title":"Centralized EKS Cluster","text":"

In this approach, your EKS Cluster will be deployed in a single AWS account called the Cluster Account. Using IAM roles for Service Accounts (IRSA) or EKS Pod Identities to deliver temporary AWS credentials and AWS Resource Access Manager (RAM) to simplify network access, you can adopt a multi account strategy for your multi tenant EKS cluster. The cluster account will contain the VPC, subnets, EKS cluster, EC2/Fargate compute resources (worker nodes), and any additional networking configurations needed to run your EKS cluster.

In a multi workload account strategy for multi tenant cluster, AWS accounts typically align with kubernetes namespaces as a mechanism for isolating groups of resources. Best practices for tenant isolation within an EKS cluster should still be followed when implementing a multi account strategy for multi tenant EKS clusters.

It is possible to have multiple Cluster Accounts in your AWS organization, and it is a best practice to have multiple Cluster Accounts that align with your software development lifecycle needs. For workloads operating at a very large scale, you may require multiple Cluster Accounts to ensure that there are enough kubernetes and AWS service quotas available to all your workloads.

In the above diagram, AWS RAM is used to share subnets from a cluster account into a workload account. Then workloads running in EKS pods use IRSA or EKS Pod Identities and role chaining to assume a role in their workload account and access their AWS resources."},{"location":"security/docs/multiaccount/#implementing-a-multi-workload-account-strategy-for-multi-tenant-cluster","title":"Implementing a Multi Workload Account Strategy for Multi Tenant Cluster","text":""},{"location":"security/docs/multiaccount/#sharing-subnets-with-aws-resource-access-manager","title":"Sharing Subnets With AWS Resource Access Manager","text":"

AWS Resource Access Manager (RAM) allows you to share resources across AWS accounts.

If RAM is enabled for your AWS Organization, you can share the VPC Subnets from the Cluster account to your workload accounts. This will allow AWS resources owned by your workload accounts, such as Amazon ElastiCache Clusters or Amazon Relational Database Service (RDS) Databases to be deployed into the same VPC as your EKS cluster, and be consumable by the workloads running on your EKS cluster.

To share a resource via RAM, open up RAM in the AWS console of the cluster account and select \"Resource Shares\" and \"Create Resource Share\". Name your Resource Share and Select the subnets you want to share. Select Next again and enter the 12 digit account IDs for the workload accounts you wish to share the subnets with, select next again, and click Create resource share to finish. After this step, the workload account can deploy resources into those subnets.

RAM shares can also be created programmatically, or with infrastructure as code.

"},{"location":"security/docs/multiaccount/#choosing-between-eks-pod-identities-and-irsa","title":"Choosing Between EKS Pod Identities and IRSA","text":"

At re:Invent 2023, AWS launched EKS Pod Identities as a simpler way of delivering temporary AWS credentials to your pods on EKS. Both IRSA and EKS Pod Identities are valid methods for delivering temporary AWS credentials to your EKS pods and will continue to be supported. You should consider which method of delivering best meets your needs.

When working with a EKS cluster and multiple AWS accounts, IRSA can directly assume roles in AWS accounts other than the account the EKS cluster is hosted in directly, while EKS Pod identities require you to configure role chaining. Refer EKS documentation for an in-depth comparison.

"},{"location":"security/docs/multiaccount/#accessing-aws-api-resources-with-iam-roles-for-service-accounts","title":"Accessing AWS API Resources with IAM Roles For Service Accounts","text":"

IAM Roles for Service Accounts (IRSA) allows you to deliver temporary AWS credentials to your workloads running on EKS. IRSA can be used to get temporary credentials for IAM roles in the workload accounts from the cluster account. This allows your workloads running on your EKS clusters in the cluster account to consume AWS API resources, such as S3 buckets hosted in the workload account seemlessly, and use IAM authentication for resources like Amazon RDS Databases or Amazon EFS FileSystems.

AWS API resources and other Resources that use IAM authentication in a workload account can only be accessed by credentials for IAM roles in that same workload account, except where cross account access is capable and has been explicity enabled.

"},{"location":"security/docs/multiaccount/#enabling-irsa-for-cross-account-access","title":"Enabling IRSA for cross account access","text":"

To enable IRSA for workloads in your Cluster Account to access resources in your Workload accounts, you first must create an IAM OIDC identity provider in your workload account. This can be done with the same procedure for setting up IRSA, except the Identity Provider will be created in the workload account.

Then when configuring IRSA for your workloads on EKS, you can follow the same steps as the documentation, but use the 12 digit account id of the workload account as mentioned in the section \"Example Create an identity provider from another account's cluster\".

After this is configured, your application running in EKS will be able to directly use its service account to assume a role in the workload account, and use resources within it.

"},{"location":"security/docs/multiaccount/#accessing-aws-api-resources-with-eks-pod-identities","title":"Accessing AWS API Resources with EKS Pod Identities","text":"

EKS Pod Identities is a new way of delivering AWS credentials to your workloads running on EKS. EKS pod identities simplifies the configuration of AWS resources as you no longer need to manage OIDC configurations to deliver AWS credentials to your pods on EKS.

"},{"location":"security/docs/multiaccount/#enabling-eks-pod-identities-for-cross-account-access","title":"Enabling EKS Pod Identities for cross account access","text":"

Unlike IRSA, EKS Pod Identities can only be used to directly grant access to a role in the same account as the EKS cluster. To access a role in another AWS account, pods that use EKS Pod Identities must perform Role Chaining.

Role chaining can be configured in an applications profile with their aws configuration file using the Process Credentials Provider available in various AWS SDKs. credential_process can be used as a credential source when configuring a profile, such as:

# Content of the AWS Config file\n[profile account_b_role] \nsource_profile = account_a_role \nrole_arn = arn:aws:iam::444455556666:role/account-b-role\n\n[profile account_a_role] \ncredential_process = /eks-credential-processrole.sh\n

The source of the script called by credential_process:

#!/bin/bash\n# Content of the eks-credential-processrole.sh\n# This will retreive the credential from the pod identities agent,\n# and return it to the AWS SDK when referenced in a profile\ncurl -H \"Authorization: $(cat $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)\" $AWS_CONTAINER_CREDENTIALS_FULL_URI | jq -c '{AccessKeyId: .AccessKeyId, SecretAccessKey: .SecretAccessKey, SessionToken: .Token, Expiration: .Expiration, Version: 1}' \n

You can create an aws config file as shown above with both Account A and B roles and specify the AWS_CONFIG_FILE and AWS_PROFILE env vars in your pod spec. EKS Pod identity webhook does not override if the env vars already exists in the pod spec.

# Snippet of the PodSpec\ncontainers: \n  - name: container-name\n    image: container-image:version\n    env:\n    - name: AWS_CONFIG_FILE\n      value: path-to-customer-provided-aws-config-file\n    - name: AWS_PROFILE\n      value: account_b_role\n

When configuring role trust policies for role chaining with EKS pod identities, you can reference EKS specific attributes as session tags and use attribute based access control(ABAC) to limit access to your IAM roles to only specific EKS Pod identity sessions, such as the Kubernetes Service Account a pod belongs to.

Please note that some of these attributes may not be universally unique, for example two EKS clusters may have identical namespaces, and one cluster may have identically named service accounts across namespaces. So when granting access via EKS Pod Identities and ABAC, it is a best practice to always consider the cluster arn and namespace when granting access to a service account.

"},{"location":"security/docs/multiaccount/#abac-and-eks-pod-identities-for-cross-account-access","title":"ABAC and EKS Pod Identities for cross account access","text":"

When using EKS Pod Identities to assume roles (role chaining) in other accounts as part of a multi account strategy, you have the option to assign a unique IAM role for each service account that needs to access another account, or use a common IAM role across multiple service accounts and use ABAC to control what accounts it can access.

To use ABAC to control what service accounts can assume a role into another account with role chaining, you create a role trust policy statement that only allows a role to be assumed by a role session when the expected values are present. The following role trust policy will only let a role from the EKS cluster account (account ID 111122223333) assume a role if the kubernetes-service-account, eks-cluster-arn and kubernetes-namespace tags all have the expected value.

{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": \"arn:aws:iam::111122223333:root\"\n            },\n            \"Action\": \"sts:AssumeRole\",\n            \"Condition\": {\n                \"StringEquals\": {\n                    \"aws:PrincipalTag/kubernetes-service-account\": \"PayrollApplication\",\n                    \"aws:PrincipalTag/eks-cluster-arn\": \"arn:aws:eks:us-east-1:111122223333:cluster/ProductionCluster\",\n                    \"aws:PrincipalTag/kubernetes-namespace\": \"PayrollNamespace\"\n                }\n            }\n        }\n    ]\n}\n

When using this strategy it is a best practice to ensure that the common IAM role only has sts:AssumeRole permissions and no other AWS access.

It is important when using ABAC that you control who has the ability to tag IAM roles and users to only those who have a strict need to do so. Someone with the ability to tag an IAM role or user would be able to set tags on roles/users identical to what would be set by EKS Pod Identities and may be able to escalate their privileges. You can restrict who has the access to set tags the kubernetes- and eks- tags on IAM role and users using IAM policy, or Service Control Policy (SCP).

"},{"location":"security/docs/multiaccount/#de-centralized-eks-clusters","title":"De-centralized EKS Clusters","text":"

In this approach, EKS clusters are deployed to respective workload AWS Accounts and live along side with other AWS resources like Amazon S3 buckets, VPCs, Amazon DynamoDB tables, etc., Each workload account is independent, self-sufficient, and operated by respective Business Unit/Application teams. This model allows the creation of reusuable blueprints for various cluster capabilities (AI/ML cluster, Batch processing, General purpose, etc.,) and vend the clusters based on the application team requirements. Both application and platform teams operate out of their respective GitOps repositories to manage the deployments to the workload clusters.

In the above diagram, Amazon EKS clusters and other AWS resources are deployed to respective workload accounts. Then workloads running in EKS pods use IRSA or EKS Pod Identities to access their AWS resources.

GitOps is a way of managing application and infrastructure deployment so that the whole system is described declaratively in a Git repository. It\u2019s an operational model that offers you the ability to manage the state of multiple Kubernetes clusters using the best practices of version control, immutable artifacts, and automation. In this multi cluster model, each workload cluster is bootstrapped with multiple Git repos, allowing each team (application, platform, security, etc.,) to deploy their respective changes on the cluster.

You would utilize IAM roles for Service Accounts (IRSA) or EKS Pod Identities in each account to allow your EKS workloads to get temporary aws credentials to securely access other AWS resources. IAM roles are created in respective workload AWS Accounts and map them to k8s service accounts to provide temporary IAM access. So, no cross-account access is required in this approach. Follow the IAM roles for Service Accounts documentation on how to setup in each workload for IRSA, and EKS Pod Identities documentation on how to setup EKS pod identities in each account.

"},{"location":"security/docs/multiaccount/#centralized-networking","title":"Centralized Networking","text":"

You can also utilize AWS RAM to share the VPC Subnets to workload accounts and launch Amazon EKS clusters and other AWS resources in them. This enables centralized network managment/administration, simplified network connectivity, and de-centralized EKS clusters. Refer this AWS blog for a detailed walkthrough and considerations of this approach.

In the above diagram, AWS RAM is used to share subnets from a central networking account into a workload account. Then EKS cluster and other AWS resources are launched in those subnets in respective workload accounts. EKS pods use IRSA or EKS Pod Identities to access their AWS resources."},{"location":"security/docs/multiaccount/#centralized-vs-de-centralized-eks-clusters","title":"Centralized vs De-centralized EKS clusters","text":"

The decision to run with a Centralized or De-centralized will depend on your requirements. This table demonstrates the key differences with each strategy.

# Centralized EKS cluster De-centralized EKS clusters Cluster Management: Managing a single EKS cluster is easier than administrating multiple clusters An Efficient cluster management automation is necessary to reduce the operational overhead of managing multiple EKS clusters Cost Efficiency: Allows reuse of EKS cluster and network resources, which promotes cost efficiency Requires networking and cluster setups per workload, which requires additional resources Resilience: Multiple workloads on the centralized cluster may be impacted if a cluster becomes impaired If a cluster becomes impaired, the damage is limited to only the workloads that run on that cluster. All other workloads are unaffected Isolation & Security: Isolation/Soft Multi-tenancy is achieved using k8s native constructs like Namespaces. Workloads may share the underlying resources like CPU, memory, etc. AWS resources are isolated into their own workload accounts which by default are not accessible from other AWS accounts. Stronger isolation on compute resources as the workloads run in individual clusters and nodes that don't share any resources. AWS resources are isolated into their own workload accounts which by default are not accessible from other AWS accounts. Performance & Scalabity: As workloads grow to very large scales you may encounter kubernetes and AWS service quotas in the cluster account. You can deploy addtional cluster accounts to scale even further As more clusters and VPCs are present, each workload has more available k8s and AWS service quota Networking: Single VPC is used per cluster, allowing for simpler connectivity for applications on that cluster Routing must be established between the de-centralized EKS cluster VPCs Kubernetes Access Management: Need to maintain many different roles and users in the cluster to provide access to all workload teams and ensure kubernetes resources are properly segregated Simplified access management as each cluster is dedicated to a workload/team AWS Access Management: AWS resources are deployed into to their own account which can only be accessed by default with IAM roles in the workload account. IAM roles in the workload accounts are assumed cross account either with IRSA or EKS Pod Identities. AWS resources are deployed into to their own account which can only be accessed by default with IAM roles in the workload account. IAM roles in the workload accounts are delivered directly to pods with IRSA or EKS Pod Identities"},{"location":"security/docs/multitenancy/","title":"Tenant Isolation","text":"

When we think of multi-tenancy, we often want to isolate a user or application from other users or applications running on a shared infrastructure.

Kubernetes is a single tenant orchestrator, i.e. a single instance of the control plane is shared among all the tenants within a cluster. There are, however, various Kubernetes objects that you can use to create the semblance of multi-tenancy. For example, Namespaces and Role-based access controls (RBAC) can be implemented to logically isolate tenants from each other. Similarly, Quotas and Limit Ranges can be used to control the amount of cluster resources each tenant can consume. Nevertheless, the cluster is the only construct that provides a strong security boundary. This is because an attacker that manages to gain access to a host within the cluster can retrieve all Secrets, ConfigMaps, and Volumes, mounted on that host. They could also impersonate the Kubelet which would allow them to manipulate the attributes of the node and/or move laterally within the cluster.

The following sections will explain how to implement tenant isolation while mitigating the risks of using a single tenant orchestrator like Kubernetes.

"},{"location":"security/docs/multitenancy/#soft-multi-tenancy","title":"Soft multi-tenancy","text":"

With soft multi-tenancy, you use native Kubernetes constructs, e.g. namespaces, roles and role bindings, and network policies, to create logical separation between tenants. RBAC, for example, can prevent tenants from accessing or manipulate each other's resources. Quotas and limit ranges control the amount of cluster resources each tenant can consume while network policies can help prevent applications deployed into different namespaces from communicating with each other.

None of these controls, however, prevent pods from different tenants from sharing a node. If stronger isolation is required, you can use a node selector, anti-affinity rules, and/or taints and tolerations to force pods from different tenants to be scheduled onto separate nodes; often referred to as sole tenant nodes. This could get rather complicated, and cost prohibitive, in an environment with many tenants.

Attention

Soft multi-tenancy implemented with Namespaces does not allow you to provide tenants with a filtered list of Namespaces because Namespaces are a globally scoped Type. If a tenant has the ability to view a particular Namespace, it can view all Namespaces within the cluster.

Warning

With soft-multi-tenancy, tenants retain the ability to query CoreDNS for all services that run within the cluster by default. An attacker could exploit this by running dig SRV *.*.svc.cluster.local from any pod in the cluster. If you need to restrict access to DNS records of services that run within your clusters, consider using the Firewall or Policy plugins for CoreDNS. For additional information, see https://github.com/coredns/policy#kubernetes-metadata-multi-tenancy-policy.

Kiosk is an open source project that can aid in the implementation of soft multi-tenancy. It is implemented as a series of CRDs and controllers that provide the following capabilities:

  • Accounts & Account Users to separate tenants in a shared Kubernetes cluster
  • Self-Service Namespace Provisioning for account users
  • Account Limits to ensure quality of service and fairness when sharing a cluster
  • Namespace Templates for secure tenant isolation and self-service namespace initialization

Loft is a commercial offering from the maintainers of Kiosk and DevSpace that adds the following capabilities:

  • Multi-cluster access for granting access to spaces in different clusters
  • Sleep mode scales down deployments in a space during periods of inactivity
  • Single sign-on with OIDC authentication providers like GitHub

There are three primary use cases that can be addressed by soft multi-tenancy.

"},{"location":"security/docs/multitenancy/#enterprise-setting","title":"Enterprise Setting","text":"

The first is in an Enterprise setting where the \"tenants\" are semi-trusted in that they are employees, contractors, or are otherwise authorized by the organization. Each tenant will typically align to an administrative division such as a department or team.

In this type of setting, a cluster administrator will usually be responsible for creating namespaces and managing policies. They may also implement a delegated administration model where certain individuals are given oversight of a namespace, allowing them to perform CRUD operations for non-policy related objects like deployments, services, pods, jobs, etc.

The isolation provided by a container runtime may be acceptable within this setting or it may need to be augmented with additional controls for pod security. It may also be necessary to restrict communication between services in different namespaces if stricter isolation is required.

"},{"location":"security/docs/multitenancy/#kubernetes-as-a-service","title":"Kubernetes as a Service","text":"

By contrast, soft multi-tenancy can be used in settings where you want to offer Kubernetes as a service (KaaS). With KaaS, your application is hosted in a shared cluster along with a collection of controllers and CRDs that provide a set of PaaS services. Tenants interact directly with the Kubernetes API server and are permitted to perform CRUD operations on non-policy objects. There is also an element of self-service in that tenants may be allowed to create and manage their own namespaces. In this type of environment, tenants are assumed to be running untrusted code.

To isolate tenants in this type of environment, you will likely need to implement strict network policies as well as pod sandboxing. Sandboxing is where you run the containers of a pod inside a micro VM like Firecracker or in a user-space kernel. Today, you can create sandboxed pods with EKS Fargate.

"},{"location":"security/docs/multitenancy/#software-as-a-service-saas","title":"Software as a Service (SaaS)","text":"

The final use case for soft multi-tenancy is in a Software-as-a-Service (SaaS) setting. In this environment, each tenant is associated with a particular instance of an application that's running within the cluster. Each instance often has its own data and uses separate access controls that are usually independent of Kubernetes RBAC.

Unlike the other use cases, the tenant in a SaaS setting does not directly interface with the Kubernetes API. Instead, the SaaS application is responsible for interfacing with the Kubernetes API to create the necessary objects to support each tenant.

"},{"location":"security/docs/multitenancy/#kubernetes-constructs","title":"Kubernetes Constructs","text":"

In each of these instances the following constructs are used to isolate tenants from each other:

"},{"location":"security/docs/multitenancy/#namespaces","title":"Namespaces","text":"

Namespaces are fundamental to implementing soft multi-tenancy. They allow you to divide the cluster into logical partitions. Quotas, network policies, service accounts, and other objects needed to implement multi-tenancy are scoped to a namespace.

"},{"location":"security/docs/multitenancy/#network-policies","title":"Network policies","text":"

By default, all pods in a Kubernetes cluster are allowed to communicate with each other. This behavior can be altered using network policies.

Network policies restrict communication between pods using labels or IP address ranges. In a multi-tenant environment where strict network isolation between tenants is required, we recommend starting with a default rule that denies communication between pods, and another rule that allows all pods to query the DNS server for name resolution. With that in place, you can begin adding more permissive rules that allow for communication within a namespace. This can be further refined as required.

Note

Amazon VPC CNI now supports Kubernetes Network Policies to create policies that can isolate sensitive workloads and protect them from unauthorized access when running Kubernetes on AWS. This means that you can use all the capabilities of the Network Policy API within your Amazon EKS cluster. This level of granular control enables you to implement the principle of least privilege, which ensures that only authorized pods are allowed to communicate with each other.

Attention

Network policies are necessary but not sufficient. The enforcement of network policies requires a policy engine such as Calico or Cilium.

"},{"location":"security/docs/multitenancy/#role-based-access-control-rbac","title":"Role-based access control (RBAC)","text":"

Roles and role bindings are the Kubernetes objects used to enforce role-based access control (RBAC) in Kubernetes. Roles contain lists of actions that can be performed against objects in your cluster. Role bindings specify the individuals or groups to whom the roles apply. In the enterprise and KaaS settings, RBAC can be used to permit administration of objects by selected groups or individuals.

"},{"location":"security/docs/multitenancy/#quotas","title":"Quotas","text":"

Quotas are used to define limits on workloads hosted in your cluster. With quotas, you can specify the maximum amount of CPU and memory that a pod can consume, or you can limit the number of resources that can be allocated in a cluster or namespace. Limit ranges allow you to declare minimum, maximum, and default values for each limit.

Overcommitting resources in a shared cluster is often beneficial because it allows you maximize your resources. However, unbounded access to a cluster can cause resource starvation, which can lead to performance degradation and loss of application availability. If a pod's requests are set too low and the actual resource utilization exceeds the capacity of the node, the node will begin to experience CPU or memory pressure. When this happens, pods may be restarted and/or evicted from the node.

To prevent this from happening, you should plan to impose quotas on namespaces in a multi-tenant environment to force tenants to specify requests and limits when scheduling their pods on the cluster. It will also mitigate a potential denial of service by constraining the amount of resources a pod can consume.

You can also use quotas to apportion the cluster's resources to align with a tenant's spend. This is particularly useful in the KaaS scenario.

"},{"location":"security/docs/multitenancy/#pod-priority-and-preemption","title":"Pod priority and preemption","text":"

Pod priority and preemption can be useful when you want to provide more importance to a Pod relative to other Pods. For example, with pod priority you can configure pods from customer A to run at a higher priority than customer B. When there's insufficient capacity available, the scheduler will evict the lower-priority pods from customer B to accommodate the higher-priority pods from customer A. This can be especially handy in a SaaS environment where customers willing to pay a premium receive a higher priority.

Attention

Pods priority can have an undesired effect on other Pods with lower priority. For example, although the victim pods are terminated gracefully but the PodDisruptionBudget is not guaranteed, which could break a application with lower priority that relies on a quorum of Pods, see Limitations of preemption.

"},{"location":"security/docs/multitenancy/#mitigating-controls","title":"Mitigating controls","text":"

Your chief concern as an administrator of a multi-tenant environment is preventing an attacker from gaining access to the underlying host. The following controls should be considered to mitigate this risk:

"},{"location":"security/docs/multitenancy/#sandboxed-execution-environments-for-containers","title":"Sandboxed execution environments for containers","text":"

Sandboxing is a technique by which each container is run in its own isolated virtual machine. Technologies that perform pod sandboxing include Firecracker and Weave's Firekube.

For additional information about the effort to make Firecracker a supported runtime for EKS, see https://threadreaderapp.com/thread/1238496944684597248.html.

"},{"location":"security/docs/multitenancy/#open-policy-agent-opa-gatekeeper","title":"Open Policy Agent (OPA) & Gatekeeper","text":"

Gatekeeper is a Kubernetes admission controller that enforces policies created with OPA. With OPA you can create a policy that runs pods from tenants on separate instances or at a higher priority than other tenants. A collection of common OPA policies can be found in the GitHub repository for this project.

There is also an experimental OPA plugin for CoreDNS that allows you to use OPA to filter/control the records returned by CoreDNS.

"},{"location":"security/docs/multitenancy/#kyverno","title":"Kyverno","text":"

Kyverno is a Kubernetes native policy engine that can validate, mutate, and generate configurations with policies as Kubernetes resources. Kyverno uses Kustomize-style overlays for validation, supports JSON Patch and strategic merge patch for mutation, and can clone resources across namespaces based on flexible triggers.

You can use Kyverno to isolate namespaces, enforce pod security and other best practices, and generate default configurations such as network policies. Several examples are included in the GitHub repository for this project. Many others are included in the policy library on the Kyverno website.

"},{"location":"security/docs/multitenancy/#isolating-tenant-workloads-to-specific-nodes","title":"Isolating tenant workloads to specific nodes","text":"

Restricting tenant workloads to run on specific nodes can be used to increase isolation in the soft multi-tenancy model. With this approach, tenant-specific workloads are only run on nodes provisioned for the respective tenants. To achieve this isolation, native Kubernetes properties (node affinity, and taints and tolerations) are used to target specific nodes for pod scheduling, and prevent pods, from other tenants, from being scheduled on the tenant-specific nodes.

"},{"location":"security/docs/multitenancy/#part-1-node-affinity","title":"Part 1 - Node affinity","text":"

Kubernetes node affinity is used to target nodes for scheduling, based on node labels. With node affinity rules, the pods are attracted to specific nodes that match the selector terms. In the below pod specification, the requiredDuringSchedulingIgnoredDuringExecution node affinity is applied to the respective pod. The result is that the pod will target nodes that are labeled with the following key/value: node-restriction.kubernetes.io/tenant: tenants-x.

...\nspec:\n  affinity:\n    nodeAffinity:\n      requiredDuringSchedulingIgnoredDuringExecution:\n        nodeSelectorTerms:\n        - matchExpressions:\n          - key: node-restriction.kubernetes.io/tenant\n            operator: In\n            values:\n            - tenants-x\n...\n

With this node affinity, the label is required during scheduling, but not during execution; if the underlying nodes' labels change, the pods will not be evicted due solely to that label change. However, future scheduling could be impacted.

Warning

The label prefix of node-restriction.kubernetes.io/ has special meaning in Kubernetes. NodeRestriction which is enabled for EKS clusters prevents kubelet from adding/removing/updating labels with this prefix. Attackers aren't able to use the kubelet's credentials to update the node object or modify the system setup to pass these labels into kubelet as kubelet isn't allowed to modify these labels. If this prefix is used for all pod to node scheduling, it prevents scenarios where an attacker may want to attract a different set of workloads to a node by modifying the node labels.

Info

Instead of node affinity, we could have used the node selector. However, node affinity is more expressive and allows for more conditions to be considered during pod scheduling. For additional information about the differences and more advanced scheduling choices, please see this CNCF blog post on Advanced Kubernetes pod to node scheduling.

"},{"location":"security/docs/multitenancy/#part-2-taints-and-tolerations","title":"Part 2 - Taints and tolerations","text":"

Attracting pods to nodes is just the first part of this three-part approach. For this approach to work, we must repel pods from scheduling onto nodes for which the pods are not authorized. To repel unwanted or unauthorized pods, Kubernetes uses node taints. Taints are used to place conditions on nodes that prevent pods from being scheduled. The below taint uses a key-value pair of tenant: tenants-x.

...\n    taints:\n      - key: tenant\n        value: tenants-x\n        effect: NoSchedule\n...\n

Given the above node taint, only pods that tolerate the taint will be allowed to be scheduled on the node. To allow authorized pods to be scheduled onto the node, the respective pod specifications must include a toleration to the taint, as seen below.

...\n  tolerations:\n  - effect: NoSchedule\n    key: tenant\n    operator: Equal\n    value: tenants-x\n...\n

Pods with the above toleration will not be stopped from scheduling on the node, at least not because of that specific taint. Taints are also used by Kubernetes to temporarily stop pod scheduling during certain conditions, like node resource pressure. With node affinity, and taints and tolerations, we can effectively attract the desired pods to specific nodes and repel unwanted pods.

Attention

Certain Kubernetes pods are required to run on all nodes. Examples of these pods are those started by the Container Network Interface (CNI) and kube-proxy daemonsets. To that end, the specifications for these pods contain very permissive tolerations, to tolerate different taints. Care should be taken to not change these tolerations. Changing these tolerations could result in incorrect cluster operation. Additionally, policy-management tools, such as OPA/Gatekeeper and Kyverno can be used to write validating policies that prevent unauthorized pods from using these permissive tolerations.

"},{"location":"security/docs/multitenancy/#part-3-policy-based-management-for-node-selection","title":"Part 3 - Policy-based management for node selection","text":"

There are several tools that can be used to help manage the node affinity and tolerations of pod specifications, including enforcement of rules in CICD pipelines. However, enforcement of isolation should also be done at the Kubernetes cluster level. For this purpose, policy-management tools can be used to mutate inbound Kubernetes API server requests, based on request payloads, to apply the respective node affinity rules and tolerations mentioned above.

For example, pods destined for the tenants-x namespace can be stamped with the correct node affinity and toleration to permit scheduling on the tenants-x nodes. Utilizing policy-management tools configured using the Kubernetes Mutating Admission Webhook, policies can be used to mutate the inbound pod specifications. The mutations add the needed elements to allow desired scheduling. An example OPA/Gatekeeper policy that adds a node affinity is seen below.

apiVersion: mutations.gatekeeper.sh/v1alpha1\nkind: Assign\nmetadata:\n  name: mutator-add-nodeaffinity-pod\n  annotations:\n    aws-eks-best-practices/description: >-\n      Adds Node affinity - https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity\nspec:\n  applyTo:\n  - groups: [\"\"]\n    kinds: [\"Pod\"]\n    versions: [\"v1\"]\n  match:\n    namespaces: [\"tenants-x\"]\n  location: \"spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms\"\n  parameters:\n    assign:\n      value: \n        - matchExpressions:\n          - key: \"tenant\"\n            operator: In\n            values:\n            - \"tenants-x\"\n

The above policy is applied to a Kubernetes API server request, to apply a pod to the tenants-x namespace. The policy adds the requiredDuringSchedulingIgnoredDuringExecution node affinity rule, so that pods are attracted to nodes with the tenant: tenants-x label.

A second policy, seen below, adds the toleration to the same pod specification, using the same matching criteria of target namespace and groups, kinds, and versions.

apiVersion: mutations.gatekeeper.sh/v1alpha1\nkind: Assign\nmetadata:\n  name: mutator-add-toleration-pod\n  annotations:\n    aws-eks-best-practices/description: >-\n      Adds toleration - https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/\nspec:\n  applyTo:\n  - groups: [\"\"]\n    kinds: [\"Pod\"]\n    versions: [\"v1\"]\n  match:\n    namespaces: [\"tenants-x\"]\n  location: \"spec.tolerations\"\n  parameters:\n    assign:\n      value: \n      - key: \"tenant\"\n        operator: \"Equal\"\n        value: \"tenants-x\"\n        effect: \"NoSchedule\"\n

The above policies are specific to pods; this is due to the paths to the mutated elements in the policies' location elements. Additional policies could be written to handle resources that create pods, like Deployment and Job resources. The listed policies and other examples can been seen in the companion GitHub project for this guide.

The result of these two mutations is that pods are attracted to the desired node, while at the same time, not repelled by the specific node taint. To verify this, we can see the snippets of output from two kubectl calls to get the nodes labeled with tenant=tenants-x, and get the pods in the tenants-x namespace.

kubectl get nodes -l tenant=tenants-x\nNAME                                        \nip-10-0-11-255...\nip-10-0-28-81...\nip-10-0-43-107...\n\nkubectl -n tenants-x get pods -owide\nNAME                                  READY   STATUS    RESTARTS   AGE   IP            NODE\ntenant-test-deploy-58b895ff87-2q7xw   1/1     Running   0          13s   10.0.42.143   ip-10-0-43-107...\ntenant-test-deploy-58b895ff87-9b6hg   1/1     Running   0          13s   10.0.18.145   ip-10-0-28-81...\ntenant-test-deploy-58b895ff87-nxvw5   1/1     Running   0          13s   10.0.30.117   ip-10-0-28-81...\ntenant-test-deploy-58b895ff87-vw796   1/1     Running   0          13s   10.0.3.113    ip-10-0-11-255...\ntenant-test-pod                       1/1     Running   0          13s   10.0.35.83    ip-10-0-43-107...\n

As we can see from the above outputs, all the pods are scheduled on the nodes labeled with tenant=tenants-x. Simply put, the pods will only run on the desired nodes, and the other pods (without the required affinity and tolerations) will not. The tenant workloads are effectively isolated.

An example mutated pod specification is seen below.

apiVersion: v1\nkind: Pod\nmetadata:\n  name: tenant-test-pod\n  namespace: tenants-x\nspec:\n  affinity:\n    nodeAffinity:\n      requiredDuringSchedulingIgnoredDuringExecution:\n        nodeSelectorTerms:\n        - matchExpressions:\n          - key: tenant\n            operator: In\n            values:\n            - tenants-x\n...\n  tolerations:\n  - effect: NoSchedule\n    key: tenant\n    operator: Equal\n    value: tenants-x\n...\n

Attention

Policy-management tools that are integrated to the Kubernetes API server request flow, using mutating and validating admission webhooks, are designed to respond to the API server's request within a specified timeframe. This is usually 3 seconds or less. If the webhook call fails to return a response within the configured time, the mutation and/or validation of the inbound API sever request may or may not occur. This behavior is based on whether the admission webhook configurations are set to Fail Open or Fail Close.

In the above examples, we used policies written for OPA/Gatekeeper. However, there are other policy management tools that handle our node-selection use case as well. For example, this Kyverno policy could be used to handle the node affinity mutation.

Tip

If operating correctly, mutating policies will effect the desired changes to inbound API server request payloads. However, validating policies should also be included to verify that the desired changes occur, before changes are allowed to persist. This is especially important when using these policies for tenant-to-node isolation. It is also a good idea to include Audit policies to routinely check your cluster for unwanted configurations.

"},{"location":"security/docs/multitenancy/#references","title":"References","text":"
  • k-rail Designed to help you secure a multi-tenant environment through the enforcement of certain policies.

  • Security Practices for MultiTenant SaaS Applications using Amazon EKS

"},{"location":"security/docs/multitenancy/#hard-multi-tenancy","title":"Hard multi-tenancy","text":"

Hard multi-tenancy can be implemented by provisioning separate clusters for each tenant. While this provides very strong isolation between tenants, it has several drawbacks.

First, when you have many tenants, this approach can quickly become expensive. Not only will you have to pay for the control plane costs for each cluster, you will not be able to share compute resources between clusters. This will eventually cause fragmentation where a subset of your clusters are underutilized while others are overutilized.

Second, you will likely need to buy or build special tooling to manage all of these clusters. In time, managing hundreds or thousands of clusters may simply become too unwieldy.

Finally, creating a cluster per tenant will be slow relative to a creating a namespace. Nevertheless, a hard-tenancy approach may be necessary in highly-regulated industries or in SaaS environments where strong isolation is required.

"},{"location":"security/docs/multitenancy/#future-directions","title":"Future directions","text":"

The Kubernetes community has recognized the current shortcomings of soft multi-tenancy and the challenges with hard multi-tenancy. The Multi-Tenancy Special Interest Group (SIG) is attempting to address these shortcomings through several incubation projects, including Hierarchical Namespace Controller (HNC) and Virtual Cluster.

The HNC proposal (KEP) describes a way to create parent-child relationships between namespaces with [policy] object inheritance along with an ability for tenant administrators to create sub-namespaces.

The Virtual Cluster proposal describes a mechanism for creating separate instances of the control plane services, including the API server, the controller manager, and scheduler, for each tenant within the cluster (also known as \"Kubernetes on Kubernetes\").

The Multi-Tenancy Benchmarks proposal provides guidelines for sharing clusters using namespaces for isolation and segmentation, and a command line tool kubectl-mtb to validate conformance to the guidelines.

"},{"location":"security/docs/multitenancy/#multi-cluster-management-tools-and-resources","title":"Multi-cluster management tools and resources","text":"
  • Banzai Cloud
  • Kommander
  • Lens
  • Nirmata
  • Rafay
  • Rancher
  • Weave Flux
"},{"location":"security/docs/network/","title":"Network security","text":"

Network security has several facets. The first involves the application of rules which restrict the flow of network traffic between services. The second involves the encryption of traffic while it is in transit. The mechanisms to implement these security measures on EKS are varied but often include the following items:

"},{"location":"security/docs/network/#traffic-control","title":"Traffic control","text":"
  • Network Policies
  • Security Groups
"},{"location":"security/docs/network/#network-encryption","title":"Network encryption","text":"
  • Service Mesh
  • Container Network Interfaces (CNIs)
  • Ingress Controllers and Load Balancers
  • Nitro Instances
  • ACM Private CA with cert-manager
"},{"location":"security/docs/network/#network-policy","title":"Network policy","text":"

Within a Kubernetes cluster, all Pod to Pod communication is allowed by default. While this flexibility may help promote experimentation, it is not considered secure. Kubernetes network policies give you a mechanism to restrict network traffic between Pods (often referred to as East/West traffic) as well as between Pods and external services. Kubernetes network policies operate at layers 3 and 4 of the OSI model. Network policies use pod, namespace selectors and labels to identify source and destination pods, but can also include IP addresses, port numbers, protocols, or a combination of these. Network Policies can be applied to both Inbound or Outbound connections to the pod, often called Ingress and Egress rules.

With native network policy support of Amazon VPC CNI Plugin, you can implement network policies to secure network traffic in kubernetes clusters. This integrates with the upstream Kubernetes Network Policy API, ensuring compatibility and adherence to Kubernetes standards. You can define policies using different identifiers supported by the upstream API. By default, all ingress and egress traffic is allowed to a pod. When a network policy with a policyType Ingress is specified, only allowed connections into the pod are those from the pod's node and those allowed by the ingress rules. Same applies for egress rules. If multiple rules are defined, then union of all rules are taken into account when making the decision. Thus, order of evaluation does not affect the policy result.

Attention

When you first provision an EKS cluster, VPC CNI Network Policy functionality is not enabled by default. Ensure you deployed supported VPC CNI Add-on version and set ENABLE_NETWORK_POLICY flag to true on the vpc-cni add-on to enable this. Refer Amazon EKS User guide for detailed instructions.

"},{"location":"security/docs/network/#recommendations","title":"Recommendations","text":""},{"location":"security/docs/network/#getting-started-with-network-policies-follow-principle-of-least-privilege","title":"Getting Started with Network Policies - Follow Principle of Least Privilege","text":""},{"location":"security/docs/network/#create-a-default-deny-policy","title":"Create a default deny policy","text":"

As with RBAC policies, it is recommended to follow least privileged access principles with network policies. Start by creating a deny all policy that restricts all inbound and outbound traffic with in a namespace.

apiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: default-deny\n  namespace: default\nspec:\n  podSelector: {}\n  policyTypes:\n  - Ingress\n  - Egress\n

Tip

The image above was created by the network policy viewer from Tufin.

"},{"location":"security/docs/network/#create-a-rule-to-allow-dns-queries","title":"Create a rule to allow DNS queries","text":"

Once you have the default deny all rule in place, you can begin layering on additional rules, such as a rule that allows pods to query CoreDNS for name resolution.

apiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-dns-access\n  namespace: default\nspec:\n  podSelector:\n    matchLabels: {}\n  policyTypes:\n  - Egress\n  egress:\n  - to:\n    - namespaceSelector:\n        matchLabels:\n          kubernetes.io/metadata.name: kube-system\n      podSelector:\n        matchLabels:\n          k8s-app: kube-dns\n    ports:\n    - protocol: UDP\n      port: 53\n

"},{"location":"security/docs/network/#incrementally-add-rules-to-selectively-allow-the-flow-of-traffic-between-namespacespods","title":"Incrementally add rules to selectively allow the flow of traffic between namespaces/pods","text":"

Understand the application requirements and create fine-grained ingress and egress rules as needed. Below example shows how to restrict ingress traffic on port 80 to app-one from client-one. This helps minimize the attack surface and reduces the risk of unauthorized access.

apiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-ingress-app-one\n  namespace: default\nspec:\n  podSelector:\n    matchLabels:\n      k8s-app: app-one\n  policyTypes:\n  - Ingress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          k8s-app: client-one\n    ports:\n    - protocol: TCP\n      port: 80\n

"},{"location":"security/docs/network/#monitoring-network-policy-enforcement","title":"Monitoring network policy enforcement","text":"
  • Use Network Policy editor
  • Network policy editor helps with visualizations, security score, autogenerates from network flow logs
  • Build network policies in an interactive way
  • Audit Logs
  • Regularly review audit logs of your EKS cluster
  • Audit logs provide wealth of information about what actions have been performed on your cluster including changes to network policies
  • Use this information to track changes to your network policies over time and detect any unauthorized or unexpected changes
  • Automated testing
  • Implement automated testing by creating a test environment that mirrors your production environment and periodically deploy workloads that attempt to violate your network policies.
  • Monitoring metrics
  • Configure your observability agents to scrape the prometheus metrics from the VPC CNI node agents, that allows to monitor the agent health, and sdk errors.
  • Audit Network Policies regularly
  • Periodically audit your Network Policies to make sure that they meet your current application requirements. As your application evolves, an audit gives you the opportunity to remove redundant ingress, egress rules and make sure that your applications don\u2019t have excessive permissions.
  • Ensure Network Policies exists using Open Policy Agent (OPA)
  • Use OPA Policy like shown below to ensure Network Policy always exists before onboarding application pods. This policy denies onboarding k8s pods with a label k8s-app: sample-app if corresponding network policy does not exist.
package kubernetes.admission\nimport data.kubernetes.networkpolicies\n\ndeny[msg] {\n    input.request.kind.kind == \"Pod\"\n    pod_label_value := {v[\"k8s-app\"] | v := input.request.object.metadata.labels}\n    contains_label(pod_label_value, \"sample-app\")\n    np_label_value := {v[\"k8s-app\"] | v := networkpolicies[_].spec.podSelector.matchLabels}\n    not contains_label(np_label_value, \"sample-app\")\n    msg:= sprintf(\"The Pod %v could not be created because it is missing an associated Network Policy.\", [input.request.object.metadata.name])\n}\ncontains_label(arr, val) {\n    arr[_] == val\n}\n
"},{"location":"security/docs/network/#troubleshooting","title":"Troubleshooting","text":""},{"location":"security/docs/network/#monitor-the-vpc-network-policy-controller-node-agent-logs","title":"Monitor the vpc-network-policy-controller, node-agent logs","text":"

Enable the EKS Control plane controller manager logs to diagnose the network policy functionality. You can stream the control plane logs to a CloudWatch log group and use CloudWatch Log insights to perform advanced queries. From the logs, you can view what pod endpoint objects are resolved to a Network Policy, reconcilation status of the policies, and debug if the policy is working as expected.

In addition, Amazon VPC CNI allows you to enable the collection and export of policy enforcement logs to Amazon Cloudwatch from the EKS worker nodes. Once enabled, you can leverage CloudWatch Container Insights to provide insights on your usage related to Network Policies.

Amazon VPC CNI also ships an SDK that provides an interface to interact with eBPF programs on the node. The SDK is installed when the aws-node is deployed onto the nodes. You can find the SDK binary installed under /opt/cni/bin directory on the node. At launch, the SDK provides support for fundamental functionalities such as inspecting eBPF programs and maps.

sudo /opt/cni/bin/aws-eks-na-cli ebpf progs\n
"},{"location":"security/docs/network/#log-network-traffic-metadata","title":"Log network traffic metadata","text":"

AWS VPC Flow Logs captures metadata about the traffic flowing through a VPC, such as source and destination IP address and port along with accepted/dropped packets. This information could be analyzed to look for suspicious or unusual activity between resources within the VPC, including Pods. However, since the IP addresses of pods frequently change as they are replaced, Flow Logs may not be sufficient on its own. Calico Enterprise extends the Flow Logs with pod labels and other metadata, making it easier to decipher the traffic flows between pods.

"},{"location":"security/docs/network/#security-groups","title":"Security groups","text":"

EKS uses AWS VPC Security Groups (SGs) to control the traffic between the Kubernetes control plane and the cluster's worker nodes. Security groups are also used to control the traffic between worker nodes, and other VPC resources, and external IP addresses. When you provision an EKS cluster (with Kubernetes version 1.14-eks.3 or greater), a cluster security group is automatically created for you. This security group allows unfettered communication between the EKS control plane and the nodes from managed node groups. For simplicity, it is recommended that you add the cluster SG to all node groups, including unmanaged node groups.

Prior to Kubernetes version 1.14 and EKS version eks.3, there were separate security groups configured for the EKS control plane and node groups. The minimum and suggested rules for the control plane and node group security groups can be found at https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html. The minimum rules for the control plane security group allows port 443 inbound from the worker node SG. This rule is what allows the kubelets to communicate with the Kubernetes API server. It also includes port 10250 for outbound traffic to the worker node SG; 10250 is the port that the kubelets listen on. Similarly, the minimum node group rules allow port 10250 inbound from the control plane SG and 443 outbound to the control plane SG. Finally there is a rule that allows unfettered communication between nodes within a node group.

If you need to control communication between services that run within the cluster and service the run outside the cluster such as an RDS database, consider security groups for pods. With security groups for pods, you can assign an existing security group to a collection of pods.

Warning

If you reference a security group that does not exist prior to the creation of the pods, the pods will not get scheduled.

You can control which pods are assigned to a security group by creating a SecurityGroupPolicy object and specifying a PodSelector or a ServiceAccountSelector. Setting the selectors to {} will assign the SGs referenced in the SecurityGroupPolicy to all pods in a namespace or all Service Accounts in a namespace. Be sure you've familiarized yourself with all the considerations before implementing security groups for pods.

Important

If you use SGs for pods you must create SGs that allow port 53 outbound to the cluster security group. Similarly, you must update the cluster security group to accept port 53 inbound traffic from the pod security group.

Important

The limits for security groups still apply when using security groups for pods so use them judiciously.

Important

You must create rules for inbound traffic from the cluster security group (kubelet) for all of the probes configured for pod.

Important

Security groups for pods relies on a feature known as ENI trunking which was created to increase the ENI density of an EC2 instance. When a pod is assigned to an SG, a VPC controller associates a branch ENI from the node group with the pod. If there aren't enough branch ENIs available in a node group at the time the pod is scheduled, the pod will stay in pending state. The number of branch ENIs an instance can support varies by instance type/family. See https://docs.aws.amazon.com/eks/latest/userguide/security-groups-for-pods.html#supported-instance-types for further details.

While security groups for pods offers an AWS-native way to control network traffic within and outside of your cluster without the overhead of a policy daemon, other options are available. For example, the Cilium policy engine allows you to reference a DNS name in a network policy. Calico Enterprise includes an option for mapping network policies to AWS security groups. If you've implemented a service mesh like Istio, you can use an egress gateway to restrict network egress to specific, fully qualified domains or IP addresses. For further information about this option, read the three part series on egress traffic control in Istio.

"},{"location":"security/docs/network/#when-to-use-network-policy-vs-security-group-for-pods","title":"When to use Network Policy vs Security Group for Pods?","text":""},{"location":"security/docs/network/#when-to-use-kubernetes-network-policy","title":"When to use Kubernetes network policy","text":"
  • Controlling pod-to-pod traffic
  • Suitable for controlling network traffic between pods inside a cluster (east-west traffic)
  • Control traffic at the IP address or port level (OSI layer 3 or 4)
"},{"location":"security/docs/network/#when-to-use-aws-security-groups-for-pods-sgp","title":"When to use AWS Security groups for pods (SGP)","text":"
  • Leverage existing AWS configurations
  • If you already have complex set of EC2 security groups that manage access to AWS services and you are migrating applications from EC2 instances to EKS, SGPs can be a very good choice allowing you to reuse security group resources and apply them to your pods.
  • Control access to AWS services
  • Your applications running within an EKS cluster wants to communicate with other AWS services (RDS database), use SGPs as an efficient mechanism to control the traffic from the pods to AWS services.
  • Isolation of Pod & Node traffic
  • If you want to completely separate pod traffic from the rest of the node traffic, use SGP in POD_SECURITY_GROUP_ENFORCING_MODE=strict mode.
"},{"location":"security/docs/network/#best-practices-using-security-groups-for-pods-and-network-policy","title":"Best practices using Security groups for pods and Network Policy","text":"
  • Layered security
  • Use a combination of SGP and kubernetes network policy for a layered security approach
  • Use SGPs to limit network level access to AWS services that are not part of a cluster, while kubernetes network policies can restrict network traffic between pods inside the cluster
  • Principle of least privilege
  • Only allow necessary traffic between pods or namespaces
  • Segment your applications
  • Wherever possible, segment applications by the network policy to reduce the blast radius if an application is compromised
  • Keep policies simple and clear
  • Kubernetes network policies can be quite granular and complex, its best to keep them as simple as possible to reduce the risk of misconfiguration and ease the management overhead
  • Reduce the attack surface
  • Minimize the attack surface by limiting the exposure of your applications

Attention

Security Groups for pods provides two enforcing modes: strict and standard. You must use standard mode when using both Network Policy and Security Groups for pods features in an EKS cluster.

When it comes to network security, a layered approach is often the most effective solution. Using kubernetes network policy and SGP in combination can provide a robust defense-in-depth strategy for your applications running in EKS.

"},{"location":"security/docs/network/#service-mesh-policy-enforcement-or-kubernetes-network-policy","title":"Service Mesh Policy Enforcement or Kubernetes network policy","text":"

A service mesh is a dedicated infrastructure layer that you can add to your applications. It allows you to transparently add capabilities like observability, traffic management, and security, without adding them to your own code.

Service mesh enforces policies at Layer 7 (application) of OSI model whereas kubernetes network policies operate at Layer 3 (network) and Layer 4 (transport). There are many offerings in this space like AWS AppMesh, Istio, Linkerd, etc.,

"},{"location":"security/docs/network/#when-to-use-service-mesh-for-policy-enforcement","title":"When to use Service mesh for policy enforcement","text":"
  • Have existing investment in a service mesh
  • Need more advanced capabilities like traffic management, observability & security
  • Traffic control, load balancing, circuit breaking, rate limiting, timeouts etc.
  • Detailed insights into how your services are performing (latency, error rates, requests per second, request volumes etc.)
  • You want to implement and leverage service mesh for security features like mTLS
"},{"location":"security/docs/network/#choose-kubernetes-network-policy-for-simpler-use-cases","title":"Choose Kubernetes network policy for simpler use cases","text":"
  • Limit which pods can communicate with each other
  • Network policies require fewer resources than a service mesh making them a good fit for simpler use cases or for smaller clusters where the overhead of running and managing a service mesh might not be justified

Tip

Network policies and Service mesh can also be used together. Use network policies to provide a baseline level of security and isolation between your pods and then use a service mesh to add additional capabilities like traffic management, observability and security.

"},{"location":"security/docs/network/#thirdparty-network-policy-engines","title":"ThirdParty Network Policy Engines","text":"

Consider a Third Party Network Policy Engine when you have advanced policy requirements like Global Network Policies, support for DNS Hostname based rules, Layer 7 rules, ServiceAccount based rules, and explicit deny/log actions, etc., Calico, is an open source policy engine from Tigera that works well with EKS. In addition to implementing the full set of Kubernetes network policy features, Calico supports extended network polices with a richer set of features, including support for layer 7 rules, e.g. HTTP, when integrated with Istio. Calico policies can be scoped to Namespaces, Pods, service accounts, or globally. When policies are scoped to a service account, it associates a set of ingress/egress rules with that service account. With the proper RBAC rules in place, you can prevent teams from overriding these rules, allowing IT security professionals to safely delegate administration of namespaces. Isovalent, the maintainers of Cilium, have also extended the network policies to include partial support for layer 7 rules, e.g. HTTP. Cilium also has support for DNS hostnames which can be useful for restricting traffic between Kubernetes Services/Pods and resources that run within or outside of your VPC. By contrast, Calico Enterprise includes a feature that allows you to map a Kubernetes network policy to an AWS security group, as well as DNS hostnames.

You can find a list of common Kubernetes network policies at https://github.com/ahmetb/kubernetes-network-policy-recipes. A similar set of rules for Calico are available at https://docs.projectcalico.org/security/calico-network-policy.

"},{"location":"security/docs/network/#migration-to-amazon-vpc-cni-network-policy-engine","title":"Migration to Amazon VPC CNI Network Policy Engine","text":"

To maintain consistency and avoid unexpected pod communication behavior, it is recommended to deploy only one Network Policy Engine in your cluster. If you want to migrate from 3P to VPC CNI Network Policy Engine, we recommend converting your existing 3P NetworkPolicy CRDs to the Kubernetes NetworkPolicy resources before enabling VPC CNI network policy support. And, test the migrated policies in a separate test cluster before applying them in you production environment. This allows you to identify and address any potential issues or inconsistencies in pod communication behavior.

"},{"location":"security/docs/network/#migration-tool","title":"Migration Tool","text":"

To assist in your migration process, we have developed a tool called K8s Network Policy Migrator that converts your existing Calico/Cilium network policy CRDs to Kubernetes native network policies. After conversion you can directly test the converted network policies on your new clusters running VPC CNI network policy controller. The tool is designed to help you streamline the migration process and ensure a smooth transition.

Important

Migration tool will only convert 3P policies that are compatible with native kubernetes network policy api. If you are using advanced network policy features offered by 3P plugins, Migration tool will skip and report them.

Please note that migration tool is currently not supported by AWS VPC CNI Network policy engineering team, it is made available to customers on a best-effort basis. We encourage you to utilize this tool to facilitate your migration process. In the event that you encounter any issues or bugs with the tool, we kindly ask you create a GitHub issue. Your feedback is invaluable to us and will assist in the continuous improvement of our services.

"},{"location":"security/docs/network/#additional-resources","title":"Additional Resources","text":"
  • Kubernetes & Tigera: Network Policies, Security, and Audit
  • Calico Enterprise
  • Cilium
  • NetworkPolicy Editor an interactive policy editor from Cilium
  • Inspektor Gadget advise network-policy gadget Suggests network policies based on an analysis of network traffic
"},{"location":"security/docs/network/#encryption-in-transit","title":"Encryption in transit","text":"

Applications that need to conform to PCI, HIPAA, or other regulations may need to encrypt data while it is in transit. Nowadays TLS is the de facto choice for encrypting traffic on the wire. TLS, like it's predecessor SSL, provides secure communications over a network using cryptographic protocols. TLS uses symmetric encryption where the keys to encrypt the data are generated based on a shared secret that is negotiated at the beginning of the session. The following are a few ways that you can encrypt data in a Kubernetes environment.

"},{"location":"security/docs/network/#nitro-instances","title":"Nitro Instances","text":"

Traffic exchanged between the following Nitro instance types, e.g. C5n, G4, I3en, M5dn, M5n, P3dn, R5dn, and R5n, is automatically encrypted by default. When there's an intermediate hop, like a transit gateway or a load balancer, the traffic is not encrypted. See Encryption in transit for further details on encryption in transit as well as the complete list of instances types that support network encryption by default.

"},{"location":"security/docs/network/#container-network-interfaces-cnis","title":"Container Network Interfaces (CNIs)","text":"

WeaveNet can be configured to automatically encrypt all traffic using NaCl encryption for sleeve traffic, and IPsec ESP for fast datapath traffic.

"},{"location":"security/docs/network/#service-mesh","title":"Service Mesh","text":"

Encryption in transit can also be implemented with a service mesh like App Mesh, Linkerd v2, and Istio. AppMesh supports mTLS with X.509 certificates or Envoy's Secret Discovery Service(SDS). Linkerd and Istio both have support for mTLS.

The aws-app-mesh-examples GitHub repository provides walkthroughs for configuring mTLS using X.509 certificates and SPIRE as SDS provider with your Envoy container:

  • Configuring mTLS using X.509 certificates
  • Configuring TLS using SPIRE (SDS)

App Mesh also supports TLS encryption with a private certificate issued by AWS Certificate Manager (ACM) or a certificate stored on the local file system of the virtual node.

The aws-app-mesh-examples GitHub repository provides walkthroughs for configuring TLS using certificates issued by ACM and certificates that are packaged with your Envoy container:

  • Configuring TLS with File Provided TLS Certificates
  • Configuring TLS with AWS Certificate Manager
"},{"location":"security/docs/network/#ingress-controllers-and-load-balancers","title":"Ingress Controllers and Load Balancers","text":"

Ingress controllers are a way for you to intelligently route HTTP/S traffic that emanates from outside the cluster to services running inside the cluster. Oftentimes, these Ingresses are fronted by a layer 4 load balancer, like the Classic Load Balancer or the Network Load Balancer (NLB). Encrypted traffic can be terminated at different places within the network, e.g. at the load balancer, at the ingress resource, or the Pod. How and where you terminate your SSL connection will ultimately be dictated by your organization's network security policy. For instance, if you have a policy that requires end-to-end encryption, you will have to decrypt the traffic at the Pod. This will place additional burden on your Pod as it will have to spend cycles establishing the initial handshake. Overall SSL/TLS processing is very CPU intensive. Consequently, if you have the flexibility, try performing the SSL offload at the Ingress or the load balancer.

"},{"location":"security/docs/network/#use-encryption-with-aws-elastic-load-balancers","title":"Use encryption with AWS Elastic load balancers","text":"

The AWS Application Load Balancer (ALB) and Network Load Balancer (NLB) both have support for transport encryption (SSL and TLS). The alb.ingress.kubernetes.io/certificate-arn annotation for the ALB lets you to specify which certificates to add to the ALB. If you omit the annotation the controller will attempt to add certificates to listeners that require it by matching the available AWS Certificate Manager (ACM) certificates using the host field. Starting with EKS v1.15 you can use the service.beta.kubernetes.io/aws-load-balancer-ssl-cert annotation with the NLB as shown in the example below.

apiVersion: v1\nkind: Service\nmetadata:\n  name: demo-app\n  namespace: default\n  labels:\n    app: demo-app\n  annotations:\n     service.beta.kubernetes.io/aws-load-balancer-type: \"nlb\"\n     service.beta.kubernetes.io/aws-load-balancer-ssl-cert: \"<certificate ARN>\"\n     service.beta.kubernetes.io/aws-load-balancer-ssl-ports: \"443\"\n     service.beta.kubernetes.io/aws-load-balancer-backend-protocol: \"http\"\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 443\n    targetPort: 80\n    protocol: TCP\n  selector:\n    app: demo-app\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: default\n  labels:\n    app: demo-app\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: demo-app\n  template:\n    metadata:\n      labels:\n        app: demo-app\n    spec:\n      containers:\n        - name: nginx\n          image: nginx\n          ports:\n            - containerPort: 443\n              protocol: TCP\n            - containerPort: 80\n              protocol: TCP\n

Following are additional examples for SSL/TLS termination.

  • Securing EKS Ingress With Contour And Let\u2019s Encrypt The GitOps Way
  • How do I terminate HTTPS traffic on Amazon EKS workloads with ACM?

Attention

Some Ingresses, like the AWS LB controller, implement the SSL/TLS using Annotations instead of as part of the Ingress Spec.

"},{"location":"security/docs/network/#acm-private-ca-with-cert-manager","title":"ACM Private CA with cert-manager","text":"

You can enable TLS and mTLS to secure your EKS application workloads at the ingress, on the pod, and between pods using ACM Private Certificate Authority (CA) and cert-manager, a popular Kubernetes add-on to distribute, renew, and revoke certificates. ACM Private CA is a highly-available, secure, managed CA without the upfront and maintenance costs of managing your own CA. If you are using the default Kubernetes certificate authority, there is an opportunity to improve your security and meet compliance requirements with ACM Private CA. ACM Private CA secures private keys in FIPS 140-2 Level 3 hardware security modules (very secure), compared with the default CA storing keys encoded in memory (less secure). A centralized CA also gives you more control and improved auditability for private certificates both inside and outside of a Kubernetes environment.

"},{"location":"security/docs/network/#short-lived-ca-mode-for-mutual-tls-between-workloads","title":"Short-Lived CA Mode for Mutual TLS Between Workloads","text":"

When using ACM Private CA for mTLS in EKS, it is recommended that you use short lived certificates with short-lived CA mode. Although it is possible to issue out short-lived certificates in the general-purpose CA mode, using short-lived CA mode works out more cost-effective (~75% cheaper than general mode) for use cases where new certificates need to be issued frequently. In addition to this, you should try to align the validity period of the private certificates with the lifetime of the pods in your EKS cluster. Learn more about ACM Private CA and its benefits here.

"},{"location":"security/docs/network/#acm-setup-instructions","title":"ACM Setup Instructions","text":"

Start by creating a Private CA by following procedures provided in the ACM Private CA tech docs. Once you have a Private CA, install cert-manager using regular installation instructions. After installing cert-manager, install the Private CA Kubernetes cert-manager plugin by following the setup instructions in GitHub. The plugin lets cert-manager request private certificates from ACM Private CA.

Now that you have a Private CA and an EKS cluster with cert-manager and the plugin installed, it\u2019s time to set permissions and create the issuer. Update IAM permissions of the EKS node role to allow access to ACM Private CA. Replace the <CA_ARN> with the value from your Private CA:

{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"awspcaissuer\",\n            \"Action\": [\n                \"acm-pca:DescribeCertificateAuthority\",\n                \"acm-pca:GetCertificate\",\n                \"acm-pca:IssueCertificate\"\n            ],\n            \"Effect\": \"Allow\",\n            \"Resource\": \"<CA_ARN>\"\n        }\n    ]\n}\n

Service Roles for IAM Accounts, or IRSA can also be used. Please see the Additional Resources section below for complete examples.

Create an Issuer in Amazon EKS by creating a Custom Resource Definition file named cluster-issuer.yaml with the following text in it, replacing <CA_ARN> and <Region> information with your Private CA.

apiVersion: awspca.cert-manager.io/v1beta1\nkind: AWSPCAClusterIssuer\nmetadata:\n          name: demo-test-root-ca\nspec:\n          arn: <CA_ARN>\n          region: <Region>\n

Deploy the Issuer you created.

kubectl apply -f cluster-issuer.yaml\n

Your EKS cluster is configured to request certificates from Private CA. You can now use cert-manager's Certificate resource to issue certificates by changing the issuerRef field's values to the Private CA Issuer you created above. For more details on how to specify and request Certificate resources, please check cert-manager's Certificate Resources guide. See examples here.

"},{"location":"security/docs/network/#acm-private-ca-with-istio-and-cert-manager","title":"ACM Private CA with Istio and cert-manager","text":"

If you are running Istio in your EKS cluster, you can disable the Istio control plane (specifically istiod) from functioning as the root Certificate Authority (CA), and configure ACM Private CA as the root CA for mTLS between workloads. If you're going with this approach, consider using the short-lived CA mode in ACM Private CA. Refer to the previous section and this blog post for more details.

"},{"location":"security/docs/network/#how-certificate-signing-works-in-istio-default","title":"How Certificate Signing Works in Istio (Default)","text":"

Workloads in Kubernetes are identified using service accounts. If you don't specify a service account, Kubernetes will automatically assign one to your workload. Also, service accounts automatically mount an associated token. This token is used by the service account for workloads to authenticate against the Kubernetes API. The service account may be sufficient as an identity for Kubernetes but Istio has its own identity management system and CA. When a workload starts up with its envoy sidecar proxy, it needs an identity assigned from Istio in order for it to be deemed as trustworthy and allowed to communicate with other services in the mesh.

To get this identity from Istio, the istio-agent sends a request known as a certificate signing request (or CSR) to the Istio control plane. This CSR contains the service account token so that the workload's identity can be verified before being processed. This verification process is handled by istiod, which acts as both the Registration Authority (or RA) and the CA. The RA serves as a gatekeeper that makes sure only verified CSR makes it through to the CA. Once the CSR is verified, it will be forwarded to the CA which will then issue a certificate containing a SPIFFE identity with the service account. This certificate is called a SPIFFE verifiable identity document (or SVID). The SVID is assigned to the requesting service for identification purposes and to encrypt the traffic in transit between the communicating services.

"},{"location":"security/docs/network/#how-certificate-signing-works-in-istio-with-acm-private-ca","title":"How Certificate Signing Works in Istio with ACM Private CA","text":"

You can use a cert-manager add-on called the Istio Certificate Signing Request agent (istio-csr) to integrate Istio with ACM Private CA. This agent allows Istio workloads and control plane components to be secured with cert manager issuers, in this case ACM Private CA. The istio-csr agent exposes the same service that istiod serves in the default config of validating incoming CSRs. Except, after verification, it will convert the requests into resources that cert manager supports (i.e. integrations with external CA issuers).

Whenever there's a CSR from a workload, it will be forwarded to istio-csr, which will request certificates from ACM Private CA. This communication between istio-csr and ACM Private CA is enabled by the AWS Private CA issuer plugin. Cert manager uses this plugin to request TLS certificates from ACM Private CA. The issuer plugin will communicate with the ACM Private CA service to request a signed certificate for the workload. Once the certificate has been signed, it will be returned to istio-csr, which will read the signed request, and return it to the workload that initiated the CSR.

"},{"location":"security/docs/network/#istio-with-private-ca-setup-instructions","title":"Istio with Private CA Setup Instructions","text":"
  1. Start by following the same setup instructions in this section to complete the following:
  2. Create a Private CA
  3. Install cert-manager
  4. Install the issuer plugin
  5. Set permissions and create an issuer. The issuer represents the CA and is used to sign istiod and mesh workload certificates. It will communicate with ACM Private CA.
  6. Create an istio-system namespace. This is where the istiod certificate and other Istio resources will be deployed.
  7. Install Istio CSR configured with AWS Private CA Issuer Plugin. You can preserve the certificate signing requests for workloads to verify that they get approved and signed (preserveCertificateRequests=true).

    helm install -n cert-manager cert-manager-istio-csr jetstack/cert-manager-istio-csr \\\n--set \"app.certmanager.issuer.group=awspca.cert-manager.io\" \\\n--set \"app.certmanager.issuer.kind=AWSPCAClusterIssuer\" \\\n--set \"app.certmanager.issuer.name=<the-name-of-the-issuer-you-created>\" \\\n--set \"app.certmanager.preserveCertificateRequests=true\" \\\n--set \"app.server.maxCertificateDuration=48h\" \\\n--set \"app.tls.certificateDuration=24h\" \\\n--set \"app.tls.istiodCertificateDuration=24h\" \\\n--set \"app.tls.rootCAFile=/var/run/secrets/istio-csr/ca.pem\" \\\n--set \"volumeMounts[0].name=root-ca\" \\\n--set \"volumeMounts[0].mountPath=/var/run/secrets/istio-csr\" \\\n--set \"volumes[0].name=root-ca\" \\\n--set \"volumes[0].secret.secretName=istio-root-ca\"\n
  8. Install Istio with custom configurations to replace istiod with cert-manager istio-csr as the certificate provider for the mesh. This process can be carried out using the Istio Operator.

    apiVersion: install.istio.io/v1alpha1\nkind: IstioOperator\nmetadata:\n  name: istio\n  namespace: istio-system\nspec:\n  profile: \"demo\"\n  hub: gcr.io/istio-release\n  values:\n  global:\n    # Change certificate provider to cert-manager istio agent for istio agent\n    caAddress: cert-manager-istio-csr.cert-manager.svc:443\n  components:\n    pilot:\n      k8s:\n        env:\n          # Disable istiod CA Sever functionality\n        - name: ENABLE_CA_SERVER\n          value: \"false\"\n        overlays:\n        - apiVersion: apps/v1\n          kind: Deployment\n          name: istiod\n          patches:\n\n            # Mount istiod serving and webhook certificate from Secret mount\n          - path: spec.template.spec.containers.[name:discovery].args[7]\n            value: \"--tlsCertFile=/etc/cert-manager/tls/tls.crt\"\n          - path: spec.template.spec.containers.[name:discovery].args[8]\n            value: \"--tlsKeyFile=/etc/cert-manager/tls/tls.key\"\n          - path: spec.template.spec.containers.[name:discovery].args[9]\n            value: \"--caCertFile=/etc/cert-manager/ca/root-cert.pem\"\n\n          - path: spec.template.spec.containers.[name:discovery].volumeMounts[6]\n            value:\n              name: cert-manager\n              mountPath: \"/etc/cert-manager/tls\"\n              readOnly: true\n          - path: spec.template.spec.containers.[name:discovery].volumeMounts[7]\n            value:\n              name: ca-root-cert\n              mountPath: \"/etc/cert-manager/ca\"\n              readOnly: true\n\n          - path: spec.template.spec.volumes[6]\n            value:\n              name: cert-manager\n              secret:\n                secretName: istiod-tls\n          - path: spec.template.spec.volumes[7]\n            value:\n              name: ca-root-cert\n              configMap:\n                defaultMode: 420\n                name: istio-ca-root-cert\n
  9. Deploy the above custom resource you created.

    istioctl operator init\nkubectl apply -f istio-custom-config.yaml\n
  10. Now you can deploy a workload to the mesh in your EKS cluster and enforce mTLS.

"},{"location":"security/docs/network/#tools-and-resources","title":"Tools and resources","text":"
  • Amazon EKS Security Immersion Workshop - Network security
  • How to implement cert-manager and the ACM Private CA plugin to enable TLS in EKS.
  • Setting up end-to-end TLS encryption on Amazon EKS with the new AWS Load Balancer Controller and ACM Private CA.
  • Private CA Kubernetes cert-manager plugin on GitHub.
  • Private CA Kubernetes cert-manager plugin user guide.
  • How to use AWS Private Certificate Authority short-lived certificate mode
  • egress-operator An operator and DNS plugin to control egress traffic from your cluster without protocol inspection
  • NeuVector by SUSE open source, zero-trust container security platform, provides policy network rules, data loss prevention (DLP), web application firewall (WAF) and network threat signatures.
"},{"location":"security/docs/pods/","title":"Pod Security","text":"

The pod specification includes a variety of different attributes that can strengthen or weaken your overall security posture. As a Kubernetes practitioner your chief concern should be preventing a process that\u2019s running in a container from escaping the isolation boundaries of the container runtime and gaining access to the underlying host.

"},{"location":"security/docs/pods/#linux-capabilities","title":"Linux Capabilities","text":"

The processes that run within a container run under the context of the [Linux] root user by default. Although the actions of root within a container are partially constrained by the set of Linux capabilities that the container runtime assigns to the containers, these default privileges could allow an attacker to escalate their privileges and/or gain access to sensitive information bound to the host, including Secrets and ConfigMaps. Below is a list of the default capabilities assigned to containers. For additional information about each capability, see http://man7.org/linux/man-pages/man7/capabilities.7.html.

CAP_AUDIT_WRITE, CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_FOWNER, CAP_FSETID, CAP_KILL, CAP_MKNOD, CAP_NET_BIND_SERVICE, CAP_NET_RAW, CAP_SETGID, CAP_SETUID, CAP_SETFCAP, CAP_SETPCAP, CAP_SYS_CHROOT

Info

EC2 and Fargate pods are assigned the aforementioned capabilities by default. Additionally, Linux capabilities can only be dropped from Fargate pods.

Pods that are run as privileged, inherit all of the Linux capabilities associated with root on the host. This should be avoided if possible.

"},{"location":"security/docs/pods/#node-authorization","title":"Node Authorization","text":"

All Kubernetes worker nodes use an authorization mode called Node Authorization. Node Authorization authorizes all API requests that originate from the kubelet and allows nodes to perform the following actions:

Read operations:

  • services
  • endpoints
  • nodes
  • pods
  • secrets, configmaps, persistent volume claims and persistent volumes related to pods bound to the kubelet\u2019s node

Write operations:

  • nodes and node status (enable the NodeRestriction admission plugin to limit a kubelet to modify its own node)
  • pods and pod status (enable the NodeRestriction admission plugin to limit a kubelet to modify pods bound to itself)
  • events

Auth-related operations:

  • Read/write access to the CertificateSigningRequest (CSR) API for TLS bootstrapping
  • the ability to create TokenReview and SubjectAccessReview for delegated authentication/authorization checks

EKS uses the node restriction admission controller which only allows the node to modify a limited set of node attributes and pod objects that are bound to the node. Nevertheless, an attacker who manages to get access to the host will still be able to glean sensitive information about the environment from the Kubernetes API that could allow them to move laterally within the cluster.

"},{"location":"security/docs/pods/#pod-security-solutions","title":"Pod Security Solutions","text":""},{"location":"security/docs/pods/#pod-security-policy-psp","title":"Pod Security Policy (PSP)","text":"

In the past, Pod Security Policy (PSP) resources were used to specify a set of requirements that pods had to meet before they could be created. As of Kubernetes version 1.21, PSP have been deprecated. They are scheduled for removal in Kubernetes version 1.25.

Attention

PSPs are deprecated in Kubernetes version 1.21. You will have until version 1.25 or roughly 2 years to transition to an alternative. This document explains the motivation for this deprecation.

"},{"location":"security/docs/pods/#migrating-to-a-new-pod-security-solution","title":"Migrating to a new pod security solution","text":"

Since PSPs have been removed as of Kubernetes v1.25, cluster administrators and operators must replace those security controls. Two solutions can fill this need:

  • Policy-as-code (PAC) solutions from the Kubernetes ecosystem
  • Kubernetes Pod Security Standards (PSS)

Both the PAC and PSS solutions can coexist with PSP; they can be used in clusters before PSP is removed. This eases adoption when migrating from PSP. Please see this document when considering migrating from PSP to PSS.

Kyverno, one of the PAC solutions outlined below, has specific guidance outlined in a blog post when migrating from PSPs to its solution including analogous policies, feature comparisons, and a migration procedure. Additional information and guidance on migration to Kyverno with respect to Pod Security Admission (PSA) has been published on the AWS blog here.

"},{"location":"security/docs/pods/#policy-as-code-pac","title":"Policy-as-code (PAC)","text":"

Policy-as-code (PAC) solutions provide guardrails to guide cluster users, and prevent unwanted behaviors, through prescribed and automated controls. PAC uses Kubernetes Dynamic Admission Controllers to intercept the Kubernetes API server request flow, via a webhook call, and mutate and validate request payloads, based on policies written and stored as code. Mutation and validation happens before the API server request results in a change to the cluster. PAC solutions use policies to match and act on API server request payloads, based on taxonomy and values.

There are several open source PAC solutions available for Kubernetes. These solutions are not part of the Kubernetes project; they are sourced from the Kubernetes ecosystem. Some PAC solutions are listed below.

  • OPA/Gatekeeper
  • Open Policy Agent (OPA)
  • Kyverno
  • Kubewarden
  • jsPolicy

For further information about PAC solutions and how to help you select the appropriate solution for your needs, see the links below.

  • Policy-based countermeasures for Kubernetes \u2013 Part 1
  • Policy-based countermeasures for Kubernetes \u2013 Part 2
"},{"location":"security/docs/pods/#pod-security-standards-pss-and-pod-security-admission-psa","title":"Pod Security Standards (PSS) and Pod Security Admission (PSA)","text":"

In response to the PSP deprecation and the ongoing need to control pod security out-of-the-box, with a built-in Kubernetes solution, the Kubernetes Auth Special Interest Group created the Pod Security Standards (PSS) and Pod Security Admission (PSA). The PSA effort includes an admission controller webhook project that implements the controls defined in the PSS. This admission controller approach resembles that used in the PAC solutions.

According to the Kubernetes documentation, the PSS \"define three different policies to broadly cover the security spectrum. These policies are cumulative and range from highly-permissive to highly-restrictive.\"

These policies are defined as:

  • Privileged: Unrestricted (unsecure) policy, providing the widest possible level of permissions. This policy allows for known privilege escalations. It is the absence of a policy. This is good for applications such as logging agents, CNIs, storage drivers, and other system wide applications that need privileged access.

  • Baseline: Minimally restrictive policy which prevents known privilege escalations. Allows the default (minimally specified) Pod configuration. The baseline policy prohibits use of hostNetwork, hostPID, hostIPC, hostPath, hostPort, the inability to add Linux capabilities, along with several other restrictions.

  • Restricted: Heavily restricted policy, following current Pod hardening best practices. This policy inherits from the baseline and adds further restrictions such as the inability to run as root or a root-group. Restricted policies may impact an application's ability to function. They are primarily targeted at running security critical applications.

These policies define profiles for pod execution, arranged into three levels of privileged vs. restricted access.

To implement the controls defined by the PSS, PSA operates in three modes:

  • enforce: Policy violations will cause the pod to be rejected.

  • audit: Policy violations will trigger the addition of an audit annotation to the event recorded in the audit log, but are otherwise allowed.

  • warn: Policy violations will trigger a user-facing warning, but are otherwise allowed.

These modes and the profile (restriction) levels are configured at the Kubernetes Namespace level, using labels, as seen in the below example.

apiVersion: v1\nkind: Namespace\nmetadata:\n  name: policy-test\n  labels:\n    pod-security.kubernetes.io/enforce: restricted\n

When used independently, these operational modes have different responses that result in different user experiences. The enforce mode will prevent pods from being created if respective podSpecs violate the configured restriction level. However, in this mode, non-pod Kubernetes objects that create pods, such as Deployments, will not be prevented from being applied to the cluster, even if the podSpec therein violates the applied PSS. In this case the Deployment will be applied, while the pod(s) will be prevented from being applied.

This is a difficult user experience, as there is no immediate indication that the successfully applied Deployment object belies failed pod creation. The offending podSpecs will not create pods. Inspecting the Deployment resource with kubectl get deploy <DEPLOYMENT_NAME> -oyaml will expose the message from the failed pod(s) .status.conditions element, as seen below.

...\nstatus:\n  conditions:\n    - lastTransitionTime: \"2022-01-20T01:02:08Z\"\n      lastUpdateTime: \"2022-01-20T01:02:08Z\"\n      message: 'pods \"test-688f68dc87-tw587\" is forbidden: violates PodSecurity \"restricted:latest\":\n        allowPrivilegeEscalation != false (container \"test\" must set securityContext.allowPrivilegeEscalation=false),\n        unrestricted capabilities (container \"test\" must set securityContext.capabilities.drop=[\"ALL\"]),\n        runAsNonRoot != true (pod or container \"test\" must set securityContext.runAsNonRoot=true),\n        seccompProfile (pod or container \"test\" must set securityContext.seccompProfile.type\n        to \"RuntimeDefault\" or \"Localhost\")'\n      reason: FailedCreate\n      status: \"True\"\n      type: ReplicaFailure\n...\n

In both the audit and warn modes, the pod restrictions do not prevent violating pods from being created and started. However, in these modes audit annotations on API server audit log events and warnings to API server clients, such as kubectl, are triggered, respectively, when pods, as well as objects that create pods, contain podSpecs with violations. A kubectl Warning message is seen below.

Warning: would violate PodSecurity \"restricted:latest\": allowPrivilegeEscalation != false (container \"test\" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container \"test\" must set securityContext.capabilities.drop=[\"ALL\"]), runAsNonRoot != true (pod or container \"test\" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container \"test\" must set securityContext.seccompProfile.type to \"RuntimeDefault\" or \"Localhost\")\ndeployment.apps/test created\n

The PSA audit and warn modes are useful when introducing the PSS without negatively impacting cluster operations.

The PSA operational modes are not mutually exclusive, and can be used in a cumulative manner. As seen below, the multiple modes can be configured in a single namespace.

apiVersion: v1\nkind: Namespace\nmetadata:\n  name: policy-test\n  labels:\n    pod-security.kubernetes.io/audit: restricted\n    pod-security.kubernetes.io/enforce: restricted\n    pod-security.kubernetes.io/warn: restricted\n

In the above example, the user-friendly warnings and audit annotations are provided when applying Deployments, while the enforce of violations are also provided at the pod level. In fact multiple PSA labels can use different profile levels, as seen below.

apiVersion: v1\nkind: Namespace\nmetadata:\n  name: policy-test\n  labels:\n    pod-security.kubernetes.io/enforce: baseline\n    pod-security.kubernetes.io/warn: restricted\n

In the above example, PSA is configured to allow the creation of all pods that satisfy the baseline profile level, and then warn on pods (and objects that create pods) that violate the restricted profile level. This is a useful approach to determine the possible impacts when changing from the baseline to restricted profiles.

"},{"location":"security/docs/pods/#existing-pods","title":"Existing Pods","text":"

If a namespace with existing pods is modified to use a more restrictive PSS profile, the audit and warn modes will produce appropriate messages; however, enforce mode will not delete the pods. The warning messages are seen below.

Warning: existing pods in namespace \"policy-test\" violate the new PodSecurity enforce level \"restricted:latest\"\nWarning: test-688f68dc87-htm8x: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile\nnamespace/policy-test configured\n
"},{"location":"security/docs/pods/#exemptions","title":"Exemptions","text":"

PSA uses Exemptions to exclude enforcement of violations against pods that would have otherwise been applied. These exemptions are listed below.

  • Usernames: requests from users with an exempt authenticated (or impersonated) username are ignored.

  • RuntimeClassNames: pods and workload resources specifying an exempt runtime class name are ignored.

  • Namespaces: pods and workload resources in an exempt namespace are ignored.

These exemptions are applied statically in the PSA admission controller configuration as part of the API server configuration.

In the Validating Webhook implementation the exemptions can be configured within a Kubernetes ConfigMap resource that gets mounted as a volume into the pod-security-webhook container.

apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: pod-security-webhook\n  namespace: pod-security-webhook\ndata:\n  podsecurityconfiguration.yaml: |\n    apiVersion: pod-security.admission.config.k8s.io/v1\n    kind: PodSecurityConfiguration\n    defaults:\n      enforce: \"restricted\"\n      enforce-version: \"latest\"\n      audit: \"restricted\"\n      audit-version: \"latest\"\n      warn: \"restricted\"\n      warn-version: \"latest\"\n    exemptions:\n      # Array of authenticated usernames to exempt.\n      usernames: []\n      # Array of runtime class names to exempt.\n      runtimeClasses: []\n      # Array of namespaces to exempt.\n      namespaces: [\"kube-system\",\"policy-test1\"]\n

As seen in the above ConfigMap YAML the cluster-wide default PSS level has been set to restricted for all PSA modes, audit, enforce, and warn. This affects all namespaces, except those exempted: namespaces: [\"kube-system\",\"policy-test1\"]. Additionally, in the ValidatingWebhookConfiguration resource, seen below, the pod-security-webhook namespace is also exempted from configured PSS.

...\nwebhooks:\n  # Audit annotations will be prefixed with this name\n  - name: \"pod-security-webhook.kubernetes.io\"\n    # Fail-closed admission webhooks can present operational challenges.\n    # You may want to consider using a failure policy of Ignore, but should \n    # consider the security tradeoffs.\n    failurePolicy: Fail\n    namespaceSelector:\n      # Exempt the webhook itself to avoid a circular dependency.\n      matchExpressions:\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values: [\"pod-security-webhook\"]\n...\n

Attention

Pod Security Admissions graduated to stable in Kubernetes v1.25. If you wanted to use the Pod Security Admission feature prior to it being enabled by default, you needed to install the dynamic admission controller (mutating webhook). The instructions for installing and configuring the webhook can be found here.

"},{"location":"security/docs/pods/#choosing-between-policy-as-code-and-pod-security-standards","title":"Choosing between policy-as-code and Pod Security Standards","text":"

The Pod Security Standards (PSS) were developed to replace the Pod Security Policy (PSP), by providing a solution that was built-in to Kubernetes and did not require solutions from the Kubernetes ecosystem. That being said, policy-as-code (PAC) solutions are considerably more flexible.

The following list of Pros and Cons is designed help you make a more informed decision about your pod security solution.

"},{"location":"security/docs/pods/#policy-as-code-as-compared-to-pod-security-standards","title":"Policy-as-code (as compared to Pod Security Standards)","text":"

Pros:

  • More flexible and more granular (down to attributes of resources if need be)
  • Not just focused on pods, can be used against different resources and actions
  • Not just applied at the namespace level
  • More mature than the Pod Security Standards
  • Decisions can be based on anything in the API server request payload, as well as existing cluster resources and external data (solution dependent)
  • Supports mutating API server requests before validation (solution dependent)
  • Can generate complementary policies and Kubernetes resources (solution dependent - From pod policies, Kyverno can auto-gen policies for higher-level controllers, such as Deployments. Kyverno can also generate additional Kubernetes resources \"when a new resource is created or when the source is updated\" by using Generate Rules.)
  • Can be used to shift left, into CICD pipelines, before making calls to the Kubernetes API server (solution dependent)
  • Can be used to implement behaviors that are not necessarily security related, such as best practices, organizational standards, etc.
  • Can be used in non-Kubernetes use cases (solution dependent)
  • Because of flexibility, the user experience can be tuned to users' needs

Cons:

  • Not built into Kubernetes
  • More complex to learn, configure, and support
  • Policy authoring may require new skills/languages/capabilities
"},{"location":"security/docs/pods/#pod-security-admission-as-compared-to-policy-as-code","title":"Pod Security Admission (as compared to policy-as-code)","text":"

Pros:

  • Built into Kubernetes
  • Simpler to configure
  • No new languages to use or policies to author
  • If the cluster default admission level is configured to privileged, namespace labels can be used to opt namespaces into the pod security profiles.

Cons:

  • Not as flexible or granular as policy-as-code
  • Only 3 levels of restrictions
  • Primarily focused on pods
"},{"location":"security/docs/pods/#summary","title":"Summary","text":"

If you currently do not have a pod security solution, beyond PSP, and your required pod security posture fits the model defined in the Pod Security Standards (PSS), then an easier path may be to adopt the PSS, in lieu of a policy-as-code solution. However, if your pod security posture does not fit the PSS model, or you envision adding additional controls, beyond that defined by PSS, then a policy-as-code solution would seem a better fit.

"},{"location":"security/docs/pods/#recommendations","title":"Recommendations","text":""},{"location":"security/docs/pods/#use-multiple-pod-security-admission-psa-modes-for-a-better-user-experience","title":"Use multiple Pod Security Admission (PSA) modes for a better user experience","text":"

As mentioned earlier, PSA enforce mode prevents pods with PSS violations from being applied, but does not stop higher-level controllers, such as Deployments. In fact, the Deployment will be applied successfully without any indication that the pods failed to be applied. While you can use kubectl to inspect the Deployment object, and discover the failed pods message from the PSA, the user experience could be better. To make the user experience better, multiple PSA modes (audit, enforce, warn) should be used.

apiVersion: v1\nkind: Namespace\nmetadata:\n  name: policy-test\n  labels:\n    pod-security.kubernetes.io/audit: restricted\n    pod-security.kubernetes.io/enforce: restricted\n    pod-security.kubernetes.io/warn: restricted\n

In the above example, with enforce mode defined, when a Deployment manifest with PSS violations in the respective podSpec is attempted to be applied to the Kubernetes API server, the Deployment will be successfully applied, but the pods will not. And, since the audit and warn modes are also enabled, the API server client will receive a warning message and the API server audit log event will be annotated with a message as well.

"},{"location":"security/docs/pods/#restrict-the-containers-that-can-run-as-privileged","title":"Restrict the containers that can run as privileged","text":"

As mentioned, containers that run as privileged inherit all of the Linux capabilities assigned to root on the host. Seldom do containers need these types of privileges to function properly. There are multiple methods that can be used to restrict the permissions and capabilities of containers.

Attention

Fargate is a launch type that enables you to run \"serverless\" container(s) where the containers of a pod are run on infrastructure that AWS manages. With Fargate, you cannot run a privileged container or configure your pod to use hostNetwork or hostPort.

"},{"location":"security/docs/pods/#do-not-run-processes-in-containers-as-root","title":"Do not run processes in containers as root","text":"

All containers run as root by default. This could be problematic if an attacker is able to exploit a vulnerability in the application and get shell access to the running container. You can mitigate this risk a variety of ways. First, by removing the shell from the container image. Second, adding the USER directive to your Dockerfile or running the containers in the pod as a non-root user. The Kubernetes podSpec includes a set of fields, under spec.securityContext, that let you specify the user and/or group under which to run your application. These fields are runAsUser and runAsGroup respectively.

To enforce the use of the spec.securityContext, and its associated elements, within the Kubernetes podSpec, policy-as-code or Pod Security Standards can be added to clusters. These solutions allow you to write and/or use policies or profiles that can validate inbound Kubernetes API server request payloads, before they are persisted into etcd. Furthermore, policy-as-code solutions can mutate inbound requests, and in some cases, generate new requests.

"},{"location":"security/docs/pods/#never-run-docker-in-docker-or-mount-the-socket-in-the-container","title":"Never run Docker in Docker or mount the socket in the container","text":"

While this conveniently lets you to build/run images in Docker containers, you're basically relinquishing complete control of the node to the process running in the container. If you need to build container images on Kubernetes use Kaniko, buildah, or a build service like CodeBuild instead.

Tip

Kubernetes clusters used for CICD processing, such as building container images, should be isolated from clusters running more generalized workloads.

"},{"location":"security/docs/pods/#restrict-the-use-of-hostpath-or-if-hostpath-is-necessary-restrict-which-prefixes-can-be-used-and-configure-the-volume-as-read-only","title":"Restrict the use of hostPath or if hostPath is necessary restrict which prefixes can be used and configure the volume as read-only","text":"

hostPath is a volume that mounts a directory from the host directly to the container. Rarely will pods need this type of access, but if they do, you need to be aware of the risks. By default pods that run as root will have write access to the file system exposed by hostPath. This could allow an attacker to modify the kubelet settings, create symbolic links to directories or files not directly exposed by the hostPath, e.g. /etc/shadow, install ssh keys, read secrets mounted to the host, and other malicious things. To mitigate the risks from hostPath, configure the spec.containers.volumeMounts as readOnly, for example:

volumeMounts:\n- name: hostPath-volume\n    readOnly: true\n    mountPath: /host-path\n

You should also use policy-as-code solutions to restrict the directories that can be used by hostPath volumes, or prevent hostPath usage altogether. You can use the Pod Security Standards Baseline or Restricted policies to prevent the use of hostPath.

For further information about the dangers of privileged escalation, read Seth Art's blog Bad Pods: Kubernetes Pod Privilege Escalation.

"},{"location":"security/docs/pods/#set-requests-and-limits-for-each-container-to-avoid-resource-contention-and-dos-attacks","title":"Set requests and limits for each container to avoid resource contention and DoS attacks","text":"

A pod without requests or limits can theoretically consume all of the resources available on a host. As additional pods are scheduled onto a node, the node may experience CPU or memory pressure which can cause the Kubelet to terminate or evict pods from the node. While you can\u2019t prevent this from happening all together, setting requests and limits will help minimize resource contention and mitigate the risk from poorly written applications that consume an excessive amount of resources.

The podSpec allows you to specify requests and limits for CPU and memory. CPU is considered a compressible resource because it can be oversubscribed. Memory is incompressible, i.e. it cannot be shared among multiple containers.

When you specify requests for CPU or memory, you\u2019re essentially designating the amount of memory that containers are guaranteed to get. Kubernetes aggregates the requests of all the containers in a pod to determine which node to schedule the pod onto. If a container exceeds the requested amount of memory it may be subject to termination if there\u2019s memory pressure on the node.

Limits are the maximum amount of CPU and memory resources that a container is allowed to consume and directly corresponds to the memory.limit_in_bytes value of the cgroup created for the container. A container that exceeds the memory limit will be OOM killed. If a container exceeds its CPU limit, it will be throttled.

Tip

When using container resources.limits it is strongly recommended that container resource usage (a.k.a. Resource Footprints) be data-driven and accurate, based on load testing. Absent an accurate and trusted resource footprint, container resources.limits can be padded. For example, resources.limits.memory could be padded 20-30% higher than observable maximums, to account for potential memory resource limit inaccuracies.

Kubernetes uses three Quality of Service (QoS) classes to prioritize the workloads running on a node. These include:

  • guaranteed
  • burstable
  • best-effort

If limits and requests are not set, the pod is configured as best-effort (lowest priority). Best-effort pods are the first to get killed when there is insufficient memory. If limits are set on all containers within the pod, or if the requests and limits are set to the same values and not equal to 0, the pod is configured as guaranteed (highest priority). Guaranteed pods will not be killed unless they exceed their configured memory limits. If the limits and requests are configured with different values and not equal to 0, or one container within the pod sets limits and the others don\u2019t or have limits set for different resources, the pods are configured as burstable (medium priority). These pods have some resource guarantees, but can be killed once they exceed their requested memory.

Attention

Requests don't affect the memory_limit_in_bytes value of the container's cgroup; the cgroup limit is set to the amount of memory available on the host. Nevertheless, setting the requests value too low could cause the pod to be targeted for termination by the kubelet if the node undergoes memory pressure.

Class Priority Condition Kill Condition Guaranteed highest limit = request != 0 Only exceed memory limits Burstable medium limit != request != 0 Can be killed if exceed request memory Best-Effort lowest limit & request Not Set First to get killed when there's insufficient memory

For additional information about resource QoS, please refer to the Kubernetes documentation.

You can force the use of requests and limits by setting a resource quota on a namespace or by creating a limit range. A resource quota allows you to specify the total amount of resources, e.g. CPU and RAM, allocated to a namespace. When it\u2019s applied to a namespace, it forces you to specify requests and limits for all containers deployed into that namespace. By contrast, limit ranges give you more granular control of the allocation of resources. With limit ranges you can min/max for CPU and memory resources per pod or per container within a namespace. You can also use them to set default request/limit values if none are provided.

Policy-as-code solutions can be used enforce requests and limits. or to even create the resource quotas and limit ranges when namespaces are created.

"},{"location":"security/docs/pods/#do-not-allow-privileged-escalation","title":"Do not allow privileged escalation","text":"

Privileged escalation allows a process to change the security context under which its running. Sudo is a good example of this as are binaries with the SUID or SGID bit. Privileged escalation is basically a way for users to execute a file with the permissions of another user or group. You can prevent a container from using privileged escalation by implementing a policy-as-code mutating policy that sets allowPrivilegeEscalation to false or by setting securityContext.allowPrivilegeEscalation in the podSpec. Policy-as-code policies can also be used to prevent API server requests from succeeding if incorrect settings are detected. Pod Security Standards can also be used to prevent pods from using privilege escalation.

"},{"location":"security/docs/pods/#disable-serviceaccount-token-mounts","title":"Disable ServiceAccount token mounts","text":"

For pods that do not need to access the Kubernetes API, you can disable the automatic mounting of a ServiceAccount token on a pod spec, or for all pods that use a particular ServiceAccount.

Attention

Disabling ServiceAccount mounting does not prevent a pod from having network access to the Kubernetes API. To prevent a pod from having any network access to the Kubernetes API, you will need to modify the EKS cluster endpoint access and use a NetworkPolicy to block pod access.

apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod-no-automount\nspec:\n  automountServiceAccountToken: false\n
apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: sa-no-automount\nautomountServiceAccountToken: false\n
"},{"location":"security/docs/pods/#disable-service-discovery","title":"Disable service discovery","text":"

For pods that do not need to lookup or call in-cluster services, you can reduce the amount of information given to a pod. You can set the Pod's DNS policy to not use CoreDNS, and not expose services in the pod's namespace as environment variables. See the Kubernetes docs on environment variables for more information on service links. The default value for a pod's DNS policy is \"ClusterFirst\" which uses in-cluster DNS, while the non-default value \"Default\" uses the underlying node's DNS resolution. See the Kubernetes docs on Pod DNS policy for more information.

Attention

Disabling service links and changing the pod's DNS policy does not prevent a pod from having network access to the in-cluster DNS service. An attacker can still enumerate services in a cluster by reaching the in-cluster DNS service. (ex: dig SRV *.*.svc.cluster.local @$CLUSTER_DNS_IP) To prevent in-cluster service discovery, use a NetworkPolicy to block pod access

apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod-no-service-info\nspec:\n    dnsPolicy: Default # \"Default\" is not the true default value\n    enableServiceLinks: false\n
"},{"location":"security/docs/pods/#configure-your-images-with-read-only-root-file-system","title":"Configure your images with read-only root file system","text":"

Configuring your images with a read-only root file system prevents an attacker from overwriting a binary on the file system that your application uses. If your application has to write to the file system, consider writing to a temporary directory or attach and mount a volume. You can enforce this by setting the pod's SecurityContext as follows:

...\nsecurityContext:\n  readOnlyRootFilesystem: true\n...\n

Policy-as-code and Pod Security Standards can be used to enforce this behavior.

Info

As per Windows containers in Kubernetes securityContext.readOnlyRootFilesystem cannot be set to true for a container running on Windows as write access is required for registry and system processes to run inside the container.

"},{"location":"security/docs/pods/#tools-and-resources","title":"Tools and resources","text":"
  • Amazon EKS Security Immersion Workshop - Pod Security
  • open-policy-agent/gatekeeper-library: The OPA Gatekeeper policy library a library of OPA/Gatekeeper policies that you can use as a substitute for PSPs.
  • Kyverno Policy Library
  • A collection of common OPA and Kyverno policies for EKS.
  • Policy based countermeasures: part 1
  • Policy based countermeasures: part 2
  • Pod Security Policy Migrator a tool that converts PSPs to OPA/Gatekeeper, KubeWarden, or Kyverno policies
  • NeuVector by SUSE open source, zero-trust container security platform, provides process and filesystem policies as well as admission control rules.
"},{"location":"security/docs/runtime/","title":"Runtime security","text":"

Runtime security provides active protection for your containers while they're running. The idea is to detect and/or prevent malicious activity from occurring inside the container. This can be achieved with a number of mechanisms in the Linux kernel or kernel extensions that are integrated with Kubernetes, such as Linux capabilities, secure computing (seccomp), AppArmor, or SELinux. There are also options like Amazon GuardDuty and third party tools that can assist with establishing baselines and detecting anomalous activity with less manual configuration of Linux kernel mechanisms.

Attention

Kubernetes does not currently provide any native mechanisms for loading seccomp, AppArmor, or SELinux profiles onto Nodes. They either have to be loaded manually or installed onto Nodes when they are bootstrapped. This has to be done prior to referencing them in your Pods because the scheduler is unaware of which nodes have profiles. See below how tools like Security Profiles Operator can help automate provisioning of profiles onto nodes.

"},{"location":"security/docs/runtime/#security-contexts-and-built-in-kubernetes-controls","title":"Security contexts and built-in Kubernetes controls","text":"

Many Linux runtime security mechanisms are tightly integrated with Kubernetes and can be configured through Kubernetes security contexts. One such option is the privileged flag, which is false by default and if enabled is essentially equivalent to root on the host. It is nearly always inappropriate to enable privileged mode in production workloads, but there are many more controls that can provide more granular privileges to containers as appropriate.

"},{"location":"security/docs/runtime/#linux-capabilities","title":"Linux capabilities","text":"

Linux capabilities allow you to grant certain capabilities to a Pod or container without providing all the abilities of the root user. Examples include CAP_NET_ADMIN, which allows configuring network interfaces or firewalls, or CAP_SYS_TIME, which allows manipulation of the system clock.

"},{"location":"security/docs/runtime/#seccomp","title":"Seccomp","text":"

With secure computing (seccomp) you can prevent a containerized application from making certain syscalls to the underlying host operating system's kernel. While the Linux operating system has a few hundred system calls, the lion's share of them are not necessary for running containers. By restricting what syscalls can be made by a container, you can effectively decrease your application's attack surface.

Seccomp works by intercepting syscalls and only allowing those that have been allowlisted to pass through. Docker has a default seccomp profile which is suitable for a majority of general purpose workloads, and other container runtimes like containerd provide comparable defaults. You can configure your container or Pod to use the container runtime's default seccomp profile by adding the following to the securityContext section of the Pod spec:

securityContext:\n  seccompProfile:\n    type: RuntimeDefault\n

As of 1.22 (in alpha, stable as of 1.27), the above RuntimeDefault can be used for all Pods on a Node using a single kubelet flag, --seccomp-default. Then the profile specified in securityContext is only needed for other profiles.

It's also possible to create your own profiles for things that require additional privileges. This can be very tedious to do manually, but there are tools like Inspektor Gadget (also recommended in the network security section for generating network policies) and Security Profiles Operator that support using tools like eBPF or logs to record baseline privilege requirements as seccomp profiles. Security Profiles Operator further allows automating the deployment of recorded profiles to nodes for use by Pods and containers.

"},{"location":"security/docs/runtime/#apparmor-and-selinux","title":"AppArmor and SELinux","text":"

AppArmor and SELinux are known as mandatory access control or MAC systems. They are similar in concept to seccomp but with different APIs and abilities, allowing access control for e.g. specific filesystem paths or network ports. Support for these tools depends on the Linux distribution, with Debian/Ubuntu supporting AppArmor and RHEL/CentOS/Bottlerocket/Amazon Linux 2023 supporting SELinux. Also see the infrastructure security section for further discussion of SELinux.

Both AppArmor and SELinux are integrated with Kubernetes, but as of Kubernetes 1.28 AppArmor profiles must be specified via annotations while SELinux labels can be set through the SELinuxOptions field on the security context directly.

As with seccomp profiles, the Security Profiles Operator mentioned above can assist with deploying profiles onto nodes in the cluster. (In the future, the project also aims to generate profiles for AppArmor and SELinux as it does for seccomp.)

"},{"location":"security/docs/runtime/#recommendations","title":"Recommendations","text":""},{"location":"security/docs/runtime/#use-amazon-guardduty-for-runtime-monitoring-and-detecting-threats-to-your-eks-environments","title":"Use Amazon GuardDuty for runtime monitoring and detecting threats to your EKS environments","text":"

If you do not currently have a solution for continuously monitoring EKS runtimes and analyzing EKS audit logs, and scanning for malware and other suspicious activity, Amazon strongly recommends the use of Amazon GuardDuty for customers who want a simple, fast, secure, scalable, and cost-effective one-click way to protect their AWS environments. Amazon GuardDuty is a security monitoring service that analyzes and processes foundational data sources, such as AWS CloudTrail management events, AWS CloudTrail event logs, VPC flow logs (from Amazon EC2 instances), Kubernetes audit logs, and DNS logs. It also includes EKS runtime monitoring. It uses continuously updated threat intelligence feeds, such as lists of malicious IP addresses and domains, and machine learning to identify unexpected, potentially unauthorized, and malicious activity within your AWS environment. This can include issues like escalation of privileges, use of exposed credentials, or communication with malicious IP addresses, domains, presence of malware on your Amazon EC2 instances and EKS container workloads, or discovery of suspicious API activity. GuardDuty informs you of the status of your AWS environment by producing security findings that you can view in the GuardDuty console or through Amazon EventBridge. GuardDuty also provides support for you to export your findings to an Amazon Simple Storage Service (S3) bucket, and integrate with other services such as AWS Security Hub and Detective.

Watch this AWS Online Tech Talk \"Enhanced threat detection for Amazon EKS with Amazon GuardDuty - AWS Online Tech Talks\" to see how to enable these additional EKS security features step-by-step in minutes.

"},{"location":"security/docs/runtime/#optionally-use-a-3rd-party-solution-for-runtime-monitoring","title":"Optionally: Use a 3rd party solution for runtime monitoring","text":"

Creating and managing seccomp and Apparmor profiles can be difficult if you're not familiar with Linux security. If you don't have the time to become proficient, consider using a 3rd party commercial solution. A lot of them have moved beyond static profiles like Apparmor and seccomp and have begun using machine learning to block or alert on suspicious activity. A handful of these solutions can be found below in the tools section. Additional options can be found on the AWS Marketplace for Containers.

"},{"location":"security/docs/runtime/#consider-adddropping-linux-capabilities-before-writing-seccomp-policies","title":"Consider add/dropping Linux capabilities before writing seccomp policies","text":"

Capabilities involve various checks in kernel functions reachable by syscalls. If the check fails, the syscall typically returns an error. The check can be done either right at the beginning of a specific syscall, or deeper in the kernel in areas that might be reachable through multiple different syscalls (such as writing to a specific privileged file). Seccomp, on the other hand, is a syscall filter which is applied to all syscalls before they are run. A process can set up a filter which allows them to revoke their right to run certain syscalls, or specific arguments for certain syscalls.

Before using seccomp, consider whether adding/removing Linux capabilities gives you the control you need. See Setting capabilities for- containers for further information.

"},{"location":"security/docs/runtime/#see-whether-you-can-accomplish-your-aims-by-using-pod-security-policies-psps","title":"See whether you can accomplish your aims by using Pod Security Policies (PSPs)","text":"

Pod Security Policies offer a lot of different ways to improve your security posture without introducing undue complexity. Explore the options available in PSPs before venturing into building seccomp and Apparmor profiles.

Warning

As of Kubernetes 1.25, PSPs have been removed and replaced with the Pod Security Admission controller. Third-party alternatives which exist include OPA/Gatekeeper and Kyverno. A collection of Gatekeeper constraints and constraint templates for implementing policies commonly found in PSPs can be pulled from the Gatekeeper library repository on GitHub. And many replacements for PSPs can be found in the Kyverno policy library including the full collection of Pod Security Standards.

"},{"location":"security/docs/runtime/#tools-and-resources","title":"Tools and Resources","text":"
  • 7 things you should know before you start
  • AppArmor Loader
  • Setting up nodes with profiles
  • Security Profiles Operator is a Kubernetes enhancement which aims to make it easier for users to use SELinux, seccomp and AppArmor in Kubernetes clusters. It provides capabilities for both generating profiles from running workloads and loading profiles onto Kubernetes nodes for use in Pods.
  • Inspektor Gadget allows inspecting, tracing, and profiling many aspects of runtime behavior on Kubernetes, including assisting in the generation of seccomp profiles.
  • Aqua
  • Qualys
  • Stackrox
  • Sysdig Secure
  • Prisma
  • NeuVector by SUSE open source, zero-trust container security platform, provides process profile rules and file access rules.
"},{"location":"upgrades/","title":"Best Practices for Cluster Upgrades","text":"

This guide shows cluster administrators how to plan and execute their Amazon EKS upgrade strategy. It also describes how to upgrade self-managed nodes, managed node groups, Karpenter nodes, and Fargate nodes. It does not include guidance on EKS Anywhere, self-managed Kubernetes, AWS Outposts, or AWS Local Zones.

"},{"location":"upgrades/#overview","title":"Overview","text":"

A Kubernetes version encompasses both the control plane and the data plane. To ensure smooth operation, both the control plane and the data plane should run the same Kubernetes minor version, such as 1.24. While AWS manages and upgrades the control plane, updating the worker nodes in the data plane is your responsibility.

  • Control plane \u2014 The version of the control plane is determined by the Kubernetes API server. In Amazon EKS clusters, AWS takes care of managing this component. Control plane upgrades can be initiated via the AWS API.
  • Data plane \u2014 The data plane version is associated with the Kubelet versions running on your individual nodes. It's possible to have nodes in the same cluster running different versions. You can check the versions of all nodes by running kubectl get nodes.
"},{"location":"upgrades/#before-upgrading","title":"Before Upgrading","text":"

If you're planning to upgrade your Kubernetes version in Amazon EKS, there are a few important policies, tools, and procedures you should put in place before starting an upgrade.

  • Understand Deprecation Policies \u2014 Gain a deep understanding of how the Kubernetes deprecation policy works. Be aware of any upcoming changes that may affect your existing applications. Newer versions of Kubernetes often phase out certain APIs and features, potentially causing issues for running applications.
  • Review Kubernetes Change Log \u2014 Thoroughly review the Kubernetes change log alongside Amazon EKS Kubernetes versions to understand any possible impact to your cluster, such as breaking changes that may affect your workloads.
  • Assess Cluster Add-Ons Compatibility \u2014 Amazon EKS doesn't automatically update an add-on when new versions are released or after you update your cluster to a new Kubernetes minor version. Review Updating an add-on to understand the compatibility of any existing cluster add-ons with the cluster version you intend to upgrade to.
  • Enable Control Plane Logging \u2014 Enable control plane logging to capture logs, errors, or issues that can arise during the upgrade process. Consider reviewing these logs for any anomalies. Test cluster upgrades in a non-production environment, or integrate automated tests into your continuous integration workflow to assess version compatibility with your applications, controllers, and custom integrations.
  • Explore eksctl for Cluster Management \u2014 Consider using eksctl to manage your EKS cluster. It provides you with the ability to upgrade the control plane, manage add-ons, and handle worker node upgrades out-of-the-box.
  • Opt for Managed Node Groups or EKS on Fargate \u2014 Streamline and automate worker node upgrades by using EKS managed node groups or EKS on Fargate. These options simplify the process and reduce manual intervention.
  • Utilize kubectl Convert Plugin \u2014 Leverage the kubectl convert plugin to facilitate the conversion of Kubernetes manifest files between different API versions. This can help ensure that your configurations remain compatible with the new Kubernetes version.
"},{"location":"upgrades/#keep-your-cluster-up-to-date","title":"Keep your cluster up-to-date","text":"

Staying current with Kubernetes updates is paramount for a secure and efficient EKS environment, reflecting the shared responsibility model in Amazon EKS. By integrating these strategies into your operational workflow, you're positioning yourself to maintain up-to-date, secure clusters that take full advantage of the latest features and improvements. Tactics:

  • Supported Version Policy \u2014 Aligned with the Kubernetes community, Amazon EKS typically provides three active Kubernetes versions. A Kubernetes minor version is under standard support in Amazon EKS for the first 14 months after it's released. Once a version is past the end of standard support date, it enters extended support for the next 12 months. Deprecation notices are issued at least 60 days before a version reaches its end of standard support date. For more details, refer to the EKS Version Lifecycle docs.
  • Auto-Upgrade Policy \u2014 We strongly recommend staying in sync with Kubernetes updates in your EKS cluster. Clusters running on a Kubernetes version that has completed its 26-month lifecycle (14 months of standard support plus 12 months of extended support) will be auto-upgraded to the next version. Note that you can disable extended support. Failure to proactively upgrade before a version's end-of-life triggers an automatic upgrade, which could disrupt your workloads and systems. For additional information, consult the EKS Version FAQs.
  • Create Upgrade Runbooks \u2014 Establish a well-documented process for managing upgrades. As part of your proactive approach, develop runbooks and specialized tools tailored to your upgrade process. This not only enhances your preparedness but also simplifies complex transitions. Make it a standard practice to upgrade your clusters at least once a year. This practice aligns you with ongoing technological advancements, thereby boosting the efficiency and security of your environment.
"},{"location":"upgrades/#review-the-eks-release-calendar","title":"Review the EKS release calendar","text":"

Review the EKS Kubernetes release calendar to learn when new versions are coming, and when support for specific versions end. Generally, EKS releases three minor versions of Kubernetes annually, and each minor version is supported for about 14 months.

Additionally, review the upstream Kubernetes release information.

"},{"location":"upgrades/#understand-how-the-shared-responsibility-model-applies-to-cluster-upgrades","title":"Understand how the shared responsibility model applies to cluster upgrades","text":"

You are responsible for initiating upgrade for both cluster control plane as well as the data plane. Learn how to initiate an upgrade. When you initiate a cluster upgrade, AWS manages upgrading the cluster control plane. You are responsible for upgrading the data plane, including Fargate pods and other add-ons. You must validate and plan upgrades for workloads running on your cluster to ensure their availability and operations are not impacted after cluster upgrade

"},{"location":"upgrades/#upgrade-clusters-in-place","title":"Upgrade clusters in-place","text":"

EKS supports an in-place cluster upgrade strategy. This maintains cluster resources, and keeps cluster configuration consistent (e.g., API endpoint, OIDC, ENIs, load balancers). This is less disruptive for cluster users, and it will use the existing workloads and resources in the cluster without requiring you to redeploy workloads or migrate external resources (e.g., DNS, storage).

When performing an in-place cluster upgrade, it is important to note that only one minor version upgrade can be executed at a time (e.g., from 1.24 to 1.25).

This means that if you need to update multiple versions, a series of sequential upgrades will be required. Planning sequential upgrades is more complicated, and has a higher risk of downtime. In this situation, evaluate a blue/green cluster upgrade strategy.

"},{"location":"upgrades/#upgrade-your-control-plane-and-data-plane-in-sequence","title":"Upgrade your control plane and data plane in sequence","text":"

To upgrade a cluster you will need to take the following actions:

  1. Review the Kubernetes and EKS release notes.
  2. Take a backup of the cluster. (optional)
  3. Identify and remediate deprecated and removed API usage in your workloads.
  4. Ensure Managed Node Groups, if used, are on the same Kubernetes version as the control plane. EKS managed node groups and nodes created by EKS Fargate Profiles support 2 minor version skew between the control plane and data plane for Kubernetes version 1.27 and below. Starting 1.28 and above, EKS managed node groups and nodes created by EKS Fargate Profiles support 3 minor version skew betweeen control plane and data plane. For example, if your EKS control plane version is 1.28, you can safely use kubelet versions as old as 1.25. If your EKS version is 1.27, the oldest kubelet version you can use is 1.25.
  5. Upgrade the cluster control plane using the AWS console or cli.
  6. Review add-on compatibility. Upgrade your Kubernetes add-ons and custom controllers, as required.
  7. Update kubectl.
  8. Upgrade the cluster data plane. Upgrade your nodes to the same Kubernetes minor version as your upgraded cluster.
"},{"location":"upgrades/#use-the-eks-documentation-to-create-an-upgrade-checklist","title":"Use the EKS Documentation to create an upgrade checklist","text":"

The EKS Kubernetes version documentation includes a detailed list of changes for each version. Build a checklist for each upgrade.

For specific EKS version upgrade guidance, review the documentation for notable changes and considerations for each version.

  • EKS 1.27
  • EKS 1.26
  • EKS 1.25
  • EKS 1.24
  • EKS 1.23
  • EKS 1.22
"},{"location":"upgrades/#upgrade-add-ons-and-components-using-the-kubernetes-api","title":"Upgrade add-ons and components using the Kubernetes API","text":"

Before you upgrade a cluster, you should understand what versions of Kubernetes components you are using. Inventory cluster components, and identify components that use the Kubernetes API directly. This includes critical cluster components such as monitoring and logging agents, cluster autoscalers, container storage drivers (e.g. EBS CSI, EFS CSI), ingress controllers, and any other workloads or add-ons that rely on the Kubernetes API directly.

Tip

Critical cluster components are often installed in a *-system namespace

kubectl get ns | grep '-system'\n

Once you have identified components that rely the Kubernetes API, check their documentation for version compatibility and upgrade requirements. For example, see the AWS Load Balancer Controller documentation for version compatibility. Some components may need to be upgraded or configuration changed before proceeding with a cluster upgrade. Some critical components to check include CoreDNS, kube-proxy, VPC CNI, and storage drivers.

Clusters often contain many workloads that use the Kubernetes API and are required for workload functionality such as ingress controllers, continuous delivery systems, and monitoring tools. When you upgrade an EKS cluster, you must also upgrade your add-ons and third-party tools to make sure they are compatible.

See the following examples of common add-ons and their relevant upgrade documentation:

  • Amazon VPC CNI: For the recommended version of the Amazon VPC CNI add-on for each cluster version, see Updating the Amazon VPC CNI plugin for Kubernetes self-managed add-on. When installed as an Amazon EKS Add-on, it can only be upgraded one minor version at a time.
  • kube-proxy: See Updating the Kubernetes kube-proxy self-managed add-on.
  • CoreDNS: See Updating the CoreDNS self-managed add-on.
  • AWS Load Balancer Controller: The AWS Load Balancer Controller needs to be compatible with the EKS version you have deployed. See the installation guide for more information.
  • Amazon Elastic Block Store (Amazon EBS) Container Storage Interface (CSI) driver: For installation and upgrade information, see Managing the Amazon EBS CSI driver as an Amazon EKS add-on.
  • Amazon Elastic File System (Amazon EFS) Container Storage Interface (CSI) driver: For installation and upgrade information, see Amazon EFS CSI driver.
  • Kubernetes Metrics Server: For more information, see metrics-server on GitHub.
  • Kubernetes Cluster Autoscaler: To upgrade the version of Kubernetes Cluster Autoscaler, change the version of the image in the deployment. The Cluster Autoscaler is tightly coupled with the Kubernetes scheduler. You will always need to upgrade it when you upgrade the cluster. Review the GitHub releases to find the address of the latest release corresponding to your Kubernetes minor version.
  • Karpenter: For installation and upgrade information, see the Karpenter documentation.
"},{"location":"upgrades/#verify-basic-eks-requirements-before-upgrading","title":"Verify basic EKS requirements before upgrading","text":"

AWS requires certain resources in your account to complete the upgrade process. If these resources aren\u2019t present, the cluster cannot be upgraded. A control plane upgrade requires the following resources:

  1. Available IP addresses: Amazon EKS requires up to five available IP addresses from the subnets you specified when you created the cluster in order to update the cluster. If not, update your cluster configuration to include new cluster subnets prior to performing the version update.
  2. EKS IAM role: The control plane IAM role is still present in the account with the necessary permissions.
  3. If your cluster has secret encryption enabled, then make sure that the cluster IAM role has permission to use the AWS Key Management Service (AWS KMS) key.
"},{"location":"upgrades/#verify-available-ip-addresses","title":"Verify available IP addresses","text":"

To update the cluster, Amazon EKS requires up to five available IP addresses from the subnets that you specified when you created your cluster.

To verify that your subnets have enough IP addresses to upgrade the cluster you can run the following command:

CLUSTER=<cluster name>\naws ec2 describe-subnets --subnet-ids \\\n  $(aws eks describe-cluster --name ${CLUSTER} \\\n  --query 'cluster.resourcesVpcConfig.subnetIds' \\\n  --output text) \\\n  --query 'Subnets[*].[SubnetId,AvailabilityZone,AvailableIpAddressCount]' \\\n  --output table\n\n----------------------------------------------------\n|                  DescribeSubnets                 |\n+---------------------------+--------------+-------+\n|  subnet-067fa8ee8476abbd6 |  us-east-1a  |  8184 |\n|  subnet-0056f7403b17d2b43 |  us-east-1b  |  8153 |\n|  subnet-09586f8fb3addbc8c |  us-east-1a  |  8120 |\n|  subnet-047f3d276a22c6bce |  us-east-1b  |  8184 |\n+---------------------------+--------------+-------+\n

The VPC CNI Metrics Helper may be used to create a CloudWatch dashboard for VPC metrics. Amazon EKS recommends updating the cluster subnets using the \"UpdateClusterConfiguration\" API prior to beginning a Kubernetes version upgrade if you are running out of IP addresses in the subnets initially specified during cluster creation. Please verify that the new subnets you will be provided:

  • belong to same set of AZs that are selected during cluster creation.
  • belong to the same VPC provided during cluster creation

Please consider associating additional CIDR blocks if the IP addresses in the existing VPC CIDR block run out. AWS enables the association of additional CIDR blocks with your existing cluster VPC, effectively expanding your IP address pool. This expansion can be accomplished by introducing additional private IP ranges (RFC 1918) or, if necessary, public IP ranges (non-RFC 1918). You must add new VPC CIDR blocks and allow VPC refresh to complete before Amazon EKS can use the new CIDR. After that, you can update the subnets based on the newly set up CIDR blocks to the VPC.

"},{"location":"upgrades/#verify-eks-iam-role","title":"Verify EKS IAM role","text":"

To verify that the IAM role is available and has the correct assume role policy in your account you can run the following commands:

CLUSTER=<cluster name>\nROLE_ARN=$(aws eks describe-cluster --name ${CLUSTER} \\\n  --query 'cluster.roleArn' --output text)\naws iam get-role --role-name ${ROLE_ARN##*/} \\\n  --query 'Role.AssumeRolePolicyDocument'\n\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"Service\": \"eks.amazonaws.com\"\n            },\n            \"Action\": \"sts:AssumeRole\"\n        }\n    ]\n}\n
"},{"location":"upgrades/#migrate-to-eks-add-ons","title":"Migrate to EKS Add-ons","text":"

Amazon EKS automatically installs add-ons such as the Amazon VPC CNI plugin for Kubernetes, kube-proxy, and CoreDNS for every cluster. Add-ons may be self-managed, or installed as Amazon EKS Add-ons. Amazon EKS Add-ons is an alternate way to manage add-ons using the EKS API.

You can use Amazon EKS Add-ons to update versions with a single command. For Example:

aws eks update-addon \u2014cluster-name my-cluster \u2014addon-name vpc-cni \u2014addon-version version-number \\\n--service-account-role-arn arn:aws:iam::111122223333:role/role-name \u2014configuration-values '{}' \u2014resolve-conflicts PRESERVE\n

Check if you have any EKS Add-ons with:

aws eks list-addons --cluster-name <cluster name>\n

Warning

EKS Add-ons are not automatically upgraded during a control plane upgrade. You must initiate EKS add-on updates, and select the desired version.

  • You are responsible for selecting a compatible version from all available versions. Review the guidance on add-on version compatibility.
  • Amazon EKS Add-ons may only be upgraded one minor version at a time.

Learn more about what components are available as EKS Add-ons, and how to get started.

Learn how to supply a custom configuration to an EKS Add-on.

"},{"location":"upgrades/#identify-and-remediate-removed-api-usage-before-upgrading-the-control-plane","title":"Identify and remediate removed API usage before upgrading the control plane","text":"

You should identify API usage of removed APIs before upgrading your EKS control plane. To do that we recommend using tools that can check a running cluster or static, rendered Kubernetes manifest files.

Running the check against static manifest files is generally more accurate. If run against live clusters, these tools may return false positives.

A deprecated Kubernetes API does not mean the API has been removed. You should check the Kubernetes Deprecation Policy to understand how API removal affects your workloads.

"},{"location":"upgrades/#cluster-insights","title":"Cluster Insights","text":"

Cluster Insights is a feature that provides findings on issues that may impact the ability to upgrade an EKS cluster to newer versions of Kubernetes. These findings are curated and managed by Amazon EKS and offer recommendations on how to remediate them. By leveraging Cluster Insights, you can minimize the effort spent to upgrade to newer Kubernetes versions.

To view insights of an EKS cluster, you can run the command:

aws eks list-insights --region <region-code> --cluster-name <my-cluster>\n\n{\n    \"insights\": [\n        {\n            \"category\": \"UPGRADE_READINESS\", \n            \"name\": \"Deprecated APIs removed in Kubernetes v1.29\", \n            \"insightStatus\": {\n                \"status\": \"PASSING\", \n                \"reason\": \"No deprecated API usage detected within the last 30 days.\"\n            }, \n            \"kubernetesVersion\": \"1.29\", \n            \"lastTransitionTime\": 1698774710.0, \n            \"lastRefreshTime\": 1700157422.0, \n            \"id\": \"123e4567-e89b-42d3-a456-579642341238\", \n            \"description\": \"Checks for usage of deprecated APIs that are scheduled for removal in Kubernetes v1.29. Upgrading your cluster before migrating to the updated APIs supported by v1.29 could cause application impact.\"\n        }\n    ]\n}\n

For a more descriptive output about the insight received, you can run the command:

aws eks describe-insight --region <region-code> --id <insight-id> --cluster-name <my-cluster>\n

You also have the option to view insights in the Amazon EKS Console. After selecting your cluster from the cluster list, insight findings are located under the Upgrade Insights tab.

If you find a cluster insight with \"status\": ERROR, you must address the issue prior to performing the cluster upgrade. Run the aws eks describe-insight command which will share the following remediation advice:

Resources affected:

\"resources\": [\n      {\n        \"insightStatus\": {\n          \"status\": \"ERROR\"\n        },\n        \"kubernetesResourceUri\": \"/apis/policy/v1beta1/podsecuritypolicies/null\"\n      }\n]\n

APIs deprecated:

\"deprecationDetails\": [\n      {\n        \"usage\": \"/apis/flowcontrol.apiserver.k8s.io/v1beta2/flowschemas\", \n        \"replacedWith\": \"/apis/flowcontrol.apiserver.k8s.io/v1beta3/flowschemas\", \n        \"stopServingVersion\": \"1.29\", \n        \"clientStats\": [], \n        \"startServingReplacementVersion\": \"1.26\"\n      }\n]\n

Recommended action to take:

\"recommendation\": \"Update manifests and API clients to use newer Kubernetes APIs if applicable before upgrading to Kubernetes v1.26.\"\n

Utilizing cluster insights through the EKS Console or CLI help speed the process of successfully upgrading EKS cluster versions. Learn more with the following resources: * Official EKS Docs * Cluster Insights launch blog.

"},{"location":"upgrades/#kube-no-trouble","title":"Kube-no-trouble","text":"

Kube-no-trouble is an open source command line utility with the command kubent. When you run kubent without any arguments it will use your current KubeConfig context and scan the cluster and print a report with what APIs will be deprecated and removed.

kubent\n\n4:17PM INF >>> Kube No Trouble `kubent` <<<\n4:17PM INF version 0.7.0 (git sha d1bb4e5fd6550b533b2013671aa8419d923ee042)\n4:17PM INF Initializing collectors and retrieving data\n4:17PM INF Target K8s version is 1.24.8-eks-ffeb93d\n4:l INF Retrieved 93 resources from collector name=Cluster\n4:17PM INF Retrieved 16 resources from collector name=\"Helm v3\"\n4:17PM INF Loaded ruleset name=custom.rego.tmpl\n4:17PM INF Loaded ruleset name=deprecated-1-16.rego\n4:17PM INF Loaded ruleset name=deprecated-1-22.rego\n4:17PM INF Loaded ruleset name=deprecated-1-25.rego\n4:17PM INF Loaded ruleset name=deprecated-1-26.rego\n4:17PM INF Loaded ruleset name=deprecated-future.rego\n__________________________________________________________________________________________\n>>> Deprecated APIs removed in 1.25 <<<\n------------------------------------------------------------------------------------------\nKIND                NAMESPACE     NAME             API_VERSION      REPLACE_WITH (SINCE)\nPodSecurityPolicy   <undefined>   eks.privileged   policy/v1beta1   <removed> (1.21.0)\n

It can also be used to scan static manifest files and helm packages. It is recommended to run kubent as part of a continuous integration (CI) process to identify issues before manifests are deployed. Scanning manifests is also more accurate than scanning live clusters.

Kube-no-trouble provides a sample Service Account and Role with the appropriate permissions for scanning the cluster.

"},{"location":"upgrades/#pluto","title":"Pluto","text":"

Another option is pluto which is similar to kubent because it supports scanning a live cluster, manifest files, helm charts and has a GitHub Action you can include in your CI process.

pluto detect-all-in-cluster\n\nNAME             KIND                VERSION          REPLACEMENT   REMOVED   DEPRECATED   REPL AVAIL  \neks.privileged   PodSecurityPolicy   policy/v1beta1                 false     true         true\n
"},{"location":"upgrades/#resources","title":"Resources","text":"

To verify that your cluster don't use deprecated APIs before the upgrade, you should monitor:

  • metric apiserver_requested_deprecated_apis since Kubernetes v1.19:
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis\n\napiserver_requested_deprecated_apis{group=\"policy\",removed_release=\"1.25\",resource=\"podsecuritypolicies\",subresource=\"\",version=\"v1beta1\"} 1\n
  • events in the audit logs with k8s.io/deprecated set to true:
CLUSTER=\"<cluster_name>\"\nQUERY_ID=$(aws logs start-query \\\n --log-group-name /aws/eks/${CLUSTER}/cluster \\\n --start-time $(date -u --date=\"-30 minutes\" \"+%s\") # or date -v-30M \"+%s\" on MacOS \\\n --end-time $(date \"+%s\") \\\n --query-string 'fields @message | filter `annotations.k8s.io/deprecated`=\"true\"' \\\n --query queryId --output text)\n\necho \"Query started (query id: $QUERY_ID), please hold ...\" && sleep 5 # give it some time to query\n\naws logs get-query-results --query-id $QUERY_ID\n

Which will output lines if deprecated APIs are in use:

{\n    \"results\": [\n        [\n            {\n                \"field\": \"@message\",\n                \"value\": \"{\\\"kind\\\":\\\"Event\\\",\\\"apiVersion\\\":\\\"audit.k8s.io/v1\\\",\\\"level\\\":\\\"Request\\\",\\\"auditID\\\":\\\"8f7883c6-b3d5-42d7-967a-1121c6f22f01\\\",\\\"stage\\\":\\\"ResponseComplete\\\",\\\"requestURI\\\":\\\"/apis/policy/v1beta1/podsecuritypolicies?allowWatchBookmarks=true\\\\u0026resourceVersion=4131\\\\u0026timeout=9m19s\\\\u0026timeoutSeconds=559\\\\u0026watch=true\\\",\\\"verb\\\":\\\"watch\\\",\\\"user\\\":{\\\"username\\\":\\\"system:apiserver\\\",\\\"uid\\\":\\\"8aabfade-da52-47da-83b4-46b16cab30fa\\\",\\\"groups\\\":[\\\"system:masters\\\"]},\\\"sourceIPs\\\":[\\\"::1\\\"],\\\"userAgent\\\":\\\"kube-apiserver/v1.24.16 (linux/amd64) kubernetes/af930c1\\\",\\\"objectRef\\\":{\\\"resource\\\":\\\"podsecuritypolicies\\\",\\\"apiGroup\\\":\\\"policy\\\",\\\"apiVersion\\\":\\\"v1beta1\\\"},\\\"responseStatus\\\":{\\\"metadata\\\":{},\\\"code\\\":200},\\\"requestReceivedTimestamp\\\":\\\"2023-10-04T12:36:11.849075Z\\\",\\\"stageTimestamp\\\":\\\"2023-10-04T12:45:30.850483Z\\\",\\\"annotations\\\":{\\\"authorization.k8s.io/decision\\\":\\\"allow\\\",\\\"authorization.k8s.io/reason\\\":\\\"\\\",\\\"k8s.io/deprecated\\\":\\\"true\\\",\\\"k8s.io/removed-release\\\":\\\"1.25\\\"}}\"\n            },\n[...]\n
"},{"location":"upgrades/#update-kubernetes-workloads-use-kubectl-convert-to-update-manifests","title":"Update Kubernetes workloads. Use kubectl-convert to update manifests","text":"

After you have identified what workloads and manifests need to be updated, you may need to change the resource type in your manifest files (e.g. PodSecurityPolicies to PodSecurityStandards). This will require updating the resource specification and additional research depending on what resource is being replaced.

If the resource type is staying the same but API version needs to be updated you can use the kubectl-convert command to automatically convert your manifest files. For example, to convert an older Deployment to apps/v1. For more information, see Install kubectl convert pluginon the Kubernetes website.

kubectl-convert -f <file> --output-version <group>/<version>

"},{"location":"upgrades/#configure-poddisruptionbudgets-and-topologyspreadconstraints-to-ensure-availability-of-your-workloads-while-the-data-plane-is-upgraded","title":"Configure PodDisruptionBudgets and topologySpreadConstraints to ensure availability of your workloads while the data plane is upgraded","text":"

Ensure your workloads have the proper PodDisruptionBudgets and topologySpreadConstraints to ensure availability of your workloads while the data plane is upgraded. Not every workload requires the same level of availability so you need to validate the scale and requirements of your workload.

Make sure workloads are spread in multiple Availability Zones and on multiple hosts with topology spreads will give a higher level of confidence that workloads will migrate to the new data plane automatically without incident.

Here is an example workload that will always have 80% of replicas available and spread replicas across zones and hosts

apiVersion: policy/v1\nkind: PodDisruptionBudget\nmetadata:\n  name: myapp\nspec:\n  minAvailable: \"80%\"\n  selector:\n    matchLabels:\n      app: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  replicas: 10\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: public.ecr.aws/eks-distro/kubernetes/pause:3.2\n        name: myapp\n        resources:\n          requests:\n            cpu: \"1\"\n            memory: 256M\n      topologySpreadConstraints:\n      - labelSelector:\n          matchLabels:\n            app: host-zone-spread\n        maxSkew: 2\n        topologyKey: kubernetes.io/hostname\n        whenUnsatisfiable: DoNotSchedule\n      - labelSelector:\n          matchLabels:\n            app: host-zone-spread\n        maxSkew: 2\n        topologyKey: topology.kubernetes.io/zone\n        whenUnsatisfiable: DoNotSchedule\n

AWS Resilience Hub has added Amazon Elastic Kubernetes Service (Amazon EKS) as a supported resource. Resilience Hub provides a single place to define, validate, and track the resilience of your applications so that you can avoid unnecessary downtime caused by software, infrastructure, or operational disruptions.

"},{"location":"upgrades/#use-managed-node-groups-or-karpenter-to-simplify-data-plane-upgrades","title":"Use Managed Node Groups or Karpenter to simplify data plane upgrades","text":"

Managed Node Groups and Karpenter both simplify node upgrades, but they take different approaches.

Managed node groups automate the provisioning and lifecycle management of nodes. This means that you can create, automatically update, or terminate nodes with a single operation.

For Karpenter data plane upgrades, refer to below sections: * Use Drift for Karpenter managed nodes * Use ExpireAfter for Karpenter managed nodes

"},{"location":"upgrades/#confirm-version-compatibility-with-existing-nodes-and-the-control-plane","title":"Confirm version compatibility with existing nodes and the control plane","text":"

Before proceeding with a Kubernetes upgrade in Amazon EKS, it's vital to ensure compatibility between your managed node groups, self-managed nodes, and the control plane. Compatibility is determined by the Kubernetes version you are using, and it varies based on different scenarios. Tactics:

  • Kubernetes v1.28+ \u2014 **** Starting from Kubernetes version 1.28 and onwards, there's a more lenient version policy for core components. Specifically, the supported skew between the Kubernetes API server and the kubelet has been extended by one minor version, going from n-2 to n-3. For example, if your EKS control plane version is 1.28, you can safely use kubelet versions as old as 1.25. This version skew is supported across AWS Fargate, managed node groups, and self-managed nodes. We highly recommend keeping your Amazon Machine Image (AMI) versions up-to-date for security reasons. Older kubelet versions might pose security risks due to potential Common Vulnerabilities and Exposures (CVEs), which could outweigh the benefits of using older kubelet versions.
  • Kubernetes < v1.28 \u2014 If you are using a version older than v1.28, the supported skew between the API server and the kubelet is n-2. For example, if your EKS version is 1.27, the oldest kubelet version you can use is 1.25. This version skew is applicable across AWS Fargate, managed node groups, and self-managed nodes.
"},{"location":"upgrades/#use-drift-for-karpenter-managed-nodes","title":"Use Drift for Karpenter managed nodes","text":"

Karpenter\u2019s Drift can automatically upgrade the Karpenter-provisioned nodes to stay in-sync with the EKS control plane. Refer to How to upgrade an EKS Cluster with Karpenter for more details.

This means that if the AMI ID specified in the Karpenter EC2 Nodeclass is updated, Karpenter will detect the drift and start replacing the nodes with the new AMI. To understand how Karpenter manages AMIs and the different options available to Karpenter users to control the AMI upgrade process see the documentation on how to manage AMIs in Karpenter.

"},{"location":"upgrades/#use-expireafter-for-karpenter-managed-nodes","title":"Use ExpireAfter for Karpenter managed nodes","text":"

Karpenter will mark nodes as expired and disrupt them after they have lived the duration specified in `spec.disruption.expireAfter. This node expiry helps to reduce security vulnerabilities and issues that can arise from long-running nodes, such as file fragmentation or memory leaks. When you set a value for expireAfter in your NodePool, this activates node expiry. For more information, see Disruption on the Karpenter website.

If you're using automatic AMI upgrades, ExpireAfter can periodically refresh and upgrade your nodes.

If it\u2019s happened that the node is drifted, but hasn\u2019t been cleaned up, node expiration will also replace the instance with the new AMI in EC2NodeClass.

"},{"location":"upgrades/#use-eksctl-to-automate-upgrades-for-self-managed-node-groups","title":"Use eksctl to automate upgrades for self-managed node groups","text":"

Self managed node groups are EC2 instances that were deployed in your account and attached to the cluster outside of the EKS service. These are usually deployed and managed by some form of automation tooling. To upgrade self-managed node groups you should refer to your tools documentation.

For example, eksctl supports deleting and draining self-managed nodes.

Some common tools include:

  • eksctl
  • kOps
  • EKS Blueprints
"},{"location":"upgrades/#backup-the-cluster-before-upgrading","title":"Backup the cluster before upgrading","text":"

New versions of Kubernetes introduce significant changes to your Amazon EKS cluster. After you upgrade a cluster, you can\u2019t downgrade it.

Velero is an community supported open-source tool that can be used to take backups of existing clusters and apply the backups to a new cluster.

Note that you can only create new clusters for Kubernetes versions currently supported by EKS. If the version your cluster is currently running is still supported and an upgrade fails, you can create a new cluster with the original version and restore the data plane. Note that AWS resources, including IAM, are not included in the backup by Velero. These resources would need to be recreated.

"},{"location":"upgrades/#restart-fargate-deployments-after-upgrading-the-control-plane","title":"Restart Fargate deployments after upgrading the control plane","text":"

To upgrade Fargate data plane nodes you need to redeploy the workloads. You can identify which workloads are running on fargate nodes by listing all pods with the -o wide option. Any node name that begins with fargate- will need to be redeployed in the cluster.

"},{"location":"upgrades/#evaluate-bluegreen-clusters-as-an-alternative-to-in-place-cluster-upgrades","title":"Evaluate Blue/Green Clusters as an alternative to in-place cluster upgrades","text":"

Some customers prefer to do a blue/green upgrade strategy. This can have benefits, but also includes downsides that should be considered.

Benefits include:

  • Possible to change multiple EKS versions at once (e.g. 1.23 to 1.25)
  • Able to switch back to the old cluster
  • Creates a new cluster which may be managed with newer systems (e.g. terraform)
  • Workloads can be migrated individually

Some downsides include:

  • API endpoint and OIDC change which requires updating consumers (e.g. kubectl and CI/CD)
  • Requires 2 clusters to be run in parallel during the migration, which can be expensive and limit region capacity
  • More coordination is needed if workloads depend on each other to be migrated together
  • Load balancers and external DNS cannot easily span multiple clusters

While this strategy is possible to do, it is more expensive than an in-place upgrade and requires more time for coordination and workload migrations. It may be required in some situations and should be planned carefully.

With high degrees of automation and declarative systems like GitOps, this may be easier to do. You will need to take additional precautions for stateful workloads so data is backed up and migrated to new clusters.

Review these blogs posts for more information:

  • Kubernetes cluster upgrade: the blue-green deployment strategy
  • Blue/Green or Canary Amazon EKS clusters migration for stateless ArgoCD workloads
"},{"location":"upgrades/#track-planned-major-changes-in-the-kubernetes-project-think-ahead","title":"Track planned major changes in the Kubernetes project \u2014 Think ahead","text":"

Don\u2019t look only at the next version. Review new versions of Kubernetes as they are released, and identify major changes. For example, some applications directly used the docker API, and support for Container Runtime Interface (CRI) for Docker (also known as Dockershim) was removed in Kubernetes 1.24. This kind of change requires more time to prepare for.

Review all documented changes for the version that you\u2019re upgrading to, and note any required upgrade steps. Also, note any requirements or procedures that are specific to Amazon EKS managed clusters.

  • Kubernetes changelog
"},{"location":"upgrades/#specific-guidance-on-feature-removals","title":"Specific Guidance on Feature Removals","text":""},{"location":"upgrades/#removal-of-dockershim-in-125-use-detector-for-docker-socket-dds","title":"Removal of Dockershim in 1.25 - Use Detector for Docker Socket (DDS)","text":"

The EKS Optimized AMI for 1.25 no longer includes support for Dockershim. If you have a dependency on Dockershim, e.g. you are mounting the Docker socket, you will need to remove those dependencies before upgrading your worker nodes to 1.25.

Find instances where you have a dependency on the Docker socket before upgrading to 1.25. We recommend using Detector for Docker Socket (DDS), a kubectl plugin..

"},{"location":"upgrades/#removal-of-podsecuritypolicy-in-125-migrate-to-pod-security-standards-or-a-policy-as-code-solution","title":"Removal of PodSecurityPolicy in 1.25 - Migrate to Pod Security Standards or a policy-as-code solution","text":"

PodSecurityPolicy was deprecated in Kubernetes 1.21, and has been removed in Kubernetes 1.25. If you are using PodSecurityPolicy in your cluster, then you must migrate to the built-in Kubernetes Pod Security Standards (PSS) or to a policy-as-code solution before upgrading your cluster to version 1.25 to avoid interruptions to your workloads.

AWS published a detailed FAQ in the EKS documentation.

Review the Pod Security Standards (PSS) and Pod Security Admission (PSA) best practices.

Review the PodSecurityPolicy Deprecation blog post on the Kubernetes website.

"},{"location":"upgrades/#deprecation-of-in-tree-storage-driver-in-123-migrate-to-container-storage-interface-csi-drivers","title":"Deprecation of In-Tree Storage Driver in 1.23 - Migrate to Container Storage Interface (CSI) Drivers","text":"

The Container Storage Interface (CSI) was designed to help Kubernetes replace its existing, in-tree storage driver mechanisms. The Amazon EBS container storage interface (CSI) migration feature is enabled by default in Amazon EKS 1.23 and later clusters. If you have pods running on a version 1.22 or earlier cluster, then you must install the Amazon EBS CSI driver before updating your cluster to version 1.23 to avoid service interruption.

Review the Amazon EBS CSI migration frequently asked questions.

"},{"location":"upgrades/#additional-resources","title":"Additional Resources","text":""},{"location":"upgrades/#clowdhaus-eks-upgrade-guidance","title":"ClowdHaus EKS Upgrade Guidance","text":"

ClowdHaus EKS Upgrade Guidance is a CLI to aid in upgrading Amazon EKS clusters. It can analyze a cluster for any potential issues to remediate prior to upgrade.

"},{"location":"upgrades/#gonogo","title":"GoNoGo","text":"

GoNoGo is an alpha-stage tool to determine the upgrade confidence of your cluster add-ons.

"},{"location":"windows/docs/ami/","title":"Amazon EKS optimized Windows AMI management","text":"

Windows Amazon EKS optimized AMIs are built on top of Windows Server 2019 and Windows Server 2022. They are configured to serve as the base image for Amazon EKS nodes. By default, the AMIs include the following components: - kubelet - kube-proxy - AWS IAM Authenticator for Kubernetes - csi-proxy - containerd

You can programmatically retrieve the Amazon Machine Image (AMI) ID for Amazon EKS optimized AMIs by querying the AWS Systems Manager Parameter Store API. This parameter eliminates the need for you to manually look up Amazon EKS optimized AMI IDs. For more information about the Systems Manager Parameter Store API, see GetParameter. Your user account must have the ssm:GetParameter IAM permission to retrieve the Amazon EKS optimized AMI metadata.

The following example retrieves the AMI ID for the latest Amazon EKS optimized AMI for Windows Server 2019 LTSC Core. The version number listed in the AMI name relates to the corresponding Kubernetes build it is prepared for.

aws ssm get-parameter --name /aws/service/ami-windows-latest/Windows_Server-2019-English-Core-EKS_Optimized-1.21/image_id --region us-east-1 --query \"Parameter.Value\" --output text\n

Example output:

ami-09770b3eec4552d4e\n
"},{"location":"windows/docs/ami/#managing-your-own-amazon-eks-optimized-windows-ami","title":"Managing your own Amazon EKS optimized Windows AMI","text":"

An essential step towards production environments is maintaining the same Amazon EKS optimized Windows AMI and kubelet version across the Amazon EKS cluster.

Using the same version across the Amazon EKS cluster reduces the time during troubleshooting and increases cluster consistency. Amazon EC2 Image Builder helps create and maintain custom Amazon EKS optimized Windows AMIs to be used across an Amazon EKS cluster.

Use Amazon EC2 Image Builder to select between Windows Server versions, AWS Windows Server AMI release dates, and/or OS build version. The build components step, allows you to select between existing EKS Optimized Windows Artifacts as well as the kubelet versions. For more information: https://docs.aws.amazon.com/eks/latest/userguide/eks-custom-ami-windows.html

NOTE: Prior to selecting a base image, consult the Windows Server Version and License section for important details pertaining to release channel updates.

"},{"location":"windows/docs/ami/#configuring-faster-launching-for-custom-eks-optimized-amis","title":"Configuring faster launching for custom EKS optimized AMIs","text":"

When using a custom Windows Amazon EKS optimized AMI, Windows worker nodes can be launched up to 65% faster by enabling the Fast Launch feature. This feature maintains a set of pre-provisioned snapshots which have the Sysprep specialize, Windows Out of Box Experience (OOBE) steps and required reboots already completed. These snapshots are then used on subsequent launches, reducing the time to scale-out or replace nodes. Fast Launch can be only enabled for AMIs you own through the EC2 console or in the AWS CLI and the number of snapshots maintained is configurable.

NOTE: Fast Launch is not compatible with the default Amazon-provided EKS optimized AMI, create a custom AMI as above before attempting to enable it.

For more information: AWS Windows AMIs - Configure your AMI for faster launching

"},{"location":"windows/docs/ami/#caching-windows-base-layers-on-custom-amis","title":"Caching Windows base layers on custom AMIs","text":"

Windows container images are larger than their Linux counterparts. If you are running any containerized .NET Framework-based application, the average image size is around 8.24GB. During pod scheduling, the container image must be fully pulled and extracted in the disk before the pod reaches Running status.

During this process, the container runtime (containerd) pulls and extracts the entire container image in the disk. The pull operation is a parallel process, meaning the container runtime pulls the container image layers in parallel. In contrast, the extraction operation occurs in a sequential process, and it is I/O intensive. Due to that, the container image can take more than 8 minutes to be fully extracted and ready to be used by the container runtime (containerd), and as a result, the pod startup time can take several minutes.

As mentioned in the Patching Windows Server and Container topic, there is an option to build a custom AMI with EKS. During the AMI preparation, you can add an additional EC2 Image builder component to pull all the necessary Windows container images locally and then generate the AMI. This strategy will drastically reduce the time a pod reaches the status Running.

On Amazon EC2 Image Builder, create a component to download the necessary images and attach it to the Image recipe. The following example pulls a specific image from a ECR repository.

name: ContainerdPull\ndescription: This component pulls the necessary containers images for a cache strategy.\nschemaVersion: 1.0\n\nphases:\n  - name: build\n    steps:\n      - name: containerdpull\n        action: ExecutePowerShell\n        inputs:\n          commands:\n            - Set-ExecutionPolicy Unrestricted -Force\n            - (Get-ECRLoginCommand).Password | docker login --username AWS --password-stdin 111000111000.dkr.ecr.us-east-1.amazonaws.com\n            - ctr image pull mcr.microsoft.com/dotnet/framework/aspnet:latest\n            - ctr image pull 111000111000.dkr.ecr.us-east-1.amazonaws.com/myappcontainerimage:latest\n

To make sure the following component works as expected, check if the IAM role used by EC2 Image builder (EC2InstanceProfileForImageBuilder) has the attached policies:

"},{"location":"windows/docs/ami/#blog-post","title":"Blog post","text":"

In the following blog post, you will find a step by step on how to implement caching strategy for custom Amazon EKS Windows AMIs:

Speeding up Windows container launch times with EC2 Image builder and image cache strategy

"},{"location":"windows/docs/gmsa/","title":"Configure gMSA for Windows Pods and containers","text":""},{"location":"windows/docs/gmsa/#what-is-a-gmsa-account","title":"What is a gMSA account","text":"

Windows-based applications such as .NET applications often use Active Directory as an identity provider, providing authorization/authentication using NTLM or Kerberos protocol.

An application server to exchange Kerberos tickets with Active Directory requires to be domain-joined. Windows containers don\u2019t support domain joins and would not make much sense as containers are ephemeral resources, creating a burden on the Active Directory RID pool.

However, administrators can leverage gMSA Active Directory accounts to negotiate a Windows authentication for resources such as Windows containers, NLB, and server farms.

"},{"location":"windows/docs/gmsa/#windows-container-and-gmsa-use-case","title":"Windows container and gMSA use case","text":"

Applications that leverage on Windows authentication, and run as Windows containers, benefit from gMSA because the Windows Node is used to exchange the Kerberos ticket on behalf of the container.There are two options available to setup the Windows worker node to support gMSA integration:

"},{"location":"windows/docs/gmsa/#1-domain-joined-windows-worker-nodes","title":"1 - Domain-joined Windows worker nodes","text":"

In this setup, the Windows worker node is domain-joined in the Active Directory domain, and the AD Computer account of the Windows worker nodes is used to authenticate against Active Directory and retrieve the gMSA identity to be used with the pod.

In the domain-joined approach, you can easily manage and harden your Windows worker nodes using existing Active Directory GPOs; however, it generates additional operational overhead and delays during Windows worker node joining in the Kubernetes cluster, as it requires additional reboots during node startup and Active Directory garage cleaning after the Kubernetes cluster terminates nodes.

In the following blog post, you will find a detailed step-by-step on how to implement the Domain-joined Windows worker node approach:

Windows Authentication on Amazon EKS Windows pods

"},{"location":"windows/docs/gmsa/#2-domainless-windows-worker-nodes","title":"2 - Domainless Windows worker nodes","text":"

In this setup, the Windows worker node isn't joined in the Active Directory domain, and a \"portable\" identity (user/password) is used to authenticate against Active Directory and retrieve the gMSA identity to be used with the pod.

The portable identity is an Active Directory user; the identity (user/password) is stored on AWS Secrets Manager or AWS System Manager Parameter Store, and an AWS-developed plugin called ccg_plugin will be used to retrieve this identity from AWS Secrets Manager or AWS System Manager Parameter Store and pass it to containerd to retrieve the gMSA identity and made it available for the pod.

In this domainless approach, you can benefit from not having any Active Directory interaction during Windows worker node startup when using gMSA and reducing the operational overhead for Active Directory administrators.

In the following blog post, you will find a detailed step-by-step on how to implement the Domainless Windows worker node approach:

Domainless Windows Authentication for Amazon EKS Windows pods

"},{"location":"windows/docs/gmsa/#important-note","title":"Important note","text":"

Despite the pod being able to use a gMSA account, it is necessary to also setup the application or service accordingly to support Windows authentication, for instance, in order to setup Microsoft IIS to support Windows authentication, you should prepared it via dockerfile:

RUN Install-WindowsFeature -Name Web-Windows-Auth -IncludeAllSubFeature\nRUN Import-Module WebAdministration; Set-ItemProperty 'IIS:\\AppPools\\SiteName' -name processModel.identityType -value 2\nRUN Import-Module WebAdministration; Set-WebConfigurationProperty -Filter '/system.webServer/security/authentication/anonymousAuthentication' -Name Enabled -Value False -PSPath 'IIS:\\' -Location 'SiteName'\nRUN Import-Module WebAdministration; Set-WebConfigurationProperty -Filter '/system.webServer/security/authentication/windowsAuthentication' -Name Enabled -Value True -PSPath 'IIS:\\' -Location 'SiteName'\n
"},{"location":"windows/docs/hardening/","title":"Windows worker nodes hardening","text":"

OS Hardening is a combination of OS configuration, patching, and removing unnecessary software packages, which aim to lock down a system and reduce the attack surface. It is a best practice to prepare your own EKS Optimized Windows AMI with the hardening configurations required by your company.

AWS provides a new EKS Optimized Windows AMI every month containing the latest Windows Server Security Patches. However, it is still the user's responsibility to harden their AMI by applying the necessary OS configurations regardless of whether they use self-managed or managed node groups.

Microsoft offers a range of tools like Microsoft Security Compliance Toolkit and Security Baselines that helps you to achieve hardening based on your security policies needs. CIS Benchmarks are also available and should be implemented on top of an Amazon EKS Optimized Windows AMI for production environments.

"},{"location":"windows/docs/hardening/#reducing-attack-surface-with-windows-server-core","title":"Reducing attack surface with Windows Server Core","text":"

Windows Server Core is a minimal installation option that is available as part of the EKS Optimized Windows AMI. Deploying Windows Server Core has a couple of benefits. First, it has a relatively small disk footprint, being 6GB on Server Core against 10GB on Windows Server with Desktop experience. Second, it has a smaller attack surface because of its smaller code base and available APIs.

AWS provides customers with new Amazon EKS Optimized Windows AMIs every month, containing the latest Microsoft security patches, regardless of the Amazon EKS-supported version. As a best practice, Windows worker nodes must be replaced with new ones based on the latest Amazon EKS-optimized AMI. Any node running for more than 45 days without an update in place or node replacement lacks security best practices.

"},{"location":"windows/docs/hardening/#avoiding-rdp-connections","title":"Avoiding RDP connections","text":"

Remote Desktop Protocol (RDP) is a connection protocol developed by Microsoft to provide users with a graphical interface to connect to another Windows computer over a network.

As a best practice, you should treat your Windows worker nodes as if they were ephemeral hosts. That means no management connections, no updates, and no troubleshooting. Any modification and update should be implemented as a new custom AMI and replaced by updating an Auto Scaling group. See Patching Windows Servers and Containers and Amazon EKS optimized Windows AMI management.

Disable RDP connections on Windows nodes during the deployment by passing the value false on the ssh property, as the example below:

nodeGroups:\n- name: windows-ng\n  instanceType: c5.xlarge\n  minSize: 1\n  volumeSize: 50\n  amiFamily: WindowsServer2019CoreContainer\n  ssh:\n    allow: false\n

If access to the Windows node is needed, use AWS System Manager Session Manager to establish a secure PowerShell session through the AWS Console and SSM agent. To see how to implement the solution watch Securely Access Windows Instances Using AWS Systems Manager Session Manager

In order to use System Manager Session Manager an additional IAM policy must be applied to the IAM role used to launch the Windows worker node. Below is an example where the AmazonSSMManagedInstanceCore is specified in the eksctl cluster manifest:

 nodeGroups:\n- name: windows-ng\n  instanceType: c5.xlarge\n  minSize: 1\n  volumeSize: 50\n  amiFamily: WindowsServer2019CoreContainer\n  ssh:\n    allow: false\n  iam:\n    attachPolicyARNs:\n      - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy\n      - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy\n      - arn:aws:iam::aws:policy/ElasticLoadBalancingFullAccess\n      - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly\n      - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore\n
"},{"location":"windows/docs/hardening/#amazon-inspector","title":"Amazon Inspector","text":"

Amazon Inspector is an automated security assessment service that helps improve the security and compliance of applications deployed on AWS. Amazon Inspector automatically assesses applications for exposure, vulnerabilities, and deviations from best practices. After performing an assessment, Amazon Inspector produces a detailed list of security findings prioritized by level of severity. These findings can be reviewed directly or as part of detailed assessment reports which are available via the Amazon Inspector console or API.

Amazon Inspector can be used to run CIS Benchmark assessment on the Windows worker node and it can be installed on a Windows Server Core by performing the following tasks:

  1. Download the following .exe file: https://inspector-agent.amazonaws.com/windows/installer/latest/AWSAgentInstall.exe
  2. Transfer the agent to the Windows worker node.
  3. Run the following command on PowerShell to install the Amazon Inspector Agent: .\\AWSAgentInstall.exe /install

Below is the ouput after the first run. As you can see, it generated findings based on the CVE database. You can use this to harden your Worker nodes or create an AMI based on the hardened configurations.

For more information on Amazon Inspector, including how to install Amazon Inspector agents, set up the CIS Benchmark assessment, and generate reports, watch the Improving the security and compliance of Windows Workloads with Amazon Inspector video.

"},{"location":"windows/docs/hardening/#amazon-guardduty","title":"Amazon GuardDuty","text":"

Amazon GuardDuty is a threat detection service that continuously monitors for malicious activity and unauthorized behavior to protect your AWS accounts, workloads, and data stored in Amazon S3. With the cloud, the collection and aggregation of account and network activities is simplified, but it can be time consuming for security teams to continuously analyze event log data for potential threats.

By using Amazon GuardDuty you have visilitiby on malicious actitivy against Windows worker nodes, like RDP brute force and Port Probe attacks.

Watch the Threat Detection for Windows Workloads using Amazon GuardDuty video to learn how to implement and run CIS Benchmarks on Optimized EKS Windows AMI

"},{"location":"windows/docs/hardening/#security-in-amazon-ec2-for-windows","title":"Security in Amazon EC2 for Windows","text":"

Read up on the Security best practices for Amazon EC2 Windows instances to implement security controls at every layer.

"},{"location":"windows/docs/images/","title":"Container image scanning","text":"

Image Scanning is an automated vulnerability assessment feature that helps improve the security of your application\u2019s container images by scanning them for a broad range of operating system vulnerabilities.

Currently, the Amazon Elastic Container Registry (ECR) is only able to scan Linux container image for vulnerabilities. However; there are third-party tools which can be integrated with an existing CI/CD pipeline for Windows container image scanning.

  • Anchore
  • PaloAlto Prisma Cloud
  • Trend Micro - Deep Security Smart Check

To learn more about how to integrate these solutions with Amazon Elastic Container Repository (ECR), check:

  • Anchore, scanning images on Amazon Elastic Container Registry (ECR)
  • PaloAlto, scanning images on Amazon Elastic Container Registry (ECR)
  • TrendMicro, scanning images on Amazon Elastic Container Registry (ECR)
"},{"location":"windows/docs/licensing/","title":"Windows Server version and License","text":""},{"location":"windows/docs/licensing/#windows-server-version","title":"Windows Server version","text":"

An Amazon EKS Optimized Windows AMI is based on Windows Server 2019 and 2022 Datacenter edition on the Long-Term Servicing Channel (LTSC). The Datacenter version doesn't have a limitation on the number of containers running on a worker node. For more information: https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/faq

"},{"location":"windows/docs/licensing/#long-term-servicing-channel-ltsc","title":"Long-Term Servicing Channel (LTSC)","text":"

Formerly called the \"Long-Term Servicing Branch\", this is the release model you are already familiar with, where a new major version of Windows Server is released every 2-3 years. Users are entitled to 5 years of mainstream support and 5 years of extended support.

"},{"location":"windows/docs/licensing/#licensing","title":"Licensing","text":"

When launching an Amazon EC2 instance with a Windows Server-based AMI, Amazon covers licensing costs and license compliance for you.

"},{"location":"windows/docs/logging/","title":"Logging","text":"

Containerized applications typically direct application logs to STDOUT. The container runtime traps these logs and does something with them - typically writes to a file. Where these files are stored depends on the container runtime and configuration.

One fundamental difference with Windows pods is they do not generate STDOUT. You can run LogMonitor to retrieve the ETW (Event Tracing for Windows), Windows Event Logs and other application specific logs from running Windows containers and pipes formatted log output to STDOUT. These logs can then be streamed using fluent-bit or fluentd to your desired destination such as Amazon CloudWatch.

The Log collection mechanism retrieves STDOUT/STDERR logs from Kubernetes pods. A DaemonSet is a common way to collect logs from containers. It gives you the ability to manage log routing/filtering/enrichment independently of the application. A fluentd DaemonSet can be used to stream these logs and any other application generated logs to a desired log aggregator.

More detailed information about log streaming from Windows workloads to CloudWatch is explained here

"},{"location":"windows/docs/logging/#logging-recomendations","title":"Logging Recomendations","text":"

The general logging best practices are no different when operating Windows workloads in Kubernetes.

  • Always log structured log entries (JSON/SYSLOG) which makes handling log entries easier as there are many pre-written parsers for such structured formats.
  • Centralize logs - dedicated logging containers can be used specifically to gather and forward log messages from all containers to a destination
  • Keep log verbosity down except when debugging. Verbosity places a lot of stress on the logging infrastructure and significant events can be lost in the noise.
  • Always log the application information along with transaction/request id for traceability. Kubernetes objects do-not carry the application name, so for example a pod name windows-twryrqyw may not carry any meaning when debugging logs. This helps with traceability and troubleshooting applications with your aggregated logs.

    How you generate these transaction/correlation id's depends on the programming construct. But a very common pattern is to use a logging Aspect/Interceptor, which can use MDC (Mapped diagnostic context) to inject a unique transaction/correlation id to every incoming request, like so:

import org.slf4j.MDC;\nimport java.util.UUID;\nClass LoggingAspect { //interceptor\n\n    @Before(value = \"execution(* *.*(..))\")\n    func before(...) {\n        transactionId = generateTransactionId();\n        MDC.put(CORRELATION_ID, transactionId);\n    }\n\n    func generateTransactionId() {\n        return UUID.randomUUID().toString();\n    }\n}\n
"},{"location":"windows/docs/monitoring/","title":"Monitoring","text":"

Prometheus, a graduated CNCF project is by far the most popular monitoring system with native integration into Kubernetes. Prometheus collects metrics around containers, pods, nodes, and clusters. Additionally, Prometheus leverages AlertsManager which lets you program alerts to warn you if something in your cluster is going wrong. Prometheus stores the metric data as a time series data identified by metric name and key/value pairs. Prometheus includes away to query using a language called PromQL, which is short for Prometheus Query Language.

The high level architecture of Prometheus metrics collection is shown below:

Prometheus uses a pull mechanism and scrapes metrics from targets using exporters and from the Kubernetes API using the kube state metrics. This means applications and services must expose a HTTP(S) endpoint containing Prometheus formatted metrics. Prometheus will then, as per its configuration, periodically pull metrics from these HTTP(S) endpoints.

An exporter lets you consume third party metrics as Prometheus formatted metrics. A Prometheus exporter is typically deployed on each node. For a complete list of exporters please refer to the Prometheus exporters. While node exporter is suited for exporting host hardware and OS metrics for linux nodes, it wont work for Windows nodes.

In a mixed node EKS cluster with Windows nodes when you use the stable Prometheus helm chart, you will see failed pods on the Windows nodes, as this exporter is not intended for Windows. You will need to treat the Windows worker pool separate and instead install the Windows exporter on the Windows worker node group.

In order to setup Prometheus monitoring for Windows nodes, you need to download and install the WMI exporter on the Windows server itself and then setup the targets inside the scrape configuration of the Prometheus configuration file. The releases page provides all available .msi installers, with respective feature sets and bug fixes. The installer will setup the windows_exporter as a Windows service, as well as create an exception in the Windows firewall. If the installer is run without any parameters, the exporter will run with default settings for enabled collectors, ports, etc.

You can check out the scheduling best practices section of this guide which suggests the use of taints/tolerations or RuntimeClass to selectively deploy node exporter only to linux nodes, while the Windows exporter is installed on Windows nodes as you bootstrap the node or using a configuration management tool of your choice (example chef, Ansible, SSM etc).

Note that, unlike the linux nodes where the node exporter is installed as a daemonset , on Windows nodes the WMI exporter is installed on the host itself. The exporter will export metrics such as the CPU usage, the memory and the disk I/O usage and can also be used to monitor IIS sites and applications, the network interfaces and services.

The windows_exporter will expose all metrics from enabled collectors by default. This is the recommended way to collect metrics to avoid errors. However, for advanced use the windows_exporter can be passed an optional list of collectors to filter metrics. The collect[] parameter, in the Prometheus configuration lets you do that.

The default install steps for Windows include downloading and starting the exporter as a service during the bootstrapping process with arguments, such as the collectors you want to filter.

> Powershell Invoke-WebRequest https://github.com/prometheus-community/windows_exporter/releases/download/v0.13.0/windows_exporter-0.13.0-amd64.msi -OutFile <DOWNLOADPATH> \n\n> msiexec /i <DOWNLOADPATH> ENABLED_COLLECTORS=\"cpu,cs,logical_disk,net,os,system,container,memory\"\n

By default, the metrics can be scraped at the /metrics endpoint on port 9182. At this point, Prometheus can consume the metrics by adding the following scrape_config to the Prometheus configuration

scrape_configs:\n    - job_name: \"prometheus\"\n      static_configs: \n        - targets: ['localhost:9090']\n    ...\n    - job_name: \"wmi_exporter\"\n      scrape_interval: 10s\n      static_configs: \n        - targets: ['<windows-node1-ip>:9182', '<windows-node2-ip>:9182', ...]\n

Prometheus configuration is reloaded using

> ps aux | grep prometheus\n> kill HUP <PID> \n

A better and recommended way to add targets is to use a Custom Resource Definition called ServiceMonitor, which comes as part of the Prometheus operator] that provides the definition for a ServiceMonitor Object and a controller that will activate the ServiceMonitors we define and automatically build the required Prometheus configuration.

The ServiceMonitor, which declaratively specifies how groups of Kubernetes services should be monitored, is used to define an application you wish to scrape metrics from within Kubernetes. Within the ServiceMonitor we specify the Kubernetes labels that the operator can use to identify the Kubernetes Service which in turn identifies the Pods, that we wish to monitor.

In order to leverage the ServiceMonitor, create an Endpoint object pointing to specific Windows targets, a headless service and a ServiceMontor for the Windows nodes.

apiVersion: v1\nkind: Endpoints\nmetadata:\n  labels:\n    k8s-app: wmiexporter\n  name: wmiexporter\n  namespace: kube-system\nsubsets:\n- addresses:\n  - ip: NODE-ONE-IP\n    targetRef:\n      kind: Node\n      name: NODE-ONE-NAME\n  - ip: NODE-TWO-IP\n    targetRef:\n      kind: Node\n      name: NODE-TWO-NAME\n  - ip: NODE-THREE-IP\n    targetRef:\n      kind: Node\n      name: NODE-THREE-NAME\n  ports:\n  - name: http-metrics\n    port: 9182\n    protocol: TCP\n\n---\napiVersion: v1\nkind: Service ##Headless Service\nmetadata:\n  labels:\n    k8s-app: wmiexporter\n  name: wmiexporter\n  namespace: kube-system\nspec:\n  clusterIP: None\n  ports:\n  - name: http-metrics\n    port: 9182\n    protocol: TCP\n    targetPort: 9182\n  sessionAffinity: None\n  type: ClusterIP\n\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor ##Custom ServiceMonitor Object\nmetadata:\n  labels:\n    k8s-app: wmiexporter\n  name: wmiexporter\n  namespace: monitoring\nspec:\n  endpoints:\n  - interval: 30s\n    port: http-metrics\n  jobLabel: k8s-app\n  namespaceSelector:\n    matchNames:\n    - kube-system\n  selector:\n    matchLabels:\n      k8s-app: wmiexporter\n

For more details on the operator and the usage of ServiceMonitor, checkout the official operator documentation. Note that Prometheus does support dynamic target discovery using many service discovery options.

"},{"location":"windows/docs/networking/","title":"Windows Networking","text":""},{"location":"windows/docs/networking/#windows-container-networking-overview","title":"Windows Container Networking Overview","text":"

Windows containers are fundamentally different than Linux containers. Linux containers use Linux constructs like namespaces, the union file system, and cgroups. On Windows, those constructs are abstracted from containerd by the Host Compute Service (HCS). HCS acts as an API layer that sits above the container implementation on Windows. Windows containers also leverage the Host Network Service (HNS) that defines the network topology on a node.

From a networking perspective, HCS and HNS make Windows containers function like virtual machines. For example, each container has a virtual network adapter (vNIC) that is connected to a Hyper-V virtual switch (vSwitch) as shown in the diagram above.

"},{"location":"windows/docs/networking/#ip-address-management","title":"IP Address Management","text":"

A node in Amazon EKS uses it's Elastic Network Interface (ENI) to connect to an AWS VPC network. Presently, only a single ENI per Windows worker node is supported. The IP address management for Windows nodes is performed by VPC Resource Controller which runs in control plane. More details about the workflow for IP address management of Windows nodes can be found here.

The number of pods that a Windows worker node can support is dictated by the size of the node and the number of available IPv4 addresses. You can calculate the IPv4 address available on the node as below: - By default, only secondary IPv4 addresses are assigned to the ENI. In such a case:

Total IPv4 addresses available for Pods = Number of supported IPv4 addresses in the primary interface - 1\n
We subtract one from the total count since one IPv4 addresses will be used as the primary address of the ENI and hence cannot be allocated to the Pods.

  • If the cluster has been configured for high pod density by enabling prefix delegation feature then-
    Total IPv4 addresses available for Pods = (Number of supported IPv4 addresses in the primary interface - 1) * 16\n
    Here, instead of allocating secondary IPv4 addresses, VPC Resource Controller will allocate /28 prefixes and therefore, the overall number of available IPv4 addresses will be boosted 16 times.

Using the formula above, we can calculate max pods for an Windows worker noded based on a m5.large instance as below: - By default, when running in secondary IP mode-

10 secondary IPv4 addresses per ENI - 1 = 9 available IPv4 addresses\n
- When using prefix delegation-
(10 secondary IPv4 addresses per ENI - 1) * 16 = 144 available IPv4 addresses\n

For more information on how many IP addresses an instance type can support, see IP addresses per network interface per instance type.

Another key consideration is the flow of network traffic. With Windows there is a risk of port exhaustion on nodes with more than 100 services. When this condition arises, the nodes will start throwing errors with the following message:

\"Policy creation failed: hcnCreateLoadBalancer failed in Win32: The specified port already exists.\"

To address this issue, we leverage Direct Server Return (DSR). DSR is an implementation of asymmetric network load distribution. In other words, the request and response traffic use different network paths. This feature speeds up communication between pods and reduces the risk of port exhaustion. We therefore recommend enabling DSR on Windows nodes.

DSR is enabled by default in Windows Server SAC EKS Optimized AMIs. For Windows Server 2019 LTSC EKS Optimized AMIs, you will need to enable it during instance provisioning using the script below and by using Windows Server 2019 Full or Core as the amiFamily in the eksctl nodeGroup. See eksctl custom AMI for additional information.

nodeGroups:\n- name: windows-ng\n  instanceType: c5.xlarge\n  minSize: 1\n  volumeSize: 50\n  amiFamily: WindowsServer2019CoreContainer\n  ssh:\n    allow: false\n
In order to utilize DSR in Windows Server 2019 and above, you will need to specify the following kube-proxy flags during instance startup. You can do this by adjusting the userdata script associated with the self-managed node groups Launch Template.

<powershell>\n[string]$EKSBinDir = \"$env:ProgramFiles\\Amazon\\EKS\"\n[string]$EKSBootstrapScriptName = 'Start-EKSBootstrap.ps1'\n[string]$EKSBootstrapScriptFile = \"$EKSBinDir\\$EKSBootstrapScriptName\"\n(Get-Content $EKSBootstrapScriptFile).replace('\"--proxy-mode=kernelspace\",', '\"--proxy-mode=kernelspace\", \"--feature-gates WinDSR=true\", \"--enable-dsr\",') | Set-Content $EKSBootstrapScriptFile \n& $EKSBootstrapScriptFile -EKSClusterName \"eks-windows\" -APIServerEndpoint \"https://<REPLACE-EKS-CLUSTER-CONFIG-API-SERVER>\" -Base64ClusterCA \"<REPLACE-EKSCLUSTER-CONFIG-DETAILS-CA>\" -DNSClusterIP \"172.20.0.10\" -KubeletExtraArgs \"--node-labels=alpha.eksctl.io/cluster-name=eks-windows,alpha.eksctl.io/nodegroup-name=windows-ng-ltsc2019 --register-with-taints=\" 3>&1 4>&1 5>&1 6>&1\n</powershell>\n

DSR enablement can be verified following the instructions in the Microsoft Networking blog and the Windows Containers on AWS Lab.

If preserving your available IPv4 addresses and minimizing wastage is crucial for your subnet, it is generally recommended to avoid using prefix delegation mode as mentioned in Prefix Mode for Windows - When to avoid. If using prefix delegation is still desired, you can take steps to optimize IPv4 address utilization in your subnet. See Configuring Parameters for Prefix Delegation for detailed instructions on how to fine-tune the IPv4 address request and allocation process. Adjusting these configurations can help you strike a balance between conserving IPv4 addresses and pod density benefits of prefix delegation.

When using the default setting of assigning secondary IPv4 addresses, there are currently no supported configurations to manipulate how the VPC Resource Controller requests and allocates IPv4 addresses. More specifically, minimum-ip-target and warm-ip-target are only supported for prefix delegation mode. Also take note that in secondary IP mode, depending on the available IP addresses on the interface, the VPC Resource Controller will typically allocate 3 unused IPv4 addresses on the node on your behalf to maintain warm IPs for faster pod startup times. If you would like to minimize IP wastage of unused warm IP addresses, you could aim to schedule more pods on a given Windows node such that you use as much IP address capacity of the ENI as possible. More explicitly, you could avoid having warm unused IPs if all IP addresses on the ENI are already in use by the node and running pods. Another workaround to help you resolve constraints with IP address availability in your subnet(s) could be to explore increasing your subnet size or separating your Windows nodes into their own dedicated subnets.

Additionally, it's important to note that IPv6 is not supported on Windows nodes at the moment.

"},{"location":"windows/docs/networking/#container-network-interface-cni-options","title":"Container Network Interface (CNI) options","text":"

The AWSVPC CNI is the de facto CNI plugin for Windows and Linux worker nodes. While the AWSVPC CNI satisfies the needs of many customers, still there may be times when you need to consider alternatives like an overlay network to avoid IP exhaustion. In these cases, the Calico CNI can be used in place of the AWSVPC CNI. Project Calico is open source software that was developed by Tigera. That software includes a CNI that works with EKS. Instructions for installing Calico CNI in EKS can be found on the Project Calico EKS installation page.

"},{"location":"windows/docs/networking/#network-polices","title":"Network Polices","text":"

It is considered a best practice to change from the default mode of open communication between pods on your Kubernetes cluster to limiting access based on network polices. The open source Project Calico has strong support for network polices that work with both Linux and Windows nodes. This feature is separate and not dependent on using the Calico CNI. We therefore recommend installing Calico and using it for network policy management.

Instructions for installing Calico in EKS can be found on the Installing Calico on Amazon EKS page.

In addition, the advice provided in the Amazon EKS Best Practices Guide for Security - Network Section applies equally to EKS clusters with Windows worker nodes, however, some features like \"Security Groups for Pods\" are not supported by Windows at this time.

"},{"location":"windows/docs/oom/","title":"Avoiding OOM errors","text":"

Windows does not have an out-of-memory process killer as Linux does. Windows always treats all user-mode memory allocations as virtual, and pagefiles are mandatory. The net effect is that Windows won't reach out of memory conditions the same way Linux does. Processes will page to disk instead of being subject to out of memory (OOM) termination. If memory is over-provisioned and all physical memory is exhausted, then paging can slow down performance.

"},{"location":"windows/docs/oom/#reserving-system-and-kubelet-memory","title":"Reserving system and kubelet memory","text":"

Different from Linux where --kubelet-reserve capture resource reservation for kubernetes system daemons like kubelet, container runtime, etc; and --system-reserve capture resource reservation for OS system daemons like sshd, udev and etc. On Windows these flags do not capture and set memory limits on kubelet or processes running on the node.

However, you can combine these flags to manage NodeAllocatable to reduce Capacity on the node with Pod manifest memory resource limit to control memory allocation per pod. Using this strategy you have a better control of memory allocation as well as a mechanism to minimize out-of-memory (OOM) on Windows nodes.

On Windows nodes, a best practice is to reserve at least 2GB of memory for the OS and process. Use --kubelet-reserve and/or --system-reserve to reduce NodeAllocatable.

Following the Amazon EKS Self-managed Windows nodes documentation, use the CloudFormation template to launch a new Windows node group with customizations to kubelet configuration. The CloudFormation has an element called BootstrapArguments which is the same as KubeletExtraArgs. Use with the following flags and values:

--kube-reserved memory=0.5Gi,ephemeral-storage=1Gi --system-reserved memory=1.5Gi,ephemeral-storage=1Gi --eviction-hard memory.available<200Mi,nodefs.available<10%\"\n

If eksctl is the deployment tool, check the following documentation to customize the kubelet configuration https://eksctl.io/usage/customizing-the-kubelet/

"},{"location":"windows/docs/oom/#windows-container-memory-requirements","title":"Windows container memory requirements","text":"

As per Microsoft documentation, a Windows Server base image for NANO requires at least 30MB, whereas Server Core requires 45MB. These numbers grow as you add Windows components such as the .NET Framework, Web Services as IIS and applications.

It is essential for you to know the minimum amount of memory required by your Windows container image, i.e. the base image plus its application layers, and set it as the container's resources/requests in the pod specification. You should also set a limit to avoid pods to consume all the available node memory in case of an application issue.

In the example below, when the Kubernetes scheduler tries to place a pod on a node, the pod's requests are used to determine which node has sufficient resources available for scheduling.

 spec:\n  - name: iis\n    image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019\n    resources:\n      limits:\n        cpu: 1\n        memory: 800Mi\n      requests:\n        cpu: .1\n        memory: 128Mi\n
"},{"location":"windows/docs/oom/#conclusion","title":"Conclusion","text":"

Using this approach minimizes the risks of memory exhaustion but does not prevent it happen. Using Amazon CloudWatch Metrics, you can set up alerts and remediations in case of memory exhaustion occurs.

"},{"location":"windows/docs/patching/","title":"Patching Windows Servers and Containers","text":"

Patching Windows Server is a standard management task for Windows Administrators. This can be accomplished using different tools like Amazon System Manager - Patch Manager, WSUS, System Center Configuration Manager, and many others. However, Windows nodes in an Amazon EKS cluster should not be treated as an ordinary Windows servers. They should be treated as an immutable server. Simply put, avoid updating an existing node, just launch a new one based on an new updated AMI.

Using EC2 Image Builder you can automate AMIs build, by creating recipes and adding components.

The following example shows components, which can be pre-existing ones built by AWS (Amazon-managed) as well as the components you create (Owned by me). Pay close attention to the Amazon-managed component called update-windows, this updates Windows Server before generating the AMI through the EC2 Image Builder pipeline.

EC2 Image Builder allows you to build AMI's based off Amazon Managed Public AMIs and customize them to meet your business requirements. You can then associate those AMIs with Launch Templates which allows you to link a new AMI to the Auto Scaling Group created by the EKS Nodegroup. After that is complete, you can begin terminating the existing Windows Nodes and new ones will be launched based on the new updated AMI.

"},{"location":"windows/docs/patching/#pushing-and-pulling-windows-images","title":"Pushing and pulling Windows images","text":"

Amazon publishes EKS optimized AMIs that include two cached Windows container images.

mcr.microsoft.com/windows/servercore\nmcr.microsoft.com/windows/nanoserver\n

Cached images are updated following the updates on the main OS. When Microsoft releases a new Windows update that directly affects the Windows container base image, the update will be launched as an ordinary Windows Update on the main OS. Keeping the environment up-to-date offers a more secure environment at the Node and Container level.

The size of a Windows container image influences push/pull operations which can lead to slow container startup times. Caching Windows container images allows the expensive I/O operations (file extraction) to occur on the AMI build creation instead of the container launch. As a result, all the necessary image layers will be extracted on the AMI and will be ready to be used, speeding up the time a Windows container launches and can start accepting traffic. During a push operation, only the layers that compose your image are uploaded to the repository.

The following example shows that on the Amazon ECR the fluentd-windows-sac2004 images have only 390.18MB. This is the amount of upload that happened during the push operation.

The following example shows a fluentd Windows ltsc image pushed to an Amazon ECR repository. The size of the layer stored in ECR is 533.05MB.

The output below from docker image ls , the size of the fluentd v1.14-windows-ltsc2019-1 is 6.96GB on disk, but that doesn't mean it downloaded and extracted that amount of data.

In practice, during the pull operation only the compressed 533.05MB will be downloaded and extracted.

REPOSITORY                                                              TAG                        IMAGE ID       CREATED         SIZE\n111122223333.dkr.ecr.us-east-1.amazonaws.com/fluentd-windows-coreltsc   latest                     721afca2c725   7 weeks ago     6.96GB\nfluent/fluentd                                                          v1.14-windows-ltsc2019-1   721afca2c725   7 weeks ago     6.96GB\namazonaws.com/eks/pause-windows                                         latest                     6392f69ae6e7   10 months ago   255MB\n

The size column shows the overall size of image, 6.96GB. Breaking it down:

  • Windows Server Core 2019 LTSC Base image = 5.74GB
  • Fluentd Uncompressed Base Image = 6.96GB
  • Difference on disk = 1.2GB
  • Fluentd compressed final image ECR = 533.05MB

The base image already exists on the local disk, resulting in the total amount on disk being 1.2GB additional. The next time you see the amount of GBs in the size column, don't worry too much, likely more than 70% is already on disk as a cached container image.

"},{"location":"windows/docs/patching/#reference","title":"Reference","text":"

Speeding up Windows container launch times with EC2 Image builder and image cache strategy

"},{"location":"windows/docs/scheduling/","title":"Running Heterogeneous workloads\u00b6","text":"

Kubernetes has support for heterogeneous clusters where you can have a mixture of Linux and Windows nodes in the same cluster. Within that cluster, you can have a mixture of Pods that run on Linux and Pods that run on Windows. You can even run multiple versions of Windows in the same cluster. However, there are several factors (as mentioned below) that will need to be accounted for when making this decision.

"},{"location":"windows/docs/scheduling/#assigning-pods-to-nodes-best-practices","title":"Assigning PODs to Nodes Best practices","text":"

In order to keep Linux and Windows workloads on their respective OS-specific nodes, you need to use some combination of node selectors and taints/tolerations. The main goal of scheduling workloads in a heterogeneous environment is to avoid breaking compatibility for existing Linux workloads.

"},{"location":"windows/docs/scheduling/#ensuring-os-specific-workloads-land-on-the-appropriate-container-host","title":"Ensuring OS-specific workloads land on the appropriate container host","text":"

Users can ensure Windows containers can be scheduled on the appropriate host using nodeSelectors. All Kubernetes nodes today have the following default labels:

kubernetes.io/os = [windows|linux]\nkubernetes.io/arch = [amd64|arm64|...]\n

If a Pod specification does not include a nodeSelector like \"kubernetes.io/os\": windows, the Pod may be scheduled on any host, Windows or Linux. This can be problematic since a Windows container can only run on Windows and a Linux container can only run on Linux.

In Enterprise environments, it's not uncommon to have a large number of pre-existing deployments for Linux containers, as well as an ecosystem of off-the-shelf configurations, like Helm charts. In these situations, you may be hesitant to make changes to a deployment's nodeSelectors. The alternative is to use Taints.

For example: --register-with-taints='os=windows:NoSchedule'

If you are using EKS, eksctl offers ways to apply taints through clusterConfig:

NodeGroups:\n  - name: windows-ng\n    amiFamily: WindowsServer2022FullContainer\n    ...\n    labels:\n      nodeclass: windows2022\n    taints:\n      os: \"windows:NoSchedule\"\n

Adding a taint to all Windows nodes, the scheduler will not schedule pods on those nodes unless they tolerate the taint. Pod manifest example:

nodeSelector:\n    kubernetes.io/os: windows\ntolerations:\n    - key: \"os\"\n      operator: \"Equal\"\n      value: \"windows\"\n      effect: \"NoSchedule\"\n
"},{"location":"windows/docs/scheduling/#handling-multiple-windows-build-in-the-same-cluster","title":"Handling multiple Windows build in the same cluster","text":"

The Windows container base image used by each pod must match the same kernel build version as the node. If you want to use multiple Windows Server builds in the same cluster, then you should set additional node labels, nodeSelectors or leverage a label called windows-build.

Kubernetes 1.17 automatically adds a new label node.kubernetes.io/windows-build to simplify the management of multiple Windows build in the same cluster. If you're running an older version, then it's recommended to add this label manually to Windows nodes.

This label reflects the Windows major, minor, and build number that need to match for compatibility. Below are values used today for each Windows Server version.

It's important to note that Windows Server is moving to the Long-Term Servicing Channel (LTSC) as the primary release channel. The Windows Server Semi-Annual Channel (SAC) was retired on August 9, 2022. There will be no future SAC releases of Windows Server.

Product Name Build Number(s) Server full 2022 LTSC 10.0.20348 Server core 2019 LTSC 10.0.17763

It is possible to check the OS build version through the following command:

kubectl get nodes -o wide\n

The KERNEL-VERSION output matches the Windows OS build version.

NAME                          STATUS   ROLES    AGE   VERSION                INTERNAL-IP   EXTERNAL-IP     OS-IMAGE                         KERNEL-VERSION                  CONTAINER-RUNTIME\nip-10-10-2-235.ec2.internal   Ready    <none>   23m   v1.24.7-eks-fb459a0    10.10.2.235   3.236.30.157    Windows Server 2022 Datacenter   10.0.20348.1607                 containerd://1.6.6\nip-10-10-31-27.ec2.internal   Ready    <none>   23m   v1.24.7-eks-fb459a0    10.10.31.27   44.204.218.24   Windows Server 2019 Datacenter   10.0.17763.4131                 containerd://1.6.6\nip-10-10-7-54.ec2.internal    Ready    <none>   31m   v1.24.11-eks-a59e1f0   10.10.7.54    3.227.8.172     Amazon Linux 2                   5.10.173-154.642.amzn2.x86_64   containerd://1.6.19\n

The example below applies an additional nodeSelector to the pod manifest in order to match the correct Windows-build version when running different Windows node groups OS versions.

nodeSelector:\n    kubernetes.io/os: windows\n    node.kubernetes.io/windows-build: '10.0.20348'\ntolerations:\n    - key: \"os\"\n    operator: \"Equal\"\n    value: \"windows\"\n    effect: \"NoSchedule\"\n
"},{"location":"windows/docs/scheduling/#simplifying-nodeselector-and-toleration-in-pod-manifests-using-runtimeclass","title":"Simplifying NodeSelector and Toleration in Pod manifests using RuntimeClass","text":"

You can also make use of RuntimeClass to simplify the process of using taints and tolerations. This can be accomplished by creating a RuntimeClass object which is used to encapsulate these taints and tolerations.

Create a RuntimeClass by running the following manifest:

apiVersion: node.k8s.io/v1beta1\nkind: RuntimeClass\nmetadata:\n  name: windows-2022\nhandler: 'docker'\nscheduling:\n  nodeSelector:\n    kubernetes.io/os: 'windows'\n    kubernetes.io/arch: 'amd64'\n    node.kubernetes.io/windows-build: '10.0.20348'\n  tolerations:\n  - effect: NoSchedule\n    key: os\n    operator: Equal\n    value: \"windows\"\n

Once the Runtimeclass is created, assign it using as a Spec on the Pod manifest:

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: iis-2022\n  labels:\n    app: iis-2022\nspec:\n  replicas: 1\n  template:\n    metadata:\n      name: iis-2022\n      labels:\n        app: iis-2022\n    spec:\n      runtimeClassName: windows-2022\n      containers:\n      - name: iis\n
"},{"location":"windows/docs/scheduling/#managed-node-group-support","title":"Managed Node Group Support","text":"

To help customers run their Windows applications in a more streamlined manner, AWS launched the support for Amazon EKS Managed Node Group (MNG) support for Windows containers on December 15, 2022. To help align operations teams, Windows MNGs are enabled using the same workflows and tools as Linux MNGs. Full and core AMI (Amazon Machine Image) family versions of Windows Server 2019 and 2022 are supported.

Following AMI families are supported for Managed Node Groups(MNG)s.

AMI Family WINDOWS_CORE_2019_x86_64 WINDOWS_FULL_2019_x86_64 WINDOWS_CORE_2022_x86_64 WINDOWS_FULL_2022_x86_64"},{"location":"windows/docs/scheduling/#additional-documentations","title":"Additional documentations","text":"

AWS Official Documentation: https://docs.aws.amazon.com/eks/latest/userguide/windows-support.html

To better understand how Pod Networking (CNI) works, check the following link: https://docs.aws.amazon.com/eks/latest/userguide/pod-networking.html

AWS Blog on Deploying Managed Node Group for Windows on EKS: https://aws.amazon.com/blogs/containers/deploying-amazon-eks-windows-managed-node-groups/

"},{"location":"windows/docs/security/","title":"Pod Security Contexts","text":"

Pod Security Policies (PSP) and Pod Security Standards (PSS) are two main ways of enforcing security in Kubernetes. Note that PodSecurityPolicy is deprecated as of Kubernetes v1.21, and will be removed in v1.25 and Pod Security Standard (PSS) is the Kubernetes recommended approach for enforcing security going forward.

A Pod Security Policy (PSP) is a native solution in Kubernetes to implement security policies. PSP is a cluster-level resource that controls security-sensitive aspects of the Pod specification. Using Pod Security Policy you can define a set of conditions that Pods must meet to be accepted by the cluster. The PSP feature has been available from the early days of Kubernetes and is designed to block misconfigured pods from being created on a given cluster.

For more information on Pod Security Policies please reference the Kubernetes documentation. According to the Kubernetes deprecation policy, older versions will stop getting support nine months after the deprecation of the feature.

On the other hand, Pod Security Standards (PSS) which is the recommended security approach and typically implemented using Security Contexts are defined as part of the Pod and container specifications in the Pod manifest. PSS is the official standard that the Kubernetes project team has defined to address the security-related best practices for Pods. It defines policies such as baseline (minimally restrictive, default), privileged (unrestrictive) and restricted (most restrictive).

We recommend starting with the baseline profile. PSS baseline profile provides a solid balance between security and potential friction, requiring a minimal list of exceptions, it serves as a good starting point for workload security. If you are currently using PSPs we recommend switching to PSS. More details on the PSS policies can be found in the Kubernetes documentation. These policies can be enforced with several tools including those from OPA and Kyverno. For example, Kyverno provides the full collection of PSS policies here.

Security context settings allow one to give privileges to select processes, use program profiles to restrict capabilities to individual programs, allow privilege escalation, filter system calls, among other things.

Windows pods in Kubernetes have some limitations and differentiators from standard Linux-based workloads when it comes to security contexts.

Windows uses a Job object per container with a system namespace filter to contain all processes in a container and provide logical isolation from the host. There is no way to run a Windows container without the namespace filtering in place. This means that system privileges cannot be asserted in the context of the host, and thus privileged containers are not available on Windows.

The following windowsOptions are the only documented Windows Security Context options while the rest are general Security Context options

For a list of security context attributes that are supported in Windows vs linux, please refer to the official documentation here.

The Pod specific settings are applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.

For example, runAsUserName setting for Pods and containers which is a Windows option is a rough equivalent of the Linux-specific runAsUser setting and in the following manifest, the pod specific security context is applied to all containers

apiVersion: v1\nkind: Pod\nmetadata:\n  name: run-as-username-pod-demo\nspec:\n  securityContext:\n    windowsOptions:\n      runAsUserName: \"ContainerUser\"\n  containers:\n  - name: run-as-username-demo\n    ...\n  nodeSelector:\n    kubernetes.io/os: windows\n

Whereas in the following, the container level security context overrides the pod level security context.

apiVersion: v1\nkind: Pod\nmetadata:\n  name: run-as-username-container-demo\nspec:\n  securityContext:\n    windowsOptions:\n      runAsUserName: \"ContainerUser\"\n  containers:\n  - name: run-as-username-demo\n    ..\n    securityContext:\n        windowsOptions:\n            runAsUserName: \"ContainerAdministrator\"\n  nodeSelector:\n    kubernetes.io/os: windows\n

Examples of acceptable values for the runAsUserName field: ContainerAdministrator, ContainerUser, NT AUTHORITY\\NETWORK SERVICE, NT AUTHORITY\\LOCAL SERVICE

It is generally a good idea to run your containers with ContainerUser for Windows pods. The users are not shared between the container and host but the ContainerAdministrator does have additional privileges with in the container. Note that, there are username limitations to be aware of.

A good example of when to use ContainerAdministrator is to set PATH. You can use the USER directive to do that, like so:

USER ContainerAdministrator\nRUN setx /M PATH \"%PATH%;C:/your/path\"\nUSER ContainerUser\n

Also note that, secrets are written in clear text on the node's volume (as compared to tmpfs/in-memory on linux). This means you have to do two things

  • Use file ACLs to secure the secrets file location
  • Use volume-level encryption using BitLocker
"},{"location":"windows/docs/storage/","title":"Persistent storage options","text":""},{"location":"windows/docs/storage/#what-is-an-in-tree-vs-out-of-tree-volume-plugin","title":"What is an in-tree vs. out-of-tree volume plugin?","text":"

Before the introduction of the Container Storage Interface (CSI), all volume plugins were in-tree meaning they were built, linked, compiled, and shipped with the core Kubernetes binaries and extend the core Kubernetes API. This meant that adding a new storage system to Kubernetes (a volume plugin) required checking code into the core Kubernetes code repository.

Out-of-tree volume plugins are developed independently of the Kubernetes code base, and are deployed (installed) on Kubernetes clusters as extensions. This gives vendors the ability to update drivers out-of-band, i.e. separately from the Kubernetes release cycle. This is largely possible because Kubernetes has created a storage interface or CSI that provides vendors a standard way of interfacing with k8s.

You can check more about Amazon Elastic Kubernetes Services (EKS) storage classes and CSI Drivers on https://docs.aws.amazon.com/eks/latest/userguide/storage.html

"},{"location":"windows/docs/storage/#in-tree-volume-plugin-for-windows","title":"In-tree Volume Plugin for Windows","text":"

Kubernetes volumes enable applications, with data persistence requirements, to be deployed on Kubernetes. The management of persistent volumes consists of provisioning/de-provisioning/resizing of volumes, attaching/detaching a volume to/from a Kubernetes node, and mounting/dismounting a volume to/from individual containers in a pod. The code for implementing these volume management actions for a specific storage back-end or protocol is shipped in the form of a Kubernetes volume plugin (In-tree Volume Plugins). On Amazon Elastic Kubernetes Services (EKS) the following class of Kubernetes volume plugins are supported on Windows:

In-tree Volume Plugin: awsElasticBlockStore

In order to use In-tree volume plugin on Windows nodes, it is necessary to create an additional StorageClass to use NTFS as the fsType. On EKS, the default StorageClass uses ext4 as the default fsType.

A StorageClass provides a way for administrators to describe the \"classes\" of storage they offer. Different classes might map to quality-of-service levels, backup policies, or arbitrary policies determined by the cluster administrators. Kubernetes is unopinionated about what classes represent. This concept is sometimes called \"profiles\" in other storage systems.

You can check it by running the following command:

kubectl describe storageclass gp2\n

Output:

Name:            gp2\nIsDefaultClass:  Yes\nAnnotations:     kubectl.kubernetes.io/last-applied-configuration={\"apiVersion\":\"storage.k8s.io/v1\",\"kind\":\"StorageClas\n\",\"metadata\":{\"annotations\":{\"storageclass.kubernetes.io/is-default-class\":\"true\"},\"name\":\"gp2\"},\"parameters\":{\"fsType\"\n\"ext4\",\"type\":\"gp2\"},\"provisioner\":\"kubernetes.io/aws-ebs\",\"volumeBindingMode\":\"WaitForFirstConsumer\"}\n,storageclass.kubernetes.io/is-default-class=true\nProvisioner:           kubernetes.io/aws-ebs\nParameters:            fsType=ext4,type=gp2\nAllowVolumeExpansion:  <unset>\nMountOptions:          <none>\nReclaimPolicy:         Delete\nVolumeBindingMode:     WaitForFirstConsumer\nEvents:                <none>\n

To create the new StorageClass to support NTFS, use the following manifest:

kind: StorageClass\napiVersion: storage.k8s.io/v1\nmetadata:\n  name: gp2-windows\nprovisioner: kubernetes.io/aws-ebs\nparameters:\n  type: gp2\n  fsType: ntfs\nvolumeBindingMode: WaitForFirstConsumer\n

Create the StorageClass by running the following command:

kubectl apply -f NTFSStorageClass.yaml\n

The next step is to create a Persistent Volume Claim (PVC).

A PersistentVolume (PV) is a piece of storage in the cluster that has been provisioned by an administrator or dynamically provisioned using PVC. It is a resource in the cluster just like a node is a cluster resource. This API object captures the details of the implementation of the storage, be that NFS, iSCSI, or a cloud-provider-specific storage system.

A PersistentVolumeClaim (PVC) is a request for storage by a user. Claims can request specific size and access modes (e.g., they can be mounted ReadWriteOnce, ReadOnlyMany or ReadWriteMany).

Users need PersistentVolumes with different attributes, such as performance, for different use cases. Cluster administrators need to be able to offer a variety of PersistentVolumes that differ in more ways than just size and access modes, without exposing users to the details of how those volumes are implemented. For these needs, there is the StorageClass resource.

In the example below, the PVC has been created within the namespace windows.

apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: ebs-windows-pv-claim\n  namespace: windows\nspec: \n  accessModes:\n    - ReadWriteOnce\n  storageClassName: gp2-windows\n  resources: \n    requests:\n      storage: 1Gi\n

Create the PVC by running the following command:

kubectl apply -f persistent-volume-claim.yaml\n

The following manifest creates a Windows Pod, setup the VolumeMount as C:\\Data and uses the PVC as the attached storage on C:\\Data.

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: windows-server-ltsc2019\n  namespace: windows\nspec:\n  selector:\n    matchLabels:\n      app: windows-server-ltsc2019\n      tier: backend\n      track: stable\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: windows-server-ltsc2019\n        tier: backend\n        track: stable\n    spec:\n      containers:\n      - name: windows-server-ltsc2019\n        image: mcr.microsoft.com/windows/servercore:ltsc2019\n        ports:\n        - name: http\n          containerPort: 80\n        imagePullPolicy: IfNotPresent\n        volumeMounts:\n        - mountPath: \"C:\\\\data\"\n          name: test-volume\n      volumes:\n        - name: test-volume\n          persistentVolumeClaim:\n            claimName: ebs-windows-pv-claim\n      nodeSelector:\n        kubernetes.io/os: windows\n        node.kubernetes.io/windows-build: '10.0.17763'\n

Test the results by accessing the Windows pod via PowerShell:

kubectl exec -it podname powershell -n windows\n

Inside the Windows Pod, run: ls

Output:

PS C:\\> ls\n\n\n    Directory: C:\\\n\n\nMode                 LastWriteTime         Length Name\n----                 -------------         ------ ----\nd-----          3/8/2021   1:54 PM                data\nd-----          3/8/2021   3:37 PM                inetpub\nd-r---          1/9/2021   7:26 AM                Program Files\nd-----          1/9/2021   7:18 AM                Program Files (x86)\nd-r---          1/9/2021   7:28 AM                Users\nd-----          3/8/2021   3:36 PM                var\nd-----          3/8/2021   3:36 PM                Windows\n-a----         12/7/2019   4:20 AM           5510 License.txt\n

The data directory is provided by the EBS volume.

"},{"location":"windows/docs/storage/#out-of-tree-for-windows","title":"Out-of-tree for Windows","text":"

Code associated with CSI plugins ship as out-of-tree scripts and binaries that are typically distributed as container images and deployed using standard Kubernetes constructs like DaemonSets and StatefulSets. CSI plugins handle a wide range of volume management actions in Kubernetes. CSI plugins typically consist of node plugins (that run on each node as a DaemonSet) and controller plugins.

CSI node plugins (especially those associated with persistent volumes exposed as either block devices or over a shared file-system) need to perform various privileged operations like scanning of disk devices, mounting of file systems, etc. These operations differ for each host operating system. For Linux worker nodes, containerized CSI node plugins are typically deployed as privileged containers. For Windows worker nodes, privileged operations for containerized CSI node plugins is supported using csi-proxy, a community-managed, stand-alone binary that needs to be pre-installed on each Windows node.

The Amazon EKS Optimized Windows AMI includes CSI-proxy starting from April 2022. Customers can use the SMB CSI Driver on Windows nodes to access Amazon FSx for Windows File Server, Amazon FSx for NetApp ONTAP SMB Shares, and/or AWS Storage Gateway \u2013 File Gateway.

The following blog has implementation details on how to setup SMB CSI Driver to use Amazon FSx for Windows File Server as a persistent storage for Windows Pods.

"},{"location":"windows/docs/storage/#amazon-fsx-for-windows-file-server","title":"Amazon FSx for Windows File Server","text":"

An option is to use Amazon FSx for Windows File Server through an SMB feature called SMB Global Mapping which makes it possible to mount a SMB share on the host, then pass directories on that share into a container. The container doesn't need to be configured with a specific server, share, username or password - that's all handled on the host instead. The container will work the same as if it had local storage.

The SMB Global Mapping is transparent to the orchestrator, and it is mounted through HostPath which can imply in secure concerns.

In the example below, the path G:\\Directory\\app-state is an SMB share on the Windows Node.

apiVersion: v1\nkind: Pod\nmetadata:\n  name: test-fsx\nspec:\n  containers:\n  - name: test-fsx\n    image: mcr.microsoft.com/windows/servercore:ltsc2019\n    command:\n      - powershell.exe\n      - -command\n      - \"Add-WindowsFeature Web-Server; Invoke-WebRequest -UseBasicParsing -Uri 'https://dotnetbinaries.blob.core.windows.net/servicemonitor/2.0.1.6/ServiceMonitor.exe' -OutFile 'C:\\\\ServiceMonitor.exe'; echo '<html><body><br/><br/><marquee><H1>Hello EKS!!!<H1><marquee></body><html>' > C:\\\\inetpub\\\\wwwroot\\\\default.html; C:\\\\ServiceMonitor.exe 'w3svc'; \"\n    volumeMounts:\n      - mountPath: C:\\dotnetapp\\app-state\n        name: test-mount\n  volumes:\n    - name: test-mount\n      hostPath: \n        path: G:\\Directory\\app-state\n        type: Directory\n  nodeSelector:\n      beta.kubernetes.io/os: windows\n      beta.kubernetes.io/arch: amd64\n

The following blog has implementation details on how to setup Amazon FSx for Windows File Server as a persistent storage for Windows Pods.

"},{"location":"ko/networking/ipvs/","title":"IPVS \ubaa8\ub4dc\uc5d0\uc11c kube-proxy \uc2e4\ud589","text":"

IPVS (IP \uac00\uc0c1 \uc11c\ubc84) \ubaa8\ub4dc\uc758 EKS\ub294 \ub808\uac70\uc2dc iptables \ubaa8\ub4dc\uc5d0\uc11c \uc2e4\ud589\ub418\ub294 kube-proxy\uc640 \ud568\uaed8 1,000\uac1c \uc774\uc0c1\uc758 \uc11c\ube44\uc2a4\uac00 \ud3ec\ud568\ub41c \ub300\uaddc\ubaa8 \ud074\ub7ec\uc2a4\ud130\ub97c \uc2e4\ud589\ud560 \ub54c \ud754\ud788 \ubc1c\uc0dd\ud558\ub294 \ub124\ud2b8\uc6cc\ud06c \uc9c0\uc5f0 \ubb38\uc81c\ub97c \ud574\uacb0\ud569\ub2c8\ub2e4.\uc774\ub7ec\ud55c \uc131\ub2a5 \ubb38\uc81c\ub294 \uac01 \ud328\ud0b7\uc5d0 \ub300\ud55c iptables \ud328\ud0b7 \ud544\ud130\ub9c1 \uaddc\uce59\uc744 \uc21c\ucc28\uc801\uc73c\ub85c \ucc98\ub9ac\ud55c \uacb0\uacfc\uc785\ub2c8\ub2e4.\uc774 \uc9c0\uc5f0 \ubb38\uc81c\ub294 iptables\uc758 \ud6c4\uc18d \ubc84\uc804\uc778 nftables\uc5d0\uc11c \ud574\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4.\ud558\uc9c0\ub9cc \uc774 \uae00\uc744 \uc4f0\ub294 \uc2dc\uc810 \ud604\uc7ac, nftable\uc744 \ud65c\uc6a9\ud558\uae30 \uc704\ud55c [kube-proxy\ub294 \uc544\uc9c1 \uac1c\ubc1c \uc911] (https://kubernetes.io/docs/reference/networking/virtual-ips/#proxy-mode-nftables) \uc774\ub2e4.\uc774 \ubb38\uc81c\ub97c \ud574\uacb0\ud558\ub824\uba74 IPVS \ubaa8\ub4dc\uc5d0\uc11c kube-proxy\uac00 \uc2e4\ud589\ub418\ub3c4\ub85d \ud074\ub7ec\uc2a4\ud130\ub97c \uad6c\uc131\ud560 \uc218 \uc788\ub2e4.

"},{"location":"ko/networking/ipvs/#_1","title":"\uac1c\uc694","text":"

\ucfe0\ubc84\ub124\ud2f0\uc2a4 \ubc84\uc804 1.11 \ubd80\ud130 GA\uac00 \ub41c IPVS\ub294 \uc120\ud615 \uac80\uc0c9\uc774 \uc544\ub2cc \ud574\uc2dc \ud14c\uc774\ube14\uc744 \uc0ac\uc6a9\ud558\uc5ec \ud328\ud0b7\uc744 \ucc98\ub9ac\ud558\ubbc0\ub85c \uc218\ucc9c \uac1c\uc758 \ub178\ub4dc\uc640 \uc11c\ube44\uc2a4\uac00 \uc788\ub294 \ud074\ub7ec\uc2a4\ud130\uc5d0 \ud6a8\uc728\uc131\uc744 \uc81c\uacf5\ud569\ub2c8\ub2e4.IPVS\ub294 \ub85c\ub4dc \ubc38\ub7f0\uc2f1\uc744 \uc704\ud574 \uc124\uacc4\ub418\uc5c8\uc73c\ubbc0\ub85c \ucfe0\ubc84\ub124\ud2f0\uc2a4 \ub124\ud2b8\uc6cc\ud0b9 \uc131\ub2a5 \ubb38\uc81c\uc5d0 \uc801\ud569\ud55c \uc194\ub8e8\uc158\uc785\ub2c8\ub2e4.

IPVS\ub294 \ud2b8\ub798\ud53d\uc744 \ubc31\uc5d4\ub4dc \ud3ec\ub4dc\uc5d0 \ubd84\uc0b0\ud558\uae30 \uc704\ud55c \uba87 \uac00\uc9c0 \uc635\uc158\uc744 \uc81c\uacf5\ud569\ub2c8\ub2e4.\uac01 \uc635\uc158\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 \uacf5\uc2dd \ucfe0\ubc84\ub124\ud2f0\uc2a4 \ubb38\uc11c \uc5d0\uc11c \ud655\uc778\ud560 \uc218 \uc788\uc9c0\ub9cc, \uac04\ub2e8\ud55c \ubaa9\ub85d\uc740 \uc544\ub798\uc5d0 \ub098\uc640 \uc788\ub2e4.\ub77c\uc6b4\ub4dc \ub85c\ube48\uacfc \ucd5c\uc18c \uc5f0\uacb0\uc740 \ucfe0\ubc84\ub124\ud2f0\uc2a4\uc758 IPVS \ub85c\ub4dc \ubc38\ub7f0\uc2f1 \uc635\uc158\uc73c\ub85c \uac00\uc7a5 \ub9ce\uc774 \uc0ac\uc6a9\ub418\ub294 \uc635\uc158 \uc911 \ud558\ub098\uc785\ub2c8\ub2e4.

- rr (\ub77c\uc6b4\ub4dc \ub85c\ube48)\n- wrr (\uc6e8\uc774\ud2f0\ub4dc \ub77c\uc6b4\ub4dc \ub85c\ube48)\n- lc (\ucd5c\uc18c \uc5f0\uacb0)\n- wlc (\uac00\uc911\uce58\uac00 \uac00\uc7a5 \uc801\uc740 \uc5f0\uacb0)\n- lblc (\uc9c0\uc5ed\uc131 \uae30\ubc18 \ucd5c\uc18c \uc5f0\uacb0)\n- lblcr (\ubcf5\uc81c\ub97c \ud1b5\ud55c \uc9c0\uc5ed\uc131 \uae30\ubc18 \ucd5c\uc18c \uc5f0\uacb0)\n- sh (\uc18c\uc2a4 \ud574\uc2f1)\n- dh (\ub370\uc2a4\ud2f0\ub124\uc774\uc158 \ud574\uc2f1)\n- sed (\ucd5c\ub2e8 \uc608\uc0c1 \uc9c0\uc5f0)\n- nq (\uc904 \uc11c\uc9c0 \ub9c8\uc138\uc694)\n

"},{"location":"ko/networking/ipvs/#_2","title":"\uad6c\ud604","text":"

EKS \ud074\ub7ec\uc2a4\ud130\uc5d0\uc11c IPVS\ub97c \ud65c\uc131\ud654\ud558\ub824\uba74 \uba87 \ub2e8\uacc4\ub9cc \uac70\uce58\uba74 \ub429\ub2c8\ub2e4.\uac00\uc7a5 \uba3c\uc800 \ud574\uc57c \ud560 \uc77c\uc740 EKS \uc791\uc5c5\uc790 \ub178\ub4dc \uc774\ubbf8\uc9c0\uc5d0 Linux \uac00\uc0c1 \uc11c\ubc84 \uad00\ub9ac ipvsadm \ud328\ud0a4\uc9c0\uac00 \uc124\uce58\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud558\ub294 \uac83\uc785\ub2c8\ub2e4.Amazon Linux 2023\uacfc \uac19\uc740 Fedora \uae30\ubc18 \uc774\ubbf8\uc9c0\uc5d0 \uc774 \ud328\ud0a4\uc9c0\ub97c \uc124\uce58\ud558\ub824\uba74 \uc791\uc5c5\uc790 \ub178\ub4dc \uc778\uc2a4\ud134\uc2a4\uc5d0\uc11c \ub2e4\uc74c \uba85\ub839\uc744 \uc2e4\ud589\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.

sudo dnf install -y ipvsadm\n
Ubuntu\uc640 \uac19\uc740 \ub370\ube44\uc548 \uae30\ubc18 \uc774\ubbf8\uc9c0\uc5d0\uc11c\ub294 \uc124\uce58 \uba85\ub839\uc774 \ub2e4\uc74c\uacfc \uac19\uc2b5\ub2c8\ub2e4.
sudo apt-get install ipvsadm\n

\ub2e4\uc74c\uc73c\ub85c \uc704\uc5d0 \ub098\uc5f4\ub41c IPVS \uad6c\uc131 \uc635\uc158\uc5d0 \ub300\ud55c \ucee4\ub110 \ubaa8\ub4c8\uc744 \ub85c\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4.\uc7ac\ubd80\ud305\ud574\ub3c4 \uacc4\uc18d \uc791\ub3d9\ud558\ub3c4\ub85d \uc774\ub7ec\ud55c \ubaa8\ub4c8\uc744 /etc/modules-load.d/ \ub514\ub809\ud1a0\ub9ac \ub0b4\uc758 \ud30c\uc77c\uc5d0 \uae30\ub85d\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4.

sudo sh -c 'cat << EOF > /etc/modules-load.d/ipvs.conf\nip_vs\nip_vs_rr\nip_vs_wrr\nip_vs_lc\nip_vs_wlc\nip_vs_lblc\nip_vs_lblcr\nip_vs_sh\nip_vs_dh\nip_vs_sed\nip_vs_nq\nnf_conntrack\nEOF'\n
\ub2e4\uc74c \uba85\ub839\uc744 \uc2e4\ud589\ud558\uc5ec \uc774\ubbf8 \uc2e4\ud589 \uc911\uc778 \uc2dc\uc2a4\ud15c\uc5d0\uc11c \uc774\ub7ec\ud55c \ubaa8\ub4c8\uc744 \ub85c\ub4dc\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.
sudo modprobe ip_vs \nsudo modprobe ip_vs_rr\nsudo modprobe ip_vs_wrr\nsudo modprobe ip_vs_lc\nsudo modprobe ip_vs_wlc\nsudo modprobe ip_vs_lblc\nsudo modprobe ip_vs_lblcr\nsudo modprobe ip_vs_sh\nsudo modprobe ip_vs_dh\nsudo modprobe ip_vs_sed\nsudo modprobe ip_vs_nq\nsudo modprobe nf_conntrack\n

Note

\uc774\ub7ec\ud55c \uc791\uc5c5\uc790 \ub178\ub4dc \ub2e8\uacc4\ub294 \uc0ac\uc6a9\uc790 \ub370\uc774\ud130 \uc2a4\ud06c\ub9bd\ud2b8 \ub97c \ud1b5\ud574 \uc791\uc5c5\uc790 \ub178\ub4dc\uc758 \ubd80\ud2b8\uc2a4\ud2b8\ub7a9 \ud504\ub85c\uc138\uc2a4\uc758 \uc77c\ubd80\ub85c \uc2e4\ud589\ud558\uac70\ub098 \uc0ac\uc6a9\uc790 \uc9c0\uc815 \uc791\uc5c5\uc790 \ub178\ub4dc AMI\ub97c \ube4c\ub4dc\ud558\uae30 \uc704\ud574 \uc2e4\ud589\ub418\ub294 \ube4c\ub4dc \uc2a4\ud06c\ub9bd\ud2b8\uc5d0\uc11c \uc2e4\ud589\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4.

\ub2e4\uc74c\uc73c\ub85c IPVS \ubaa8\ub4dc\uc5d0\uc11c \uc2e4\ud589\ub418\ub3c4\ub85d \ud074\ub7ec\uc2a4\ud130\uc758 kube-proxy DaemonSet\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4. \uc774\ub294 kube-proxy mode\ub97c ipvs\ub85c \uc124\uc815\ud558\uace0 ipvs scheduler\ub97c \uc704\uc5d0 \ub098\uc5f4\ub41c \ub85c\ub4dc \ubc38\ub7f0\uc2f1 \uc635\uc158 \uc911 \ud558\ub098\ub85c \uc124\uc815\ud558\uba74 \ub429\ub2c8\ub2e4(\uc608: \ub77c\uc6b4\ub4dc \ub85c\ube48\uc758 \uacbd\uc6b0 rr).

Warning

\uc774\ub294 \uc6b4\uc601 \uc911\ub2e8\uc744 \uc57c\uae30\ud558\ub294 \ubcc0\uacbd\uc774\ubbc0\ub85c \uadfc\ubb34 \uc2dc\uac04 \uc678 \uc2dc\uac04\uc5d0 \uc218\ud589\ud574\uc57c \ud569\ub2c8\ub2e4.\uc601\ud5a5\uc744 \ucd5c\uc18c\ud654\ud558\ub824\uba74 \ucd08\uae30 EKS \ud074\ub7ec\uc2a4\ud130 \uc0dd\uc131 \uc911\uc5d0 \uc774\ub7ec\ud55c \ubcc0\uacbd\uc744 \uc218\ud589\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4.

kube-proxy EKS \uc560\ub4dc\uc628\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uc5ec AWS CLI \uba85\ub839\uc744 \uc2e4\ud589\ud558\uc5ec IPVS\ub97c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.

aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name kube-proxy \\\n  --configuration-values '{\"ipvs\": {\"scheduler\": \"rr\"}, \"mode\": \"ipvs\"}' \\\n  --resolve-conflicts OVERWRITE\n
\ub610\ub294 \ud074\ub7ec\uc2a4\ud130\uc5d0\uc11c kube-proxy-config \ucee8\ud53c\uadf8\ub9f5\uc744 \uc218\uc815\ud558\uc5ec \uc774 \uc791\uc5c5\uc744 \uc218\ud589\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.
kubectl -n kube-system edit cm kube-proxy-config\n
ipvs\uc5d0\uc11c scheduler \uc124\uc815\uc744 \ucc3e\uc544 \uac12\uc744 \uc704\uc5d0 \ub098\uc5f4\ub41c IPVS \ub85c\ub4dc \ubc38\ub7f0\uc2f1 \uc635\uc158 \uc911 \ud558\ub098\ub85c \uc124\uc815\ud569\ub2c8\ub2e4(\uc608: \ub77c\uc6b4\ub4dc \ub85c\ube48\uc758 \uacbd\uc6b0 rr) \uae30\ubcf8\uac12\uc774 iptables\uc778 mode \uc124\uc815\uc744 \ucc3e\uc544 \uac12\uc744 ipvs\ub85c \ubcc0\uacbd\ud569\ub2c8\ub2e4. \ub450 \uc635\uc158 \uc911 \ud558\ub098\uc758 \uacb0\uacfc\ub294 \uc544\ub798 \uad6c\uc131\uacfc \uc720\uc0ac\ud574\uc57c \ud569\ub2c8\ub2e4.
  iptables:\n    masqueradeAll: false\n    masqueradeBit: 14\n    minSyncPeriod: 0s\n    syncPeriod: 30s\n  ipvs:\n    excludeCIDRs: null\n    minSyncPeriod: 0s\n    scheduler: \"rr\"\n    syncPeriod: 30s\n  kind: KubeProxyConfiguration\n  metricsBindAddress: 0.0.0.0:10249\n  mode: \"ipvs\"\n  nodePortAddresses: null\n  oomScoreAdj: -998\n  portRange: \"\"\n  udpIdleTimeout: 250ms\n

\uc774\ub7ec\ud55c \ubcc0\uacbd\uc744 \uc218\ud589\ud558\uae30 \uc804\uc5d0 \uc791\uc5c5\uc790 \ub178\ub4dc\uac00 \ud074\ub7ec\uc2a4\ud130\uc5d0 \uc5f0\uacb0\ub41c \uacbd\uc6b0 kube-proxy \ub370\ubaac\uc14b\uc744 \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4.

kubectl -n kube-system rollout restart ds kube-proxy\n

"},{"location":"ko/networking/ipvs/#_3","title":"\uc720\ud6a8\uc131 \uac80\uc0ac","text":"

\uc791\uc5c5\uc790 \ub178\ub4dc \uc911 \ud558\ub098\uc5d0\uc11c \ub2e4\uc74c \uba85\ub839\uc744 \uc2e4\ud589\ud558\uc5ec \ud074\ub7ec\uc2a4\ud130 \ubc0f \uc791\uc5c5\uc790 \ub178\ub4dc\uac00 IPVS \ubaa8\ub4dc\uc5d0\uc11c \uc2e4\ud589\ub418\uace0 \uc788\ub294\uc9c0 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.

sudo ipvsadm -L\n

\ucd5c\uc18c\ud55c \ucfe0\ubc84\ub124\ud2f0\uc2a4 API \uc11c\ubc84 \uc11c\ube44\uc2a4\uc5d0 \ub300\ud55c \ud56d\ubaa9\uc774 10.100.0.1\uc774\uace0 CoreDNS \uc11c\ube44\uc2a4\uc5d0 \ub300\ud55c \ud56d\ubaa9\uc774 10.100.0.10\uc778 \uc544\ub798\uc640 \ube44\uc2b7\ud55c \uacb0\uacfc\ub97c \ubcfc \uc218 \uc788\uc744 \uac83\uc774\ub2e4.

IP Virtual Server version 1.2.1 (size=4096)\nProt LocalAddress:Port Scheduler Flags\n  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn\nTCP  ip-10-100-0-1.us-east-1. rr\n  -> ip-192-168-113-81.us-eas Masq        1      0          0\n  -> ip-192-168-162-166.us-ea Masq        1      1          0\nTCP  ip-10-100-0-10.us-east-1 rr\n  -> ip-192-168-104-215.us-ea Masq        1      0          0\n  -> ip-192-168-123-227.us-ea Masq        1      0          0\nUDP  ip-10-100-0-10.us-east-1 rr\n  -> ip-192-168-104-215.us-ea Masq        1      0          0\n  -> ip-192-168-123-227.us-ea Masq        1      0          0\n

Note

\uc774 \uc608\uc81c \ucd9c\ub825\uc740 \uc11c\ube44\uc2a4 IP \uc8fc\uc18c \ubc94\uc704\uac00 10.100.0.0/16\uc778 EKS \ud074\ub7ec\uc2a4\ud130\uc5d0\uc11c \uac00\uc838\uc628 \uac83\uc785\ub2c8\ub2e4.

"}]} \ No newline at end of file diff --git a/security/docs/compliance/index.html b/security/docs/compliance/index.html new file mode 100644 index 000000000..0bfc134aa --- /dev/null +++ b/security/docs/compliance/index.html @@ -0,0 +1,2360 @@ + + + + + + + + + + + + + + + + + + + + + + + Regulatory Compliance - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + +

Compliance

+

Compliance is a shared responsibility between AWS and the consumers of its services. Generally speaking, AWS is responsible for “security of the cloud” whereas its users are responsible for “security in the cloud.” The line that delineates what AWS and its users are responsible for will vary depending on the service. For example, with Fargate, AWS is responsible for managing the physical security of its data centers, the hardware, the virtual infrastructure (Amazon EC2), and the container runtime (Docker). Users of Fargate are responsible for securing the container image and their application. Knowing who is responsible for what is an important consideration when running workloads that must adhere to compliance standards.

+

The following table shows the compliance programs with which the different container services conform.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Compliance ProgramAmazon ECS OrchestratorAmazon EKS OrchestratorECS FargateAmazon ECR
PCI DSS Level 11111
HIPAA Eligible1111
SOC I1111
SOC II1111
SOC III1111
ISO 27001:20131111
ISO 9001:20151111
ISO 27017:20151111
ISO 27018:20191111
IRAP1111
FedRAMP Moderate (East/West)1101
FedRAMP High (GovCloud)1101
DOD CC SRG1DISA Review (IL5)01
HIPAA BAA1111
MTCS1101
C51101
K-ISMS1101
ENS High1101
OSPAR1101
HITRUST CSF1111
+

Compliance status changes over time. For the latest status, always refer to https://aws.amazon.com/compliance/services-in-scope/.

+

For further information about cloud accreditation models and best practices, see the AWS whitepaper, Accreditation Models for Secure Cloud Adoption

+

Shifting Left

+

The concept of shifting left involves catching policy violations and errors earlier in the software development lifecycle. From a security perspective, this can be very beneficial. A developer, for example, can fix issues with their configuration before their application is deployed to the cluster. Catching mistakes like this earlier will help prevent configurations that violate your policies from being deployed.

+

Policy as Code

+

Policy can be thought of as a set of rules for governing behaviors, i.e. behaviors that are allowed or those that are prohibited. For example, you may have a policy that says that all Dockerfiles should include a USER directive that causes the container to run as a non-root user. As a document, a policy like this can be hard to discover and enforce. It may also become outdated as your requirements change. With Policy as Code (PaC) solutions, you can automate security, compliance, and privacy controls that detect, prevent, reduce, and counteract known and persistent threats. Furthermore, they give you mechanism to codify your policies and manage them as you do other code artifacts. The benefit of this approach is that you can reuse your DevOps and GitOps strategies to manage and consistently apply policies across fleets of Kubernetes clusters. Please refer to Pod Security for information about PaC options and the future of PSPs.

+

Use policy-as-code tools in pipelines to detect violations before deployment

+
    +
  • OPA is an open source policy engine that's part of the CNCF. It's used for making policy decisions and can be run a variety of different ways, e.g. as a language library or a service. OPA policies are written in a Domain Specific Language (DSL) called Rego. While it is often run as part of a Kubernetes Dynamic Admission Controller as the Gatekeeper project, OPA can also be incorporated into your CI/CD pipeline. This allows developers to get feedback about their configuration earlier in the release cycle which can subsequently help them resolve issues before they get to production. A collection of common OPA policies can be found in the GitHub repository for this project.
  • +
  • Conftest is built on top of OPA and it provides a developer focused experience for testing Kubernetes configuration.
  • +
  • Kyverno is a policy engine designed for Kubernetes. With Kyverno, policies are managed as Kubernetes resources and no new language is required to write policies. This allows using familiar tools such as kubectl, git, and kustomize to manage policies. Kyverno policies can validate, mutate, and generate Kubernetes resources plus ensure OCI image supply chain security. The Kyverno CLI can be used to test policies and validate resources as part of a CI/CD pipeline. All the Kyverno community policies can be found on the Kyverno website, and for examples using the Kyverno CLI to write tests in pipelines, see the policies repository.
  • +
+

Tools and resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/data/index.html b/security/docs/data/index.html new file mode 100644 index 000000000..6039790cc --- /dev/null +++ b/security/docs/data/index.html @@ -0,0 +1,2438 @@ + + + + + + + + + + + + + + + + + + + + + + + Data Encryption and Secrets Management - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Data encryption and secrets management

+

Encryption at rest

+

There are three different AWS-native storage options you can use with Kubernetes: EBS, EFS, and FSx for Lustre. All three offer encryption at rest using a service managed key or a customer master key (CMK). For EBS you can use the in-tree storage driver or the EBS CSI driver. Both include parameters for encrypting volumes and supplying a CMK. For EFS, you can use the EFS CSI driver, however, unlike EBS, the EFS CSI driver does not support dynamic provisioning. If you want to use EFS with EKS, you will need to provision and configure at-rest encryption for the file system prior to creating a PV. For further information about EFS file encryption, please refer to Encrypting Data at Rest. Besides offering at-rest encryption, EFS and FSx for Lustre include an option for encrypting data in transit. FSx for Lustre does this by default. For EFS, you can add transport encryption by adding the tls parameter to mountOptions in your PV as in this example:

+
apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: efs-pv
+spec:
+  capacity:
+    storage: 5Gi
+  volumeMode: Filesystem
+  accessModes:
+    - ReadWriteOnce
+  persistentVolumeReclaimPolicy: Retain
+  storageClassName: efs-sc
+  mountOptions:
+    - tls
+  csi:
+    driver: efs.csi.aws.com
+    volumeHandle: <file_system_id>
+
+

The FSx CSI driver supports dynamic provisioning of Lustre file systems. It encrypts data with a service managed key by default, although there is an option to provide your own CMK as in this example:

+
kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+  name: fsx-sc
+provisioner: fsx.csi.aws.com
+parameters:
+  subnetId: subnet-056da83524edbe641
+  securityGroupIds: sg-086f61ea73388fb6b
+  deploymentType: PERSISTENT_1
+  kmsKeyId: <kms_arn>
+
+
+

Attention

+

As of May 28, 2020 all data written to the ephemeral volume in EKS Fargate pods is encrypted by default using an industry-standard AES-256 cryptographic algorithm. No modifications to your application are necessary as encryption and decryption are handled seamlessly by the service.

+
+

Encrypt data at rest

+

Encrypting data at rest is considered a best practice. If you're unsure whether encryption is necessary, encrypt your data.

+

Rotate your CMKs periodically

+

Configure KMS to automatically rotate your CMKs. This will rotate your keys once a year while saving old keys indefinitely so that your data can still be decrypted. For additional information see Rotating customer master keys

+

Use EFS access points to simplify access to shared datasets

+

If you have shared datasets with different POSIX file permissions or want to restrict access to part of the shared file system by creating different mount points, consider using EFS access points. To learn more about working with access points, see https://docs.aws.amazon.com/efs/latest/ug/efs-access-points.html. Today, if you want to use an access point (AP) you'll need to reference the AP in the PV's volumeHandle parameter.

+
+

Attention

+

As of March 23, 2021 the EFS CSI driver supports dynamic provisioning of EFS Access Points. Access points are application-specific entry points into an EFS file system that make it easier to share a file system between multiple pods. Each EFS file system can have up to 120 PVs. See Introducing Amazon EFS CSI dynamic provisioning for additional information.

+
+

Secrets management

+

Kubernetes secrets are used to store sensitive information, such as user certificates, passwords, or API keys. They are persisted in etcd as base64 encoded strings. On EKS, the EBS volumes for etcd nodes are encrypted with EBS encryption. A pod can retrieve a Kubernetes secrets objects by referencing the secret in the podSpec. These secrets can either be mapped to an environment variable or mounted as volume. For additional information on creating secrets, see https://kubernetes.io/docs/concepts/configuration/secret/.

+
+

Caution

+

Secrets in a particular namespace can be referenced by all pods in the secret's namespace.

+
+
+

Caution

+

The node authorizer allows the Kubelet to read all of the secrets mounted to the node.

+
+

Use AWS KMS for envelope encryption of Kubernetes secrets

+

This allows you to encrypt your secrets with a unique data encryption key (DEK). The DEK is then encrypted using a key encryption key (KEK) from AWS KMS which can be automatically rotated on a recurring schedule. With the KMS plugin for Kubernetes, all Kubernetes secrets are stored in etcd in ciphertext instead of plain text and can only be decrypted by the Kubernetes API server. +For additional details, see using EKS encryption provider support for defense in depth

+

Audit the use of Kubernetes Secrets

+

On EKS, turn on audit logging and create a CloudWatch metrics filter and alarm to alert you when a secret is used (optional). The following is an example of a metrics filter for the Kubernetes audit log, {($.verb="get") && ($.objectRef.resource="secret")}. You can also use the following queries with CloudWatch Log Insights:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| stats count(*) by objectRef.name as secret
+| filter verb="get" and objectRef.resource="secrets"
+
+

The above query will display the number of times a secret has been accessed within a specific timeframe.

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter verb="get" and objectRef.resource="secrets"
+| display objectRef.namespace, objectRef.name, user.username, responseStatus.code
+
+

This query will display the secret, along with the namespace and username of the user who attempted to access the secret and the response code.

+

Rotate your secrets periodically

+

Kubernetes doesn't automatically rotate secrets. If you have to rotate secrets, consider using an external secret store, e.g. Vault or AWS Secrets Manager.

+

Use separate namespaces as a way to isolate secrets from different applications

+

If you have secrets that cannot be shared between applications in a namespace, create a separate namespace for those applications.

+

Use volume mounts instead of environment variables

+

The values of environment variables can unintentionally appear in logs. Secrets mounted as volumes are instantiated as tmpfs volumes (a RAM backed file system) that are automatically removed from the node when the pod is deleted.

+

Use an external secrets provider

+

There are several viable alternatives to using Kubernetes secrets, including AWS Secrets Manager and Hashicorp's Vault. These services offer features such as fine grained access controls, strong encryption, and automatic rotation of secrets that are not available with Kubernetes Secrets. Bitnami's Sealed Secrets is another approach that uses asymmetric encryption to create "sealed secrets". A public key is used to encrypt the secret while the private key used to decrypt the secret is kept within the cluster, allowing you to safely store sealed secrets in source control systems like Git. See Managing secrets deployment in Kubernetes using Sealed Secrets for further information.

+

As the use of external secrets stores has grown, so has need for integrating them with Kubernetes. The Secret Store CSI Driver is a community project that uses the CSI driver model to fetch secrets from external secret stores. Currently, the Driver has support for AWS Secrets Manager, Azure, Vault, and GCP. The AWS provider supports both AWS Secrets Manager and AWS Parameter Store. It can also be configured to rotate secrets when they expire and can synchronize AWS Secrets Manager secrets to Kubernetes Secrets. Synchronization of secrets can be useful when you need to reference a secret as an environment variable instead of reading them from a volume.

+
+

Note

+

When the secret store CSI driver has to fetch a secret, it assumes the IRSA role assigned to the pod that references a secret. The code for this operation can be found here.

+
+

For additional information about the AWS Secrets & Configuration Provider (ASCP) refer to the following resources:

+ +

external-secrets is yet another way to use an external secret store with Kubernetes. Like the CSI Driver, external-secrets works against a variety of different backends, including AWS Secrets Manager. The difference is, rather than retrieving secrets from the external secret store, external-secrets copies secrets from these backends to Kubernetes as Secrets. This lets you manage secrets using your preferred secret store and interact with secrets in a Kubernetes-native way.

+

Tools and resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/detective/index.html b/security/docs/detective/index.html new file mode 100644 index 000000000..ac5de6bbe --- /dev/null +++ b/security/docs/detective/index.html @@ -0,0 +1,2522 @@ + + + + + + + + + + + + + + + + + + + + + + + Detective Controls - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Auditing and logging

+

Collecting and analyzing [audit] logs is useful for a variety of different reasons. Logs can help with root cause analysis and attribution, i.e. ascribing a change to a particular user. When enough logs have been collected, they can be used to detect anomalous behaviors too. On EKS, the audit logs are sent to Amazon Cloudwatch Logs. The audit policy for EKS is as follows:

+
apiVersion: audit.k8s.io/v1beta1
+kind: Policy
+rules:
+  # Log full request and response for changes to aws-auth ConfigMap in kube-system namespace
+  - level: RequestResponse
+    namespaces: ["kube-system"]
+    verbs: ["update", "patch", "delete"]
+    resources:
+      - group: "" # core
+        resources: ["configmaps"]
+        resourceNames: ["aws-auth"]
+    omitStages:
+      - "RequestReceived"
+  # Do not log watch operations performed by kube-proxy on endpoints and services
+  - level: None
+    users: ["system:kube-proxy"]
+    verbs: ["watch"]
+    resources:
+      - group: "" # core
+        resources: ["endpoints", "services", "services/status"]
+  # Do not log get operations performed by kubelet on nodes and their statuses
+  - level: None
+    users: ["kubelet"] # legacy kubelet identity
+    verbs: ["get"]
+    resources:
+      - group: "" # core
+        resources: ["nodes", "nodes/status"]
+  # Do not log get operations performed by the system:nodes group on nodes and their statuses
+  - level: None
+    userGroups: ["system:nodes"]
+    verbs: ["get"]
+    resources:
+      - group: "" # core
+        resources: ["nodes", "nodes/status"]
+  # Do not log get and update operations performed by controller manager, scheduler, and endpoint-controller on endpoints in kube-system namespace
+  - level: None
+    users:
+      - system:kube-controller-manager
+      - system:kube-scheduler
+      - system:serviceaccount:kube-system:endpoint-controller
+    verbs: ["get", "update"]
+    namespaces: ["kube-system"]
+    resources:
+      - group: "" # core
+        resources: ["endpoints"]
+  # Do not log get operations performed by apiserver on namespaces and their statuses/finalizations
+  - level: None
+    users: ["system:apiserver"]
+    verbs: ["get"]
+    resources:
+      - group: "" # core
+        resources: ["namespaces", "namespaces/status", "namespaces/finalize"]
+  # Do not log get and list operations performed by controller manager on metrics.k8s.io resources
+  - level: None
+    users:
+      - system:kube-controller-manager
+    verbs: ["get", "list"]
+    resources:
+      - group: "metrics.k8s.io"
+  # Do not log access to health, version, and swagger non-resource URLs
+  - level: None
+    nonResourceURLs:
+      - /healthz*
+      - /version
+      - /swagger*
+  # Do not log events resources
+  - level: None
+    resources:
+      - group: "" # core
+        resources: ["events"]
+  # Log request for updates/patches to nodes and pods statuses by kubelet and node problem detector
+  - level: Request
+    users: ["kubelet", "system:node-problem-detector", "system:serviceaccount:kube-system:node-problem-detector"]
+    verbs: ["update", "patch"]
+    resources:
+      - group: "" # core
+        resources: ["nodes/status", "pods/status"]
+    omitStages:
+      - "RequestReceived"
+  # Log request for updates/patches to nodes and pods statuses by system:nodes group
+  - level: Request
+    userGroups: ["system:nodes"]
+    verbs: ["update", "patch"]
+    resources:
+      - group: "" # core
+        resources: ["nodes/status", "pods/status"]
+    omitStages:
+      - "RequestReceived"
+  # Log delete collection requests by namespace-controller in kube-system namespace
+  - level: Request
+    users: ["system:serviceaccount:kube-system:namespace-controller"]
+    verbs: ["deletecollection"]
+    omitStages:
+      - "RequestReceived"
+  # Log metadata for secrets, configmaps, and tokenreviews to protect sensitive data
+  - level: Metadata
+    resources:
+      - group: "" # core
+        resources: ["secrets", "configmaps"]
+      - group: authentication.k8s.io
+        resources: ["tokenreviews"]
+    omitStages:
+      - "RequestReceived"
+  # Log requests for serviceaccounts/token resources
+  - level: Request
+    resources:
+      - group: "" # core
+        resources: ["serviceaccounts/token"]
+  # Log get, list, and watch requests for various resource groups
+  - level: Request
+    verbs: ["get", "list", "watch"]
+    resources: 
+      - group: "" # core
+      - group: "admissionregistration.k8s.io"
+      - group: "apiextensions.k8s.io"
+      - group: "apiregistration.k8s.io"
+      - group: "apps"
+      - group: "authentication.k8s.io"
+      - group: "authorization.k8s.io"
+      - group: "autoscaling"
+      - group: "batch"
+      - group: "certificates.k8s.io"
+      - group: "extensions"
+      - group: "metrics.k8s.io"
+      - group: "networking.k8s.io"
+      - group: "policy"
+      - group: "rbac.authorization.k8s.io"
+      - group: "scheduling.k8s.io"
+      - group: "settings.k8s.io"
+      - group: "storage.k8s.io"
+    omitStages:
+      - "RequestReceived"
+  # Default logging level for known APIs to log request and response
+  - level: RequestResponse
+    resources: 
+      - group: "" # core
+      - group: "admissionregistration.k8s.io"
+      - group: "apiextensions.k8s.io"
+      - group: "apiregistration.k8s.io"
+      - group: "apps"
+      - group: "authentication.k8s.io"
+      - group: "authorization.k8s.io"
+      - group: "autoscaling"
+      - group: "batch"
+      - group: "certificates.k8s.io"
+      - group: "extensions"
+      - group: "metrics.k8s.io"
+      - group: "networking.k8s.io"
+      - group: "policy"
+      - group: "rbac.authorization.k8s.io"
+      - group: "scheduling.k8s.io"
+      - group: "settings.k8s.io"
+      - group: "storage.k8s.io"
+    omitStages:
+      - "RequestReceived"
+  # Default logging level for all other requests to log metadata only
+  - level: Metadata
+    omitStages:
+      - "RequestReceived"
+
+

Recommendations

+

Enable audit logs

+

The audit logs are part of the EKS managed Kubernetes control plane logs that are managed by EKS. Instructions for enabling/disabling the control plane logs, which includes the logs for the Kubernetes API server, the controller manager, and the scheduler, along with the audit log, can be found here, https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html#enabling-control-plane-log-export.

+
+

Info

+

When you enable control plane logging, you will incur costs for storing the logs in CloudWatch. This raises a broader issue about the ongoing cost of security. Ultimately you will have to weigh those costs against the cost of a security breach, e.g. financial loss, damage to your reputation, etc. You may find that you can adequately secure your environment by implementing only some of the recommendations in this guide.

+
+
+

Warning

+

The maximum size for a CloudWatch Logs entry is 256KB whereas the maximum Kubernetes API request size is 1.5MiB. Log entries greater than 256KB will either be truncated or only include the request metadata.

+
+

Utilize audit metadata

+

Kubernetes audit logs include two annotations that indicate whether or not a request was authorized authorization.k8s.io/decision and the reason for the decision authorization.k8s.io/reason. Use these attributes to ascertain why a particular API call was allowed.

+

Create alarms for suspicious events

+

Create an alarm to automatically alert you where there is an increase in 403 Forbidden and 401 Unauthorized responses, and then use attributes like host, sourceIPs, and k8s_user.username to find out where those requests are coming from.

+

Analyze logs with Log Insights

+

Use CloudWatch Log Insights to monitor changes to RBAC objects, e.g. Roles, RoleBindings, ClusterRoles, and ClusterRoleBindings. A few sample queries appear below:

+

Lists updates to the aws-auth ConfigMap:

+
fields @timestamp, @message
+| filter @logStream like "kube-apiserver-audit"
+| filter verb in ["update", "patch"]
+| filter objectRef.resource = "configmaps" and objectRef.name = "aws-auth" and objectRef.namespace = "kube-system"
+| sort @timestamp desc
+
+

Lists creation of new or changes to validation webhooks:

+
fields @timestamp, @message
+| filter @logStream like "kube-apiserver-audit"
+| filter verb in ["create", "update", "patch"] and responseStatus.code = 201
+| filter objectRef.resource = "validatingwebhookconfigurations"
+| sort @timestamp desc
+
+

Lists create, update, delete operations to Roles:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="roles" and verb in ["create", "update", "patch", "delete"]
+
+

Lists create, update, delete operations to RoleBindings:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="rolebindings" and verb in ["create", "update", "patch", "delete"]
+
+

Lists create, update, delete operations to ClusterRoles:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="clusterroles" and verb in ["create", "update", "patch", "delete"]
+
+

Lists create, update, delete operations to ClusterRoleBindings:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="clusterrolebindings" and verb in ["create", "update", "patch", "delete"]
+
+

Plots unauthorized read operations against Secrets:

+
fields @timestamp, @message
+| sort @timestamp desc
+| limit 100
+| filter objectRef.resource="secrets" and verb in ["get", "watch", "list"] and responseStatus.code="401"
+| stats count() by bin(1m)
+
+

List of failed anonymous requests:

+
fields @timestamp, @message, sourceIPs.0
+| sort @timestamp desc
+| limit 100
+| filter user.username="system:anonymous" and responseStatus.code in ["401", "403"]
+
+

Audit your CloudTrail logs

+

AWS APIs called by pods that are utilizing IAM Roles for Service Accounts (IRSA) are automatically logged to CloudTrail along with the name of the service account. If the name of a service account that wasn't explicitly authorized to call an API appears in the log, it may be an indication that the IAM role's trust policy was misconfigured. Generally speaking, Cloudtrail is a great way to ascribe AWS API calls to specific IAM principals.

+

Use CloudTrail Insights to unearth suspicious activity

+

CloudTrail insights automatically analyzes write management events from CloudTrail trails and alerts you of unusual activity. This can help you identify when there's an increase in call volume on write APIs in your AWS account, including from pods that use IRSA to assume an IAM role. See Announcing CloudTrail Insights: Identify and Response to Unusual API Activity for further information.

+

Additional resources

+

As the volume of logs increases, parsing and filtering them with Log Insights or another log analysis tool may become ineffective. As an alternative, you might want to consider running Sysdig Falco and ekscloudwatch. Falco analyzes audit logs and flags anomalies or abuse over an extended period of time. The ekscloudwatch project forwards audit log events from CloudWatch to Falco for analysis. Falco provides a set of default audit rules along with the ability to add your own.

+

Yet another option might be to store the audit logs in S3 and use the SageMaker Random Cut Forest algorithm to anomalous behaviors that warrant further investigation.

+

Tools and resources

+

The following commercial and open source projects can be used to assess your cluster's alignment with established best practices:

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/hosts/index.html b/security/docs/hosts/index.html new file mode 100644 index 000000000..bd7c05f71 --- /dev/null +++ b/security/docs/hosts/index.html @@ -0,0 +1,2516 @@ + + + + + + + + + + + + + + + + + + + + + + + Infrastructure Security - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Protecting the infrastructure (hosts)

+

Inasmuch as it's important to secure your container images, it's equally important to safeguard the infrastructure that runs them. This section explores different ways to mitigate risks from attacks launched directly against the host. These guidelines should be used in conjunction with those outlined in the Runtime Security section.

+

Recommendations

+

Use an OS optimized for running containers

+

Consider using Flatcar Linux, Project Atomic, RancherOS, and Bottlerocket, a special purpose OS from AWS designed for running Linux containers. It includes a reduced attack surface, a disk image that is verified on boot, and enforced permission boundaries using SELinux.

+

Alternately, use the EKS optimized AMI for your Kubernetes worker nodes. The EKS optimized AMI is released regularly and contains a minimal set of OS packages and binaries necessary to run your containerized workloads.

+

Please refer Amazon EKS AMI RHEL Build Specification for a sample configuration script which can be used for building a custom Amazon EKS AMI running on Red Hat Enterprise Linux using Hashicorp Packer. This script can be further leveraged to build STIG compliant EKS custom AMIs.

+

Keep your worker node OS updated

+

Regardless of whether you use a container-optimized host OS like Bottlerocket or a larger, but still minimalist, Amazon Machine Image like the EKS optimized AMIs, it is best practice to keep these host OS images up to date with the latest security patches.

+

For the EKS optimized AMIs, regularly check the CHANGELOG and/or release notes channel and automate the rollout of updated worker node images into your cluster.

+

Treat your infrastructure as immutable and automate the replacement of your worker nodes

+

Rather than performing in-place upgrades, replace your workers when a new patch or update becomes available. This can be approached a couple of ways. You can either add instances to an existing autoscaling group using the latest AMI as you sequentially cordon and drain nodes until all of the nodes in the group have been replaced with the latest AMI. Alternatively, you can add instances to a new node group while you sequentially cordon and drain nodes from the old node group until all of the nodes have been replaced. EKS managed node groups uses the first approach and will display a message in the console to upgrade your workers when a new AMI becomes available. eksctl also has a mechanism for creating node groups with the latest AMI and for gracefully cordoning and draining pods from nodes groups before the instances are terminated. If you decide to use a different method for replacing your worker nodes, it is strongly recommended that you automate the process to minimize human oversight as you will likely need to replace workers regularly as new updates/patches are released and when the control plane is upgraded.

+

With EKS Fargate, AWS will automatically update the underlying infrastructure as updates become available. Oftentimes this can be done seamlessly, but there may be times when an update will cause your pod to be rescheduled. Hence, we recommend that you create deployments with multiple replicas when running your application as a Fargate pod.

+

Periodically run kube-bench to verify compliance with CIS benchmarks for Kubernetes

+

kube-bench is an open source project from Aqua that evaluates your cluster against the CIS benchmarks for Kubernetes. The benchmark describes the best practices for securing unmanaged Kubernetes clusters. The CIS Kubernetes Benchmark encompasses the control plane and the data plane. Since Amazon EKS provides a fully managed control plane, not all of the recommendations from the CIS Kubernetes Benchmark are applicable. To ensure this scope reflects how Amazon EKS is implemented, AWS created the CIS Amazon EKS Benchmark. The EKS benchmark inherits from CIS Kubernetes Benchmark with additional inputs from the community with specific configuration considerations for EKS clusters.

+

When running kube-bench against an EKS cluster, follow these instructions from Aqua Security. For further information see Introducing The CIS Amazon EKS Benchmark.

+

Minimize access to worker nodes

+

Instead of enabling SSH access, use SSM Session Manager when you need to remote into a host. Unlike SSH keys which can be lost, copied, or shared, Session Manager allows you to control access to EC2 instances using IAM. Moreover, it provides an audit trail and log of the commands that were run on the instance.

+

As of August 19th, 2020 Managed Node Groups support custom AMIs and EC2 Launch Templates. This allows you to embed the SSM agent into the AMI or install it as the worker node is being bootstrapped. If you rather not modify the Optimized AMI or the ASG's launch template, you can install the SSM agent with a DaemonSet as in this example.

+

Minimal IAM policy for SSM based SSH Access

+

The AmazonSSMManagedInstanceCore AWS managed policy contains a number of permissions that are not required for SSM Session Manager / SSM RunCommand if you're just looking to avoid SSH access. Of concern specifically is the +* permissions for ssm:GetParameter(s) which would allow for the role to access all parameters in Parameter Store (including SecureStrings with the AWS managed KMS key configured).

+

The following IAM policy contains the minimal set of permissions to enable node access via SSM Systems Manager.

+
{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "EnableAccessViaSSMSessionManager",
+      "Effect": "Allow",
+      "Action": [
+        "ssmmessages:OpenDataChannel",
+        "ssmmessages:OpenControlChannel",
+        "ssmmessages:CreateDataChannel",
+        "ssmmessages:CreateControlChannel",
+        "ssm:UpdateInstanceInformation"
+      ],
+      "Resource": "*"
+    },
+    {
+      "Sid": "EnableSSMRunCommand",
+      "Effect": "Allow",
+      "Action": [
+        "ssm:UpdateInstanceInformation",
+        "ec2messages:SendReply",
+        "ec2messages:GetMessages",
+        "ec2messages:GetEndpoint",
+        "ec2messages:FailMessage",
+        "ec2messages:DeleteMessage",
+        "ec2messages:AcknowledgeMessage"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
+
+

With this policy in place and the Session Manager plugin installed, you can then run

+
aws ssm start-session --target [INSTANCE_ID_OF_EKS_NODE]
+
+

to access the node.

+
+

Note

+

You may also want to consider adding permissions to enable Session Manager logging.

+
+

Deploy workers onto private subnets

+

By deploying workers onto private subnets, you minimize their exposure to the Internet where attacks often originate. Beginning April 22, 2020, the assignment of public IP addresses to nodes in a managed node groups will be controlled by the subnet they are deployed onto. Prior to this, nodes in a Managed Node Group were automatically assigned a public IP. If you choose to deploy your worker nodes on to public subnets, implement restrictive AWS security group rules to limit their exposure.

+

Run Amazon Inspector to assess hosts for exposure, vulnerabilities, and deviations from best practices

+

You can use Amazon Inspector to check for unintended network access to your nodes and for vulnerabilities on the underlying Amazon EC2 instances.

+

Amazon Inspector can provide common vulnerabilities and exposures (CVE) data for your Amazon EC2 instances only if the Amazon EC2 Systems Manager (SSM) agent is installed and enabled. This agent is preinstalled on several Amazon Machine Images (AMIs) including EKS optimized Amazon Linux AMIs. Regardless of SSM agent status, all of your Amazon EC2 instances are scanned for network reachability issues. For more information about configuring scans for Amazon EC2, see Scanning Amazon EC2 instances.

+
+

Attention

+

Inspector cannot be run on the infrastructure used to run Fargate pods.

+
+

Alternatives

+

Run SELinux

+
+

Info

+

Available on Red Hat Enterprise Linux (RHEL), CentOS, Bottlerocket, and Amazon Linux 2023

+
+

SELinux provides an additional layer of security to keep containers isolated from each other and from the host. SELinux allows administrators to enforce mandatory access controls (MAC) for every user, application, process, and file. Think of it as a backstop that restricts the operations that can be performed against to specific resources based on a set of labels. On EKS, SELinux can be used to prevent containers from accessing each other's resources.

+

Container SELinux policies are defined in the container-selinux package. Docker CE requires this package (along with its dependencies) so that the processes and files created by Docker (or other container runtimes) run with limited system access. Containers leverage the container_t label which is an alias to svirt_lxc_net_t. These policies effectively prevent containers from accessing certain features of the host.

+

When you configure SELinux for Docker, Docker automatically labels workloads container_t as a type and gives each container a unique MCS level. This will isolate containers from one another. If you need looser restrictions, you can create your own profile in SElinux which grants a container permissions to specific areas of the file system. This is similar to PSPs in that you can create different profiles for different containers/pods. For example, you can have a profile for general workloads with a set of restrictive controls and another for things that require privileged access.

+

SELinux for Containers has a set of options that can be configured to modify the default restrictions. The following SELinux Booleans can be enabled or disabled based on your needs:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
BooleanDefaultDescription
container_connect_anyoffAllow containers to access privileged ports on the host. For example, if you have a container that needs to map ports to 443 or 80 on the host.
container_manage_cgroupoffAllow containers to manage cgroup configuration. For example, a container running systemd will need this to be enabled.
container_use_cephfsoffAllow containers to use a ceph file system.
+

By default, containers are allowed to read/execute under /usr and read most content from /etc. The files under /var/lib/docker and /var/lib/containers have the label container_var_lib_t. To view a full list of default, labels see the container.fc file.

+
docker container run -it \
+  -v /var/lib/docker/image/overlay2/repositories.json:/host/repositories.json \
+  centos:7 cat /host/repositories.json
+# cat: /host/repositories.json: Permission denied
+
+docker container run -it \
+  -v /etc/passwd:/host/etc/passwd \
+  centos:7 cat /host/etc/passwd
+# cat: /host/etc/passwd: Permission denied
+
+

Files labeled with container_file_t are the only files that are writable by containers. If you want a volume mount to be writeable, you will needed to specify :z or :Z at the end.

+
    +
  • :z will re-label the files so that the container can read/write
  • +
  • :Z will re-label the files so that only the container can read/write
  • +
+
ls -Z /var/lib/misc
+# -rw-r--r--. root root system_u:object_r:var_lib_t:s0   postfix.aliasesdb-stamp
+
+docker container run -it \
+  -v /var/lib/misc:/host/var/lib/misc:z \
+  centos:7 echo "Relabeled!"
+
+ls -Z /var/lib/misc
+#-rw-r--r--. root root system_u:object_r:container_file_t:s0 postfix.aliasesdb-stamp
+
+
docker container run -it \
+  -v /var/log:/host/var/log:Z \
+  fluentbit:latest
+
+

In Kubernetes, relabeling is slightly different. Rather than having Docker automatically relabel the files, you can specify a custom MCS label to run the pod. Volumes that support relabeling will automatically be relabeled so that they are accessible. Pods with a matching MCS label will be able to access the volume. If you need strict isolation, set a different MCS label for each pod.

+
securityContext:
+  seLinuxOptions:
+    # Provide a unique MCS label per container
+    # You can specify user, role, and type also
+    # enforcement based on type and level (svert)
+    level: s0:c144:c154
+
+

In this example s0:c144:c154 corresponds to an MCS label assigned to a file that the container is allowed to access.

+

On EKS you could create policies that allow for privileged containers to run, like FluentD and create an SELinux policy to allow it to read from /var/log on the host without needing to relabel the host directory. Pods with the same label will be able to access the same host volumes.

+

We have implemented sample AMIs for Amazon EKS that have SELinux configured on CentOS 7 and RHEL 7. These AMIs were developed to demonstrate sample implementations that meet requirements of highly regulated customers, such as STIG, CJIS, and C2S.

+
+

Caution

+

SELinux will ignore containers where the type is unconfined.

+
+

Tools and resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/iam/index.html b/security/docs/iam/index.html new file mode 100644 index 000000000..879362a9c --- /dev/null +++ b/security/docs/iam/index.html @@ -0,0 +1,3524 @@ + + + + + + + + + + + + + + + + + + + + + + + Identity and Access Management - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Identity and Access Management

+

Identity and Access Management (IAM) is an AWS service that performs two essential functions: Authentication and Authorization. Authentication involves the verification of a identity whereas authorization governs the actions that can be performed by AWS resources. Within AWS, a resource can be another AWS service, e.g. EC2, or an AWS principal such as an IAM User or Role. The rules governing the actions that a resource is allowed to perform are expressed as IAM policies.

+

Controlling Access to EKS Clusters

+

The Kubernetes project supports a variety of different strategies to authenticate requests to the kube-apiserver service, e.g. Bearer Tokens, X.509 certificates, OIDC, etc. EKS currently has native support for webhook token authentication, service account tokens, and as of February 21, 2021, OIDC authentication.

+

The webhook authentication strategy calls a webhook that verifies bearer tokens. On EKS, these bearer tokens are generated by the AWS CLI or the aws-iam-authenticator client when you run kubectl commands. As you execute commands, the token is passed to the kube-apiserver which forwards it to the authentication webhook. If the request is well-formed, the webhook calls a pre-signed URL embedded in the token's body. This URL validates the request's signature and returns information about the user, e.g. the user's account, Arn, and UserId to the kube-apiserver.

+

To manually generate a authentication token, type the following command in a terminal window:

+
aws eks get-token --cluster-name <cluster_name>
+
+

You can also get a token programmatically. Below is an example written in Go:

+
package main
+
+import (
+  "fmt"
+  "log"
+  "sigs.k8s.io/aws-iam-authenticator/pkg/token"
+)
+
+func main()  {
+  g, _ := token.NewGenerator(false, false)
+  tk, err := g.Get("<cluster_name>")
+  if err != nil {
+    log.Fatal(err)
+  }
+  fmt.Println(tk)
+}
+
+

The output should resemble this:

+
{
+  "kind": "ExecCredential",
+  "apiVersion": "client.authentication.k8s.io/v1alpha1",
+  "spec": {},
+  "status": {
+    "expirationTimestamp": "2020-02-19T16:08:27Z",
+    "token": "k8s-aws-v1.aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFKTkdSSUxLTlNSQzJXNVFBJTJGMjAyMDAyMTklMkZ1cy1lYXN0LTElMkZzdHMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDIwMDIxOVQxNTU0MjdaJlgtQW16LUV4cGlyZXM9NjAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JTNCeC1rOHMtYXdzLWlkJlgtQW16LVNpZ25hdHVyZT0yMjBmOGYzNTg1ZTMyMGRkYjVlNjgzYTVjOWE0MDUzMDFhZDc2NTQ2ZjI0ZjI4MTExZmRhZDA5Y2Y2NDhhMzkz"
+  }
+}
+
+

Each token starts with k8s-aws-v1. followed by a base64 encoded string. The string, when decoded, should resemble to something similar to this:

+
https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXJPFRILKNSRC2W5QA%2F20200219%2Fus-xxxx-1%2Fsts%2Faws4_request&X-Amz-Date=20200219T155427Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=XXXf8f3285e320ddb5e683a5c9a405301ad76546f24f28111fdad09cf648a393
+
+

The token consists of a pre-signed URL that includes an Amazon credential and signature. For additional details see https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html.

+

The token has a time to live (TTL) of 15 minutes after which a new token will need to be generated. This is handled automatically when you use a client like kubectl, however, if you're using the Kubernetes dashboard, you will need to generate a new token and re-authenticate each time the token expires.

+

Once the user's identity has been authenticated by the AWS IAM service, the kube-apiserver reads the aws-auth ConfigMap in the kube-system Namespace to determine the RBAC group to associate with the user. The aws-auth ConfigMap is used to create a static mapping between IAM principals, i.e. IAM Users and Roles, and Kubernetes RBAC groups. RBAC groups can be referenced in Kubernetes RoleBindings or ClusterRoleBindings. They are similar to IAM Roles in that they define a set of actions (verbs) that can be performed against a collection of Kubernetes resources (objects).

+

Cluster Access Manager

+

Cluster Access Manager, now the preferred way to manage access of AWS IAM principals to Amazon EKS clusters, is a functionality of the AWS API and is an opt-in feature for EKS v1.23 and later clusters (new or existing). It simplifies identity mapping between AWS IAM and Kubernetes RBACs, eliminating the need to switch between AWS and Kubernetes APIs or editing the aws-auth ConfigMap for access management, reducing operational overhead, and helping address misconfigurations. The tool also enables cluster administrators to revoke or refine cluster-admin permissions automatically granted to the AWS IAM principal used to create the cluster.

+

This API relies on two concepts:

+
    +
  • Access Entries: A cluster identity directly linked to an AWS IAM principal (user or role) allowed to authenticate to an Amazon EKS cluster.
  • +
  • Access Policies: Are Amazon EKS specific policies that provides the authorization for an Access Entry to perform actions in the Amazon EKS cluster.
  • +
+
+

At launch Amazon EKS supports only predefined and AWS managed policies. Access policies are not IAM entities and are defined and managed by Amazon EKS.

+
+

Cluster Access Manager allows the combination of upstream RBAC with Access Policies supporting allow and pass (but not deny) on Kubernetes AuthZ decisions regarding API server requests. A deny decision will happen when both, the upstream RBAC and Amazon EKS authorizers can't determine the outcome of a request evaluation.

+

With this feature, Amazon EKS supports three modes of authentication:

+
    +
  1. CONFIG_MAP to continue using aws-auth configMap exclusively.
  2. +
  3. API_AND_CONFIG_MAP to source authenticated IAM principals from both EKS Access Entry APIs and the aws-auth configMap, prioritizing the Access Entries. Ideal to migrate existing aws-auth permissions to Access Entries.
  4. +
  5. API to exclusively rely on EKS Access Entry APIs. This is the new recommended approach.
  6. +
+

To get started, cluster administrators can create or update Amazon EKS clusters, setting the preferred authentication to API_AND_CONFIG_MAP or API method and define Access Entries to grant access the desired AWS IAM principals.

+
$ aws eks create-cluster \
+    --name <CLUSTER_NAME> \
+    --role-arn <CLUSTER_ROLE_ARN> \
+    --resources-vpc-config subnetIds=<value>,endpointPublicAccess=true,endpointPrivateAccess=true \
+    --logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}' \
+    --access-config authenticationMode=API_AND_CONFIG_MAP,bootstrapClusterCreatorAdminPermissions=false
+
+

The above command is an example to create an Amazon EKS cluster already without the admin permissions of the cluster creator.

+

It is possible to update Amazon EKS clusters configuration to enable API authenticationMode using the update-cluster-config command, to do that on existing clusters using CONFIG_MAP you will have to first update to API_AND_CONFIG_MAP and then to API. These operations cannot be reverted, meaning that's not possible to switch from API to API_AND_CONFIG_MAP or CONFIG_MAP, and also from API_AND_CONFIG_MAP to CONFIG_MAP.

+
$ aws eks update-cluster-config \
+    --name <CLUSTER_NAME> \
+    --access-config authenticationMode=API
+
+

The API support commands to add and revoke access to the cluster, as well as validate the existing Access Policies and Access Entries for the specified cluster. The default policies are created to match Kubernetes RBACs as follows.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
EKS Access PolicyKubernetes RBAC
AmazonEKSClusterAdminPolicycluster-admin
AmazonEKSAdminPolicyadmin
AmazonEKSEditPolicyedit
AmazonEKSViewPolicyview
+
$ aws eks list-access-policies
+{
+    "accessPolicies": [
+        {
+            "name": "AmazonEKSAdminPolicy",
+            "arn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy"
+        },
+        {
+            "name": "AmazonEKSClusterAdminPolicy",
+            "arn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
+        },
+        {
+            "name": "AmazonEKSEditPolicy",
+            "arn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSEditPolicy"
+        },
+        {
+            "name": "AmazonEKSViewPolicy",
+            "arn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy"
+        }
+    ]
+}
+
+$ aws eks list-access-entries --cluster-name <CLUSTER_NAME>
+
+{
+    "accessEntries": []
+}
+
+
+

No Access Entries are available when the cluster is created without the cluster creator admin permission, which is the only entry created by default.

+
+

The aws-auth ConfigMap (deprecated)

+

One way Kubernetes integration with AWS authentication can be done is via the aws-auth ConfigMap, which resides in the kube-system Namespace. It is responsible for mapping the AWS IAM Identities (Users, Groups, and Roles) authentication, to Kubernetes role-based access control (RBAC) authorization. The aws-auth ConfigMap is automatically created in your Amazon EKS cluster during its provisioning phase. It was initially created to allow nodes to join your cluster, but as mentioned you can also use this ConfigMap to add RBACs access to IAM principals.

+

To check your cluster's aws-auth ConfigMap, you can use the following command.

+
kubectl -n kube-system get configmap aws-auth -o yaml
+
+

This is a sample of a default configuration of the aws-auth ConfigMap.

+
apiVersion: v1
+data:
+  mapRoles: |
+    - groups:
+      - system:bootstrappers
+      - system:nodes
+      - system:node-proxier
+      rolearn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/kube-system-<SELF_GENERATED_UUID>
+      username: system:node:{{SessionName}}
+kind: ConfigMap
+metadata:
+  creationTimestamp: "2023-10-22T18:19:30Z"
+  name: aws-auth
+  namespace: kube-system
+
+

The main session of this ConfigMap, is under data in the mapRoles block, which is basically composed by 3 parameters.

+
    +
  • groups: The Kubernetes group(s) to map the IAM Role to. This can be a default group, or a custom group specified in a clusterrolebinding or rolebinding. In the above example we have just system groups declared.
  • +
  • rolearn: The ARN of the AWS IAM Role be mapped to the Kubernetes group(s) add, using the following format arn:<PARTITION>:iam::<AWS_ACCOUNT_ID>:role/role-name.
  • +
  • username: The username within Kubernetes to map to the AWS IAM role. This can be any custom name.
  • +
+
+

It is also possible to map permissions for AWS IAM Users, defining a new configuration block for mapUsers, under data in the aws-auth ConfigMap, replacing the rolearn parameter for userarn, however as a Best Practice it's always recommended to user mapRoles instead.

+
+

To manage permissions, you can edit the aws-auth ConfigMap adding or removing access to your Amazon EKS cluster. Although it's possible to edit the aws-auth ConfigMap manually, it's recommended using tools like eksctl, since this is a very senstitive configuration, and an inaccurate configuration can lock you outside your Amazon EKS Cluster. Check the subsection Use tools to make changes to the aws-auth ConfigMap below for more details.

+

Cluster Access Recommendations

+

Make the EKS Cluster Endpoint private

+

By default when you provision an EKS cluster, the API cluster endpoint is set to public, i.e. it can be accessed from the Internet. Despite being accessible from the Internet, the endpoint is still considered secure because it requires all API requests to be authenticated by IAM and then authorized by Kubernetes RBAC. That said, if your corporate security policy mandates that you restrict access to the API from the Internet or prevents you from routing traffic outside the cluster VPC, you can:

+
    +
  • Configure the EKS cluster endpoint to be private. See Modifying Cluster Endpoint Access for further information on this topic.
  • +
  • Leave the cluster endpoint public and specify which CIDR blocks can communicate with the cluster endpoint. The blocks are effectively a whitelisted set of public IP addresses that are allowed to access the cluster endpoint.
  • +
  • Configure public access with a set of whitelisted CIDR blocks and set private endpoint access to enabled. This will allow public access from a specific range of public IPs while forcing all network traffic between the kubelets (workers) and the Kubernetes API through the cross-account ENIs that get provisioned into the cluster VPC when the control plane is provisioned.
  • +
+

Don't use a service account token for authentication

+

A service account token is a long-lived, static credential. If it is compromised, lost, or stolen, an attacker may be able to perform all the actions associated with that token until the service account is deleted. At times, you may need to grant an exception for applications that have to consume the Kubernetes API from outside the cluster, e.g. a CI/CD pipeline application. If such applications run on AWS infrastructure, like EC2 instances, consider using an instance profile and mapping that to a Kubernetes RBAC role.

+

Employ least privileged access to AWS Resources

+

An IAM User does not need to be assigned privileges to AWS resources to access the Kubernetes API. If you need to grant an IAM user access to an EKS cluster, create an entry in the aws-auth ConfigMap for that user that maps to a specific Kubernetes RBAC group.

+

Remove the cluster-admin permissions from the cluster creator principal

+

By default Amazon EKS clusters are created with a permanent cluster-admin permission bound to the cluster creator principal. With the Cluster Access Manager API, it's possible to create clusters without this permission setting the --access-config bootstrapClusterCreatorAdminPermissions to false, when using API_AND_CONFIG_MAP or API authentication mode. Revoke this access considered a best practice to avoid any unwanted changes to the cluster configuration. The process to revoke this access, follows the same process to revoke any other access to the cluster.

+

The API gives you flexibility to only disassociate an IAM principal from an Access Policy, in this case the AmazonEKSClusterAdminPolicy.

+
$ aws eks list-associated-access-policies \
+    --cluster-name <CLUSTER_NAME> \
+    --principal-arn <IAM_PRINCIPAL_ARN>
+
+$ aws eks disassociate-access-policy --cluster-name <CLUSTER_NAME> \
+    --principal-arn <IAM_PRINCIPAL_ARN. \
+    --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
+
+

Or completely removing the Access Entry associated with the cluster-admin permission.

+
$ aws eks list-access-entries --cluster-name <CLUSTER_NAME>
+
+{
+    "accessEntries": []
+}
+
+$ aws eks delete-access-entry --cluster-name <CLUSTER_NAME> \
+  --principal-arn <IAM_PRINCIPAL_ARN>
+
+
+

This access can be granted again if needed during an incident, emergency or break glass scenario where the cluster is otherwise inaccessible.

+
+

If the cluster still configured with the CONFIG_MAP authentication method, all additional users should be granted access to the cluster through the aws-auth ConfigMap, and after aws-auth ConfigMap is configured, the role assigned to the entity that created the cluster, can be deleted and only recreated in case of an incident, emergency or break glass scenario, or where the aws-auth ConfigMap is corrupted and the cluster is otherwise inaccessible. This can be particularly useful in production clusters.

+

Use IAM Roles when multiple users need identical access to the cluster

+

Rather than creating an entry for each individual IAM User, allow those users to assume an IAM Role and map that role to a Kubernetes RBAC group. This will be easier to maintain, especially as the number of users that require access grows.

+
+

Attention

+

When accessing the EKS cluster with the IAM entity mapped by aws-auth ConfigMap, the username described is recorded in the user field of the Kubernetes audit log. If you're using an IAM role, the actual users who assume that role aren't recorded and can't be audited.

+
+

If still using the aws-auth configMap as the authentication method, when assigning K8s RBAC permissions to an IAM role, you should include {{SessionName}} in your username. That way, the audit log will record the session name so you can track who the actual user assume this role along with the CloudTrail log.

+
- rolearn: arn:aws:iam::XXXXXXXXXXXX:role/testRole
+  username: testRole:{{SessionName}}
+  groups:
+    - system:masters
+
+
+

In Kubernetes 1.20 and above, this change is no longer required, since user.extra.sessionName.0 was added to the Kubernetes audit log.

+
+

Employ least privileged access when creating RoleBindings and ClusterRoleBindings

+

Like the earlier point about granting access to AWS Resources, RoleBindings and ClusterRoleBindings should only include the set of permissions necessary to perform a specific function. Avoid using ["*"] in your Roles and ClusterRoles unless it's absolutely necessary. If you're unsure what permissions to assign, consider using a tool like audit2rbac to automatically generate Roles and binding based on the observed API calls in the Kubernetes Audit Log.

+

Create cluster using an automated process

+

As seen in earlier steps, when creating an Amazon EKS cluster, if not using the using API_AND_CONFIG_MAP or API authentication mode, and not opting out to delegate cluster-admin permissions to the cluster creator, the IAM entity user or role, such as a federated user that creates the cluster, is automatically granted system:masters permissions in the cluster's RBAC configuration. Even being a best practice to remove this permission, as described here if using the CONFIG_MAP authentication method, relying on aws-auth ConfigMap, this access cannot be revoked. Therefore it is a good idea to create the cluster with an infrastructure automation pipeline tied to dedicated IAM role, with no permissions to be assumed by other users or entities and regularly audit this role's permissions, policies, and who has access to trigger the pipeline. Also, this role should not be used to perform routine actions on the cluster, and be exclusively used to cluster level actions triggered by the pipeline, via SCM code changes for example.

+

Create the cluster with a dedicated IAM role

+

When you create an Amazon EKS cluster, the IAM entity user or role, such as a federated user that creates the cluster, is automatically granted system:masters permissions in the cluster's RBAC configuration. This access cannot be removed and is not managed through the aws-auth ConfigMap. Therefore it is a good idea to create the cluster with a dedicated IAM role and regularly audit who can assume this role. This role should not be used to perform routine actions on the cluster, and instead additional users should be granted access to the cluster through the aws-auth ConfigMap for this purpose. After the aws-auth ConfigMap is configured, the role should be secured and only used in temporary elevated privilege mode / break glass for scenarios where the cluster is otherwise inaccessible. This can be particularly useful in clusters which do not have direct user access configured.

+

Regularly audit access to the cluster

+

Who requires access is likely to change over time. Plan to periodically audit the aws-auth ConfigMap to see who has been granted access and the rights they've been assigned. You can also use open source tooling like kubectl-who-can, or rbac-lookup to examine the roles bound to a particular service account, user, or group. We'll explore this topic further when we get to the section on auditing.

+

If relying on aws-auth configMap use tools to make changes

+

An improperly formatted aws-auth ConfigMap may cause you to lose access to the cluster. If you need to make changes to the ConfigMap, use a tool.

+

eksctl +The eksctl CLI includes a command for adding identity mappings to the aws-auth +ConfigMap.

+

View CLI Help:

+
$ eksctl create iamidentitymapping --help
+...
+
+

Check the identities mapped to your Amazon EKS Cluster.

+
$ eksctl get iamidentitymapping --cluster $CLUSTER_NAME --region $AWS_REGION
+ARN                                                                   USERNAME                        GROUPS                                                  ACCOUNT
+arn:aws:iam::788355785855:role/kube-system-<SELF_GENERATED_UUID>      system:node:{{SessionName}}     system:bootstrappers,system:nodes,system:node-proxier  
+
+

Make an IAM Role a Cluster Admin:

+
$ eksctl create iamidentitymapping --cluster  <CLUSTER_NAME> --region=<region> --arn arn:aws:iam::123456:role/testing --group system:masters --username admin
+...
+
+

For more information, review eksctl docs

+

aws-auth by keikoproj

+

aws-auth by keikoproj includes both a cli and a go library.

+

Download and view help CLI help:

+
$ go get github.com/keikoproj/aws-auth
+...
+$ aws-auth help
+...
+
+

Alternatively, install aws-auth with the krew plugin manager for kubectl.

+
$ kubectl krew install aws-auth
+...
+$ kubectl aws-auth
+...
+
+

Review the aws-auth docs on GitHub for more information, including the go library.

+

AWS IAM Authenticator CLI

+

The aws-iam-authenticator project includes a CLI for updating the ConfigMap.

+

Download a release on GitHub.

+

Add cluster permissions to an IAM Role:

+
$ ./aws-iam-authenticator add role --rolearn arn:aws:iam::185309785115:role/lil-dev-role-cluster --username lil-dev-user --groups system:masters --kubeconfig ~/.kube/config
+...
+
+

Alternative Approaches to Authentication and Access Management

+

While IAM is the preferred way to authenticate users who need access to an EKS cluster, it is possible to use an OIDC identity provider such as GitHub using an authentication proxy and Kubernetes impersonation. Posts for two such solutions have been published on the AWS Open Source blog:

+ +
+

Attention

+

EKS natively supports OIDC authentication without using a proxy. For further information, please read the launch blog, Introducing OIDC identity provider authentication for Amazon EKS. For an example showing how to configure EKS with Dex, a popular open source OIDC provider with connectors for a variety of different authention methods, see Using Dex & dex-k8s-authenticator to authenticate to Amazon EKS. As described in the blogs, the username/group of users authenticated by an OIDC provider will appear in the Kubernetes audit log.

+
+

You can also use AWS SSO to federate AWS with an external identity provider, e.g. Azure AD. If you decide to use this, the AWS CLI v2.0 includes an option to create a named profile that makes it easy to associate an SSO session with your current CLI session and assume an IAM role. Know that you must assume a role prior to running kubectl as the IAM role is used to determine the user's Kubernetes RBAC group.

+

Identities and Credentials for EKS pods

+

Certain applications that run within a Kubernetes cluster need permission to call the Kubernetes API to function properly. For example, the AWS Load Balancer Controller needs to be able to list a Service's Endpoints. The controller also needs to be able to invoke AWS APIs to provision and configure an ALB. In this section we will explore the best practices for assigning rights and privileges to Pods.

+

Kubernetes Service Accounts

+

A service account is a special type of object that allows you to assign a Kubernetes RBAC role to a pod. A default service account is created automatically for each Namespace within a cluster. When you deploy a pod into a Namespace without referencing a specific service account, the default service account for that Namespace will automatically get assigned to the Pod and the Secret, i.e. the service account (JWT) token for that service account, will get mounted to the pod as a volume at /var/run/secrets/kubernetes.io/serviceaccount. Decoding the service account token in that directory will reveal the following metadata:

+
{
+  "iss": "kubernetes/serviceaccount",
+  "kubernetes.io/serviceaccount/namespace": "default",
+  "kubernetes.io/serviceaccount/secret.name": "default-token-5pv4z",
+  "kubernetes.io/serviceaccount/service-account.name": "default",
+  "kubernetes.io/serviceaccount/service-account.uid": "3b36ddb5-438c-11ea-9438-063a49b60fba",
+  "sub": "system:serviceaccount:default:default"
+}
+
+

The default service account has the following permissions to the Kubernetes API.

+
apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  annotations:
+    rbac.authorization.kubernetes.io/autoupdate: "true"
+  creationTimestamp: "2020-01-30T18:13:25Z"
+  labels:
+    kubernetes.io/bootstrapping: rbac-defaults
+  name: system:discovery
+  resourceVersion: "43"
+  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/system%3Adiscovery
+  uid: 350d2ab8-438c-11ea-9438-063a49b60fba
+rules:
+- nonResourceURLs:
+  - /api
+  - /api/*
+  - /apis
+  - /apis/*
+  - /healthz
+  - /openapi
+  - /openapi/*
+  - /version
+  - /version/
+  verbs:
+  - get
+
+

This role authorizes unauthenticated and authenticated users to read API information and is deemed safe to be publicly accessible.

+

When an application running within a Pod calls the Kubernetes APIs, the Pod needs to be assigned a service account that explicitly grants it permission to call those APIs. Similar to guidelines for user access, the Role or ClusterRole bound to a service account should be restricted to the API resources and methods that the application needs to function and nothing else. To use a non-default service account simply set the spec.serviceAccountName field of a Pod to the name of the service account you wish to use. For additional information about creating service accounts, see https://kubernetes.io/docs/reference/access-authn-authz/rbac/#service-account-permissions.

+
+

Note

+

Prior to Kubernetes 1.24, Kubernetes would automatically create a secret for each a service account. This secret was mounted to the pod at /var/run/secrets/kubernetes.io/serviceaccount and would be used by the pod to authenticate to the Kubernetes API server. In Kubernetes 1.24, a service account token is dynamically generated when the pod runs and is only valid for an hour by default. A secret for the service account will not be created. If you have an application that runs outside the cluster that needs to authenticate to the Kubernetes API, e.g. Jenkins, you will need to create a secret of type kubernetes.io/service-account-token along with an annotation that references the service account such as metadata.annotations.kubernetes.io/service-account.name: <SERVICE_ACCOUNT_NAME>. Secrets created in this way do not expire.

+
+

IAM Roles for Service Accounts (IRSA)

+

IRSA is a feature that allows you to assign an IAM role to a Kubernetes service account. It works by leveraging a Kubernetes feature known as Service Account Token Volume Projection. When Pods are configured with a Service Account that references an IAM Role, the Kubernetes API server will call the public OIDC discovery endpoint for the cluster on startup. The endpoint cryptographically signs the OIDC token issued by Kubernetes and the resulting token mounted as a volume. This signed token allows the Pod to call the AWS APIs associated IAM role. When an AWS API is invoked, the AWS SDKs calls sts:AssumeRoleWithWebIdentity. After validating the token's signature, IAM exchanges the Kubernetes issued token for a temporary AWS role credential.

+

When using IRSA, it is important to reuse AWS SDK sessions to avoid unneeded calls to AWS STS.

+

Decoding the (JWT) token for IRSA will produce output similar to the example you see below:

+
{
+  "aud": [
+    "sts.amazonaws.com"
+  ],
+  "exp": 1582306514,
+  "iat": 1582220114,
+  "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128",
+  "kubernetes.io": {
+    "namespace": "default",
+    "pod": {
+      "name": "alpine-57b5664646-rf966",
+      "uid": "5a20f883-5407-11ea-a85c-0e62b7a4a436"
+    },
+    "serviceaccount": {
+      "name": "s3-read-only",
+      "uid": "a720ba5c-5406-11ea-9438-063a49b60fba"
+    }
+  },
+  "nbf": 1582220114,
+  "sub": "system:serviceaccount:default:s3-read-only"
+}
+
+

This particular token grants the Pod view-only privileges to S3 by assuming an IAM role. When the application attempts to read from S3, the token is exchanged for a temporary set of IAM credentials that resembles this:

+
{
+    "AssumedRoleUser": {
+        "AssumedRoleId": "AROA36C6WWEJULFUYMPB6:abc",
+        "Arn": "arn:aws:sts::123456789012:assumed-role/eksctl-winterfell-addon-iamserviceaccount-de-Role1-1D61LT75JH3MB/abc"
+    },
+    "Audience": "sts.amazonaws.com",
+    "Provider": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128",
+    "SubjectFromWebIdentityToken": "system:serviceaccount:default:s3-read-only",
+    "Credentials": {
+        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
+        "SessionToken": "FwoGZXIvYXdzEGMaDMLxAZkuLpmSwYXShiL9A1S0X87VBC1mHCrRe/pB2oes+l1eXxUYnPJyC9ayOoXMvqXQsomq0xs6OqZ3vaa5Iw1HIyA4Cv1suLaOCoU3hNvOIJ6C94H1vU0siQYk7DIq9Av5RZe+uE2FnOctNBvYLd3i0IZo1ajjc00yRK3v24VRq9nQpoPLuqyH2jzlhCEjXuPScPbi5KEVs9fNcOTtgzbVf7IG2gNiwNs5aCpN4Bv/Zv2A6zp5xGz9cWj2f0aD9v66vX4bexOs5t/YYhwuwAvkkJPSIGvxja0xRThnceHyFHKtj0H+bi/PWAtlI8YJcDX69cM30JAHDdQH+ltm/4scFptW1hlvMaP+WReCAaCrsHrAT+yka7ttw5YlUyvZ8EPog+j6fwHlxmrXM9h1BqdikomyJU00gm1++FJelfP+1zAwcyrxCnbRl3ARFrAt8hIlrT6Vyu8WvWtLxcI8KcLcJQb/LgkW+sCTGlYcY8z3zkigJMbYn07ewTL5Ss7LazTJJa758I7PZan/v3xQHd5DEc5WBneiV3iOznDFgup0VAMkIviVjVCkszaPSVEdK2NU7jtrh6Jfm7bU/3P6ZG+CkyDLIa8MBn9KPXeJd/y+jTk5Ii+fIwO/+mDpGNUribg6TPxhzZ8b/XdZO1kS1gVgqjXyVC+M+BRBh6C4H21w/eMzjCtDIpoxt5rGKL6Nu/IFMipoC4fgx6LIIHwtGYMG7SWQi7OsMAkiwZRg0n68/RqWgLzBt/4pfjSRYuk=",
+        "Expiration": "2020-02-20T18:49:50Z",
+        "AccessKeyId": "ASIAIOSFODNN7EXAMPLE"
+    }
+}
+
+

A mutating webhook that runs as part of the EKS control plane injects the AWS Role ARN and the path to a web identity token file into the Pod as environment variables. These values can also be supplied manually.

+
AWS_ROLE_ARN=arn:aws:iam::AWS_ACCOUNT_ID:role/IAM_ROLE_NAME
+AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
+
+

The kubelet will automatically rotate the projected token when it is older than 80% of its total TTL, or after 24 hours. The AWS SDKs are responsible for reloading the token when it rotates. For further information about IRSA, see https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html.

+

EKS Pod Identities

+

EKS Pod Identities is a feature launched at re:Invent 2023 that allows you to assign an IAM role to a kubernetes service account, without the need to configure an Open Id Connect (OIDC) identity provider(IDP) for each cluster in your AWS account. To use EKS Pod Identity, you must deploy an agent which runs as a DaemonSet pod on every eligible worker node. This agent is made available to you as an EKS Add-on and is a pre-requisite to use EKS Pod Identity feature. Your applications must use a supported version of the AWS SDK to use this feature.

+

When EKS Pod Identities are configured for a Pod, EKS will mount and refresh a pod identity token at /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token. This token will be used by the AWS SDK to communicate with the EKS Pod Identity Agent, which uses the pod identity token and the agent's IAM role to create temporary credentials for your pods by calling the AssumeRoleForPodIdentity API. The pod identity token delivered to your pods is a JWT issued from your EKS cluster and cryptographically signed, with appropriate JWT claims for use with EKS Pod Identities.

+

To learn more about EKS Pod Identities, please see this blog.

+

You do not have to make any modifications to your application code to use EKS Pod Identities. Supported AWS SDK versions will automatically discover credentials made available with EKS Pod Identities by using the credential provider chain. Like IRSA, EKS pod identities sets variables within your pods to direct them how to find AWS credentials.

+

Working with IAM roles for EKS Pod Identities

+
    +
  • EKS Pod Identities can only directly assume an IAM role that belongs to the same AWS account as the EKS cluster. To access an IAM role in another AWS account, you must assume that role by configuring a profile in your SDK configuration, or in your application's code.
  • +
  • When EKS Pod Identities are being configured for Service Accounts, the person or process configuring the Pod Identity Association must have the iam:PassRole entitlement for that role.
  • +
  • Each Service Account may only have one IAM role associated with it through EKS Pod Identities, however you can associate the same IAM role with multiple service accounts.
  • +
  • IAM roles used with EKS Pod Identities must allow the pods.eks.amazonaws.com Service Principal to assume them, and set session tags. The following is an example role trust policy which allows EKS Pod Identities to use an IAM role:
  • +
+
{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Principal": {
+        "Service": "pods.eks.amazonaws.com"
+      },
+      "Action": [
+        "sts:AssumeRole",
+        "sts:TagSession"
+      ],
+      "Condition": {
+        "StringEquals": {
+          "aws:SourceOrgId": "${aws:ResourceOrgId}"
+        }
+      }
+    }
+  ]
+}
+
+

AWS recommends using condition keys like aws:SourceOrgId to help protect against the cross-service confused deputy problem. In the above example role trust policy, the ResourceOrgId is a variable equal to the AWS Organizations Organization ID of the AWS Organization that the AWS account belongs to. EKS will pass in a value for aws:SourceOrgId equal to that when assuming a role with EKS Pod Identities.

+

ABAC and EKS Pod Identities

+

When EKS Pod Identities assumes an IAM role, it sets the following session tags:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EKS Pod Identities Session TagValue
kubernetes-namespaceThe namespace the pod associated with EKS Pod Identities runs in.
kubernetes-service-accountThe name of the kubernetes service account associated with EKS Pod Identities
eks-cluster-arnThe ARN of the EKS cluster, e.g. arn:${Partition}:eks:${Region}:${Account}:cluster/${ClusterName}. The cluster ARN is unique, but if a cluster is deleted and recreated in the same region with the same name, within the same AWS account, it will have the same ARN.
eks-cluster-nameThe name of the EKS cluster. Please note that EKS cluster names can be same within your AWS account, and EKS clusters in other AWS accounts.
kubernetes-pod-nameThe name of the pod in EKS.
kubernetes-pod-uidThe UID of the pod in EKS.
+

These session tags allow you to use Attribute Based Access Control(ABAC) to grant access to your AWS resources to only specific kubernetes service accounts. When doing so, it is very important to understand that kubernetes service accounts are only unique within a namespace, and kubernetes namespaces are only unique within an EKS cluster. These session tags can be accessed in AWS policies by using the aws:PrincipalTag/<tag-key> global condition key, such as aws:PrincipalTag/eks-cluster-arn

+

For example, if you wanted to grant access to only a specific service account to access an AWS resource in your account with an IAM or resource policy, you would need to check eks-cluster-arn and kubernetes-namespace tags as well as the kubernetes-service-account to ensure that only that service accounts from the intended cluster have access to that resource as other clusters could have identical kubernetes-service-accounts and kubernetes-namespaces.

+

This example S3 Bucket policy only grants access to objects in the S3 bucket it's attached to, only if kubernetes-service-account, kubernetes-namespace, eks-cluster-arn all meet their expected values, where the EKS cluster is hosted in the AWS account 111122223333.

+
{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Principal": {
+                "AWS": "arn:aws:iam::111122223333:root"
+            },
+            "Action": "s3:*",
+            "Resource":            [
+                "arn:aws:s3:::ExampleBucket/*"
+            ],
+            "Condition": {
+                "StringEquals": {
+                    "aws:PrincipalTag/kubernetes-service-account": "s3objectservice",
+                    "aws:PrincipalTag/eks-cluster-arn": "arn:aws:eks:us-west-2:111122223333:cluster/ProductionCluster",
+                    "aws:PrincipalTag/kubernetes-namespace": "s3datanamespace"
+                }
+            }
+        }
+    ]
+}
+
+

EKS Pod Identities compared to IRSA

+

Both EKS Pod Identities and IRSA are preferred ways to deliver temporary AWS credentials to your EKS pods. Unless you have specific usecases for IRSA, we recommend you use EKS Pod Identities when using EKS. This table helps compare the two features.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#EKS Pod IdentitiesIRSA
Requires permission to create an OIDC IDP in your AWS accounts?NoYes
Requires unique IDP setup per clusterNoYes
Sets relevant session tags for use with ABACYesNo
Requires an iam:PassRole Check?YesNo
Uses AWS STS Quota from your AWS account?NoYes
Can access other AWS accountsIndirectly with role chainingDirectly with sts:AssumeRoleWithWebIdentity
Compatible with AWS SDKsYesYes
Requires Pod Identity Agent Daemonset on nodes?YesNo
+

Identities and Credentials for EKS pods Recommendations

+

Update the aws-node daemonset to use IRSA

+

At present, the aws-node daemonset is configured to use a role assigned to the EC2 instances to assign IPs to pods. This role includes several AWS managed policies, e.g. AmazonEKS_CNI_Policy and EC2ContainerRegistryReadOnly that effectively allow all pods running on a node to attach/detach ENIs, assign/unassign IP addresses, or pull images from ECR. Since this presents a risk to your cluster, it is recommended that you update the aws-node daemonset to use IRSA. A script for doing this can be found in the repository for this guide.

+

The aws-node daemonset supports EKS Pod Identities in versions v1.15.5 and later.

+

Restrict access to the instance profile assigned to the worker node

+

When you use IRSA or EKS Pod Identities, it updates the credential chain of the pod to use IRSA or EKS Pod Identities first, however, the pod can still inherit the rights of the instance profile assigned to the worker node. When using IRSA or EKS Pod Identities, it is strongly recommended that you block access instance metadata to help ensure that your applications only have the permissions they require, and not their nodes.

+
+

Caution

+

Blocking access to instance metadata will prevent pods that do not use IRSA or EKS Pod Identities from inheriting the role assigned to the worker node.

+
+

You can block access to instance metadata by requiring the instance to use IMDSv2 only and updating the hop count to 1 as in the example below. You can also include these settings in the node group's launch template. Do not disable instance metadata as this will prevent components like the node termination handler and other things that rely on instance metadata from working properly.

+
$ aws ec2 modify-instance-metadata-options --instance-id <value> --http-tokens required --http-put-response-hop-limit 1
+...
+
+

If you are using Terraform to create launch templates for use with Managed Node Groups, add the metadata block to configure the hop count as seen in this code snippet:

+
resource "aws_launch_template" "foo" {
+  name = "foo"
+  ...
+    metadata_options {
+    http_endpoint               = "enabled"
+    http_tokens                 = "required"
+    http_put_response_hop_limit = 1
+    instance_metadata_tags      = "enabled"
+  }
+  ...
+
+

You can also block a pod's access to EC2 metadata by manipulating iptables on the node. For further information about this method, see Limiting access to the instance metadata service.

+

If you have an application that is using an older version of the AWS SDK that doesn't support IRSA or EKS Pod Identities, you should update the SDK version.

+

Scope the IAM Role trust policy for IRSA Roles to the service account name, namespace, and cluster

+

The trust policy can be scoped to a Namespace or a specific service account within a Namespace. When using IRSA it's best to make the role trust policy as explicit as possible by including the service account name. This will effectively prevent other Pods within the same Namespace from assuming the role. The CLI eksctl will do this automatically when you use it to create service accounts/IAM roles. See https://eksctl.io/usage/iamserviceaccounts/ for further information.

+

When working with IAM directly, this is adding condition into the role's trust policy that uses conditions to ensure the :sub claim are the namespace and service account you expect. As an example, before we had an IRSA token with a sub claim of "system:serviceaccount:default:s3-read-only" . This is the default namespace and the service account is s3-read-only. You would use a condition like the following to ensure that only your service account in a given namespace from your cluster can assume that role:

+
  "Condition": {
+      "StringEquals": {
+          "oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128:aud": "sts.amazonaws.com",
+          "oidc.eks.us-west-2.amazonaws.com/id/D43CF17C27A865933144EA99A26FB128:sub": "system:serviceaccount:default:s3-read-only"
+      }
+  }
+
+

Use one IAM role per application

+

With both IRSA and EKS Pod Identity, it is a best practice to give each application its own IAM role. This gives you improved isolation as you can modify one application without impacting another, and allows you to apply the principal of least privilege by only granting an application the permissions it needs.

+

When using ABAC with EKS Pod Identity, you may use a common IAM role across multiple service accounts and rely on their session attributes for access control. This is especially useful when operating at scale, as ABAC allows you to operate with fewer IAM roles.

+

When your application needs access to IMDS, use IMDSv2 and increase the hop limit on EC2 instances to 2

+

IMDSv2 requires you use a PUT request to get a session token. The initial PUT request has to include a TTL for the session token. Newer versions of the AWS SDKs will handle this and the renewal of said token automatically. It's also important to be aware that the default hop limit on EC2 instances is intentionally set to 1 to prevent IP forwarding. As a consequence, Pods that request a session token that are run on EC2 instances may eventually time out and fallback to using the IMDSv1 data flow. EKS adds support IMDSv2 by enabling both v1 and v2 and changing the hop limit to 2 on nodes provisioned by eksctl or with the official CloudFormation templates.

+

Disable auto-mounting of service account tokens

+

If your application doesn't need to call the Kubernetes API set the automountServiceAccountToken attribute to false in the PodSpec for your application or patch the default service account in each namespace so that it's no longer mounted to pods automatically. For example:

+
kubectl patch serviceaccount default -p $'automountServiceAccountToken: false'
+
+

Use dedicated service accounts for each application

+

Each application should have its own dedicated service account. This applies to service accounts for the Kubernetes API as well as IRSA and EKS Pod Identity.

+
+

Attention

+

If you employ a blue/green approach to cluster upgrades instead of performing an in-place cluster upgrade when using IRSA, you will need to update the trust policy of each of the IRSA IAM roles with the OIDC endpoint of the new cluster. A blue/green cluster upgrade is where you create a cluster running a newer version of Kubernetes alongside the old cluster and use a load balancer or a service mesh to seamlessly shift traffic from services running on the old cluster to the new cluster. +When using blue/green cluster upgrades with EKS Pod Identity, you would create pod identity associations between the IAM roles and service accounts in the new cluster. And update the IAM role trust policy if you have a sourceArn condition.

+
+

Run the application as a non-root user

+

Containers run as root by default. While this allows them to read the web identity token file, running a container as root is not considered a best practice. As an alternative, consider adding the spec.securityContext.runAsUser attribute to the PodSpec. The value of runAsUser is arbitrary value.

+

In the following example, all processes within the Pod will run under the user ID specified in the runAsUser field.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: security-context-demo
+spec:
+  securityContext:
+    runAsUser: 1000
+    runAsGroup: 3000
+  containers:
+  - name: sec-ctx-demo
+    image: busybox
+    command: [ "sh", "-c", "sleep 1h" ]
+
+

When you run a container as a non-root user, it prevents the container from reading the IRSA service account token because the token is assigned 0600 [root] permissions by default. If you update the securityContext for your container to include fsgroup=65534 [Nobody] it will allow the container to read the token.

+
spec:
+  securityContext:
+    fsGroup: 65534
+
+

In Kubernetes 1.19 and above, this change is no longer required and applications can read the IRSA service account token without adding them to the Nobody group.

+

Grant least privileged access to applications

+

Action Hero is a utility that you can run alongside your application to identify the AWS API calls and corresponding IAM permissions your application needs to function properly. It is similar to IAM Access Advisor in that it helps you gradually limit the scope of IAM roles assigned to applications. Consult the documentation on granting least privileged access to AWS resources for further information.

+

Consider setting a permissions boundary on IAM roles used with IRSA and Pod Identities. You can use the permissions boundary to ensure that the roles used by IRSA or Pod Identities can not exceed a maximum level of permissions. For an example guide on getting started with permissions boundaries with an example permissions boundary policy, please see this github repo.

+

Review and revoke unnecessary anonymous access to your EKS cluster

+

Ideally anonymous access should be disabled for all API actions. Anonymous access is granted by creating a RoleBinding or ClusterRoleBinding for the Kubernetes built-in user system:anonymous. You can use the rbac-lookup tool to identify permissions that system:anonymous user has on your cluster:

+
./rbac-lookup | grep -P 'system:(anonymous)|(unauthenticated)'
+system:anonymous               cluster-wide        ClusterRole/system:discovery
+system:unauthenticated         cluster-wide        ClusterRole/system:discovery
+system:unauthenticated         cluster-wide        ClusterRole/system:public-info-viewer
+
+

Any role or ClusterRole other than system:public-info-viewer should not be bound to system:anonymous user or system:unauthenticated group.

+

There may be some legitimate reasons to enable anonymous access on specific APIs. If this is the case for your cluster ensure that only those specific APIs are accessible by anonymous user and exposing those APIs without authentication doesn’t make your cluster vulnerable.

+

Prior to Kubernetes/EKS Version 1.14, system:unauthenticated group was associated to system:discovery and system:basic-user ClusterRoles by default. Note that even if you have updated your cluster to version 1.14 or higher, these permissions may still be enabled on your cluster, since cluster updates do not revoke these permissions. +To check which ClusterRoles have "system:unauthenticated" except system:public-info-viewer you can run the following command (requires jq util):

+
kubectl get ClusterRoleBinding -o json | jq -r '.items[] | select(.subjects[]?.name =="system:unauthenticated") | select(.metadata.name != "system:public-info-viewer") | .metadata.name'
+
+

And "system:unauthenticated" can be removed from all the roles except "system:public-info-viewer" using:

+
kubectl get ClusterRoleBinding -o json | jq -r '.items[] | select(.subjects[]?.name =="system:unauthenticated") | select(.metadata.name != "system:public-info-viewer") | del(.subjects[] | select(.name =="system:unauthenticated"))' | kubectl apply -f -
+
+

Alternatively, you can check and remove it manually by kubectl describe and kubectl edit. To check if system:unauthenticated group has system:discovery permissions on your cluster run the following command:

+
kubectl describe clusterrolebindings system:discovery
+
+Name:         system:discovery
+Labels:       kubernetes.io/bootstrapping=rbac-defaults
+Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
+Role:
+  Kind:  ClusterRole
+  Name:  system:discovery
+Subjects:
+  Kind   Name                    Namespace
+  ----   ----                    ---------
+  Group  system:authenticated
+  Group  system:unauthenticated
+
+

To check if system:unauthenticated group has system:basic-user permission on your cluster run the following command:

+
kubectl describe clusterrolebindings system:basic-user
+
+Name:         system:basic-user
+Labels:       kubernetes.io/bootstrapping=rbac-defaults
+Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
+Role:
+  Kind:  ClusterRole
+  Name:  system:basic-user
+Subjects:
+  Kind   Name                    Namespace
+  ----   ----                    ---------
+  Group  system:authenticated
+  Group  system:unauthenticated
+
+

If system:unauthenticated group is bound to system:discovery and/or system:basic-user ClusterRoles on your cluster, you should disassociate these roles from system:unauthenticated group. Edit system:discovery ClusterRoleBinding using the following command:

+
kubectl edit clusterrolebindings system:discovery
+
+

The above command will open the current definition of system:discovery ClusterRoleBinding in an editor as shown below:

+
# Please edit the object below. Lines beginning with a '#' will be ignored,
+# and an empty file will abort the edit. If an error occurs while saving this file will be
+# reopened with the relevant failures.
+#
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  annotations:
+    rbac.authorization.kubernetes.io/autoupdate: "true"
+  creationTimestamp: "2021-06-17T20:50:49Z"
+  labels:
+    kubernetes.io/bootstrapping: rbac-defaults
+  name: system:discovery
+  resourceVersion: "24502985"
+  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterrolebindings/system%3Adiscovery
+  uid: b7936268-5043-431a-a0e1-171a423abeb6
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: system:discovery
+subjects:
+- apiGroup: rbac.authorization.k8s.io
+  kind: Group
+  name: system:authenticated
+- apiGroup: rbac.authorization.k8s.io
+  kind: Group
+  name: system:unauthenticated
+
+

Delete the entry for system:unauthenticated group from the “subjects” section in the above editor screen.

+

Repeat the same steps for system:basic-user ClusterRoleBinding.

+

Reuse AWS SDK sessions with IRSA

+

When you use IRSA, applications written using the AWS SDK use the token delivered to your pods to call sts:AssumeRoleWithWebIdentity to generate temporary AWS credentials. This is different from other AWS compute services, where the compute service delivers temporary AWS credentials directly to the AWS compute resource, such as a lambda function. This means that every time an AWS SDK session is initialized, a call to AWS STS for AssumeRoleWithWebIdentity is made. If your application scales rapidly and initializes many AWS SDK sessions, you may experience throttling from AWS STS as your code will be making many calls for AssumeRoleWithWebIdentity.

+

To avoid this scenario, we recommend reusing AWS SDK sessions within your application so that unnecessary calls to AssumeRoleWithWebIdentity are not made.

+

In the following example code, a session is created using the boto3 python SDK, and that same session is used to create clients and interact with both Amazon S3 and Amazon SQS. AssumeRoleWithWebIdentity is only called once, and the AWS SDK will refresh the credentials of my_session when they expire automatically.

+
import boto3
+
+# Create your own session
+my_session = boto3.session.Session()
+
+# Now we can create low-level clients from our session
+sqs = my_session.client('sqs')
+s3 = my_session.client('s3')
+
+s3response = s3.list_buckets()
+sqsresponse = sqs.list_queues()
+
+
+#print the response from the S3 and SQS APIs
+print("s3 response:")
+print(s3response)
+print("---")
+print("sqs response:")
+print(sqsresponse)
+
+

If you're migrating an application from another AWS compute service, such as EC2, to EKS with IRSA, this is a particularly important detail. On other compute services initializing an AWS SDK session does not call AWS STS unless you instruct it to.

+

Alternative approaches

+

While IRSA and EKS Pod Identities are the preferred ways to assign an AWS identity to a pod, they require that you include recent version of the AWS SDKs in your application. For a complete listing of the SDKs that currently support IRSA, see https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html, for EKS Pod Identities, see https://docs.aws.amazon.com/eks/latest/userguide/pod-id-minimum-sdk.html. If you have an application that you can't immediately update with a compatible SDK, there are several community-built solutions available for assigning IAM roles to Kubernetes pods, including kube2iam and kiam. Although AWS doesn't endorse, condone, nor support the use of these solutions, they are frequently used by the community at large to achieve similar results as IRSA and EKS Pod Identities.

+

If you need to use one of these non-aws provided solutions, please exercise due diligence and ensure you understand security implications of doing so.

+

Tools and Resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/image/index.html b/security/docs/image/index.html new file mode 100644 index 000000000..447770b47 --- /dev/null +++ b/security/docs/image/index.html @@ -0,0 +1,2660 @@ + + + + + + + + + + + + + + + + + + + + + + + Image Security - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Image security

+

You should consider the container image as your first line of defense against an attack. An insecure, poorly constructed image can allow an attacker to escape the bounds of the container and gain access to the host. Once on the host, an attacker can gain access to sensitive information or move laterally within the cluster or with your AWS account. The following best practices will help mitigate risk of this happening.

+

Recommendations

+

Create minimal images

+

Start by removing all extraneous binaries from the container image. If you’re using an unfamiliar image from Dockerhub, inspect the image using an application like Dive which can show you the contents of each of the container’s layers. Remove all binaries with the SETUID and SETGID bits as they can be used to escalate privilege and consider removing all shells and utilities like nc and curl that can be used for nefarious purposes. You can find the files with SETUID and SETGID bits with the following command:

+
find / -perm /6000 -type f -exec ls -ld {} \;
+
+

To remove the special permissions from these files, add the following directive to your container image:

+
RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} \; || true
+
+

Colloquially, this is known as de-fanging your image.

+

Use multi-stage builds

+

Using multi-stage builds is a way to create minimal images. Oftentimes, multi-stage builds are used to automate parts of the Continuous Integration cycle. For example, multi-stage builds can be used to lint your source code or perform static code analysis. This affords developers an opportunity to get near immediate feedback instead of waiting for a pipeline to execute. Multi-stage builds are attractive from a security standpoint because they allow you to minimize the size of the final image pushed to your container registry. Container images devoid of build tools and other extraneous binaries improves your security posture by reducing the attack surface of the image. For additional information about multi-stage builds, see Docker's multi-stage builds documentation.

+

Create Software Bill of Materials (SBOMs) for your container image

+

A “software bill of materials” (SBOM) is a nested inventory of the software artifacts that make up your container image. +SBOM is a key building block in software security and software supply chain risk management. Generating, storing SBOMS in a central repository and scanning SBOMs for vulnerabilities helps address the following concerns:

+
    +
  • Visibility: understand what components make up your container image. Storing in a central repository allows SBOMs to be audited and scanned anytime, even post deployment to detect and respond to new vulnerabilities such as zero day vulnerabilities.
  • +
  • Provenance Verification: assurance that existing assumptions of where and how an artifact originates from are true and that the artifact or its accompanying metadata have not been tampered with during the build or delivery processes.
  • +
  • Trustworthiness: assurance that a given artifact and its contents can be trusted to do what it is purported to do, i.e. is suitable for a purpose. This involves judgement on whether the code is safe to execute and making informed decisions about the risks associated with executing the code. Trustworthiness is assured by creating an attested pipeline execution report along with attested SBOM and attested CVE scan report to assure the consumers of the image that this image is in-fact created through secure means (pipeline) with secure components.
  • +
  • Dependency Trust Verification: recursive checking of an artifact’s dependency tree for trustworthiness and provenance of the artifacts it uses. Drift in SBOMs can help detect malicious activity including unauthorized, untrusted dependencies, infiltration attempts.
  • +
+

The following tools can be used to generate SBOM:

+
    +
  • Amazon Inspector can be used to create and export SBOMs.
  • +
  • Syft from Anchore can also be used for SBOM generation. For quicker vulnerability scans, the SBOM generated for a container image can be used as an input to scan. The SBOM and scan report are then attested and attached to the image before pushing the image to a central OCI repository such as Amazon ECR for review and audit purposes.
  • +
+

Learn more about securing your software supply chain by reviewing CNCF Software Supply Chain Best Practices guide.

+

Scan images for vulnerabilities regularly

+

Like their virtual machine counterparts, container images can contain binaries and application libraries with vulnerabilities or develop vulnerabilities over time. The best way to safeguard against exploits is by regularly scanning your images with an image scanner. Images that are stored in Amazon ECR can be scanned on push or on-demand (once during a 24 hour period). ECR currently supports two types of scanning - Basic and Enhanced. Basic scanning leverages Clair an open source image scanning solution for no cost. Enhanced scanning uses Amazon Inspector to provide automatic continuous scans for additional cost. After an image is scanned, the results are logged to the event stream for ECR in EventBridge. You can also see the results of a scan from within the ECR console. Images with a HIGH or CRITICAL vulnerability should be deleted or rebuilt. If an image that has been deployed develops a vulnerability, it should be replaced as soon as possible.

+

Knowing where images with vulnerabilities have been deployed is essential to keeping your environment secure. While you could conceivably build an image tracking solution yourself, there are already several commercial offerings that provide this and other advanced capabilities out of the box, including:

+ +

A Kubernetes validation webhook could also be used to validate that images are free of critical vulnerabilities. Validation webhooks are invoked prior to the Kubernetes API. They are typically used to reject requests that don't comply with the validation criteria defined in the webhook. This is an example of a serverless webhook that calls the ECR describeImageScanFindings API to determine whether a pod is pulling an image with critical vulnerabilities. If vulnerabilities are found, the pod is rejected and a message with list of CVEs is returned as an Event.

+

Use attestations to validate artifact integrity

+

An attestation is a cryptographically signed “statement” that claims something - a “predicate” e.g. a pipeline run or the SBOM or the vulnerability scan report is true about another thing - a “subject” i.e. the container image.

+

Attestations help users to validate that an artifact comes from a trusted source in the software supply chain. As an example, we may use a container image without knowing all the software components or dependencies that are included in that image. However, if we trust whatever the producer of the container image says about what software is present, we can use the producer’s attestation to rely on that artifact. This means that we can proceed to use the artifact safely in our workflow in place of having done the analysis ourself.

+
    +
  • Attestations can be created using AWS Signer or Sigstore cosign.
  • +
  • Kubernetes admission controllers such as Kyverno can be used to verify attestations.
  • +
  • Refer to this workshop to learn more about software supply chain management best practices on AWS using open source tools with topics including creating and attaching attestations to a container image.
  • +
+

Create IAM policies for ECR repositories

+

Nowadays, it is not uncommon for an organization to have multiple development teams operating independently within a shared AWS account. If these teams don't need to share assets, you may want to create a set of IAM policies that restrict access to the repositories each team can interact with. A good way to implement this is by using ECR namespaces. Namespaces are a way to group similar repositories together. For example, all of the registries for team A can be prefaced with the team-a/ while those for team B can use the team-b/ prefix. The policy to restrict access might look like the following:

+
{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "AllowPushPull",
+      "Effect": "Allow",
+      "Action": [
+        "ecr:GetDownloadUrlForLayer",
+        "ecr:BatchGetImage",
+        "ecr:BatchCheckLayerAvailability",
+        "ecr:PutImage",
+        "ecr:InitiateLayerUpload",
+        "ecr:UploadLayerPart",
+        "ecr:CompleteLayerUpload"
+      ],
+      "Resource": [
+        "arn:aws:ecr:<region>:<account_id>:repository/team-a/*"
+      ]
+    }
+  ]
+}
+
+

Consider using ECR private endpoints

+

The ECR API has a public endpoint. Consequently, ECR registries can be accessed from the Internet so long as the request has been authenticated and authorized by IAM. For those who need to operate in a sandboxed environment where the cluster VPC lacks an Internet Gateway (IGW), you can configure a private endpoint for ECR. Creating a private endpoint enables you to privately access the ECR API through a private IP address instead of routing traffic across the Internet. For additional information on this topic, see Amazon ECR interface VPC endpoints.

+

Implement endpoint policies for ECR

+

The default endpoint policy for allows access to all ECR repositories within a region. This might allow an attacker/insider to exfiltrate data by packaging it as a container image and pushing it to a registry in another AWS account. Mitigating this risk involves creating an endpoint policy that limits API access to ECR repositories. For example, the following policy allows all AWS principles in your account to perform all actions against your and only your ECR repositories:

+
{
+  "Statement": [
+    {
+      "Sid": "LimitECRAccess",
+      "Principal": "*",
+      "Action": "*",
+      "Effect": "Allow",
+      "Resource": "arn:aws:ecr:<region>:<account_id>:repository/*"
+    }
+  ]
+}
+
+

You can enhance this further by setting a condition that uses the new PrincipalOrgID attribute which will prevent pushing/pulling of images by an IAM principle that is not part of your AWS Organization. See, aws:PrincipalOrgID for additional details. +We recommended applying the same policy to both the com.amazonaws.<region>.ecr.dkr and the com.amazonaws.<region>.ecr.api endpoints. +Since EKS pulls images for kube-proxy, coredns, and aws-node from ECR, you will need to add the account ID of the registry, e.g. 602401143452.dkr.ecr.us-west-2.amazonaws.com/* to the list of resources in the endpoint policy or alter the policy to allow pulls from "*" and restrict pushes to your account ID. The table below reveals the mapping between the AWS accounts where EKS images are vended from and cluster region.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account NumberRegion
602401143452All commercial regions except for those listed below
------
800184023465ap-east-1 - Asia Pacific (Hong Kong)
558608220178me-south-1 - Middle East (Bahrain)
918309763551cn-north-1 - China (Beijing)
961992271922cn-northwest-1 - China (Ningxia)
+

For further information about using endpoint policies, see Using VPC endpoint policies to control Amazon ECR access.

+

Implement lifecycle policies for ECR

+

The NIST Application Container Security Guide warns about the risk of "stale images in registries", noting that over time old images with vulnerable, out-of-date software packages should be removed to prevent accidental deployment and exposure. +Each ECR repository can have a lifecycle policy that sets rules for when images expire. The AWS official documentation describes how to set up test rules, evaluate them and then apply them. There are several lifecycle policy examples in the official docs that show different ways of filtering the images in a repository:

+
    +
  • Filtering by image age or count
  • +
  • Filtering by tagged or untagged images
  • +
  • Filtering by image tags, either in multiple rules or a single rule
  • +
+
+Warning +

If the image for long running application is purged from ECR, it can cause an image pull errors when the application is redeployed or scaled horizontally. When using image lifecycle policies, be sure you have good CI/CD practices in place to keep deployments and the images that they reference up to date and always create [image] expiry rules that account for how often you do releases/deployments.

+
+

Create a set of curated images

+

Rather than allowing developers to create their own images, consider creating a set of vetted images for the different application stacks in your organization. By doing so, developers can forego learning how to compose Dockerfiles and concentrate on writing code. As changes are merged into Master, a CI/CD pipeline can automatically compile the asset, store it in an artifact repository and copy the artifact into the appropriate image before pushing it to a Docker registry like ECR. At the very least you should create a set of base images from which developers to create their own Dockerfiles. Ideally, you want to avoid pulling images from Dockerhub because 1/ you don't always know what is in the image and 2/ about a fifth of the top 1000 images have vulnerabilities. A list of those images and their vulnerabilities can be found here.

+

Add the USER directive to your Dockerfiles to run as a non-root user

+

As was mentioned in the pod security section, you should avoid running container as root. While you can configure this as part of the podSpec, it is a good habit to use the USER directive to your Dockerfiles. The USER directive sets the UID to use when running RUN, ENTRYPOINT, or CMD instruction that appears after the USER directive.

+

Lint your Dockerfiles

+

Linting can be used to verify that your Dockerfiles are adhering to a set of predefined guidelines, e.g. the inclusion of the USER directive or the requirement that all images be tagged. dockerfile_lint is an open source project from RedHat that verifies common best practices and includes a rule engine that you can use to build your own rules for linting Dockerfiles. It can be incorporated into a CI pipeline, in that builds with Dockerfiles that violate a rule will automatically fail.

+

Build images from Scratch

+

Reducing the attack surface of your container images should be primary aim when building images. The ideal way to do this is by creating minimal images that are devoid of binaries that can be used to exploit vulnerabilities. Fortunately, Docker has a mechanism to create images from scratch. With languages like Go, you can create a static linked binary and reference it in your Dockerfile as in this example:

+
############################
+# STEP 1 build executable binary
+############################
+FROM golang:alpine AS builder# Install git.
+# Git is required for fetching the dependencies.
+RUN apk update && apk add --no-cache gitWORKDIR $GOPATH/src/mypackage/myapp/COPY . . # Fetch dependencies.
+# Using go get.
+RUN go get -d -v# Build the binary.
+RUN go build -o /go/bin/hello
+
+############################
+# STEP 2 build a small image
+############################
+FROM scratch# Copy our static executable.
+COPY --from=builder /go/bin/hello /go/bin/hello# Run the hello binary.
+ENTRYPOINT ["/go/bin/hello"]
+
+

This creates a container image that consists of your application and nothing else, making it extremely secure.

+

Use immutable tags with ECR

+

Immutable tags force you to update the image tag on each push to the image repository. This can thwart an attacker from overwriting an image with a malicious version without changing the image's tags. Additionally, it gives you a way to easily and uniquely identify an image.

+

Sign your images, SBOMs, pipeline runs and vulnerability reports

+

When Docker was first introduced, there was no cryptographic model for verifying container images. With v2, Docker added digests to the image manifest. This allowed an image’s configuration to be hashed and for the hash to be used to generate an ID for the image. When image signing is enabled, the Docker engine verifies the manifest’s signature, ensuring that the content was produced from a trusted source and no tampering has occurred. After each layer is downloaded, the engine verifies the digest of the layer, ensuring that the content matches the content specified in the manifest. Image signing effectively allows you to create a secure supply chain, through the verification of digital signatures associated with the image.

+

We can use AWS Signer or Sigstore Cosign, to sign container images, create attestations for SBOMs, vulnerability scan reports and pipeline run reports. These attestations assure the trustworthiness and integrity of the image, that it is in fact created by the trusted pipeline without any interference or tampering, and that it contains only the software components that are documented (in the SBOM) that is verified and trusted by the image publisher. These attestations can be attached to the container image and pushed to the repository.

+

In the next section we will see how to use the attested artifacts for audits and admissions controller verification.

+

Image integrity verification using Kubernetes admission controller

+

We can verify image signatures, attested artifacts in an automated way before deploying the image to target Kubernetes cluster using dynamic admission controller and admit deployments only when the security metadata of the artifacts comply with the admission controller policies.

+

For example we can write a policy that cryptographically verifies the signature of an image, an attested SBOM, attested pipeline run report, or attested CVE scan report. We can write conditions in the policy to check data in the report, e.g. a CVE scan should not have any critical CVEs. Deployment is allowed only for images that satisfy these conditions and all other deployments will be rejected by the admissions controller.

+

Examples of admission controller include:

+ +

Update the packages in your container images

+

You should include RUN apt-get update && apt-get upgrade in your Dockerfiles to upgrade the packages in your images. Although upgrading requires you to run as root, this occurs during image build phase. The application doesn't need to run as root. You can install the updates and then switch to a different user with the USER directive. If your base image runs as a non-root user, switch to root and back; don't solely rely on the maintainers of the base image to install the latest security updates.

+

Run apt-get clean to delete the installer files from /var/cache/apt/archives/. You can also run rm -rf /var/lib/apt/lists/* after installing packages. This removes the index files or the lists of packages that are available to install. Be aware that these commands may be different for each package manager. For example:

+
RUN apt-get update && apt-get install -y \
+    curl \
+    git \
+    libsqlite3-dev \
+    && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+

Tools and resources

+
    +
  • Amazon EKS Security Immersion Workshop - Image Security
  • +
  • docker-slim Build secure minimal images
  • +
  • dockle Verifies that your Dockerfile aligns with best practices for creating secure images
  • +
  • dockerfile-lint Rule based linter for Dockerfiles
  • +
  • hadolint A smart dockerfile linter
  • +
  • Gatekeeper and OPA A policy based admission controller
  • +
  • Kyverno A Kubernetes-native policy engine
  • +
  • in-toto Allows the user to verify if a step in the supply chain was intended to be performed, and if the step was performed by the right actor
  • +
  • Notary A project for signing container images
  • +
  • Notary v2
  • +
  • Grafeas An open artifact metadata API to audit and govern your software supply chain
  • +
  • NeuVector by SUSE open source, zero-trust container security platform, provides container, image and registry scanning for vulnerabilities, secrets and compliance.
  • +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/images/SRM-EKS.jpg b/security/docs/images/SRM-EKS.jpg new file mode 100644 index 000000000..fd4dc3f75 Binary files /dev/null and b/security/docs/images/SRM-EKS.jpg differ diff --git a/security/docs/images/SRM-MNG.jpg b/security/docs/images/SRM-MNG.jpg new file mode 100644 index 000000000..2c68e4613 Binary files /dev/null and b/security/docs/images/SRM-MNG.jpg differ diff --git a/security/docs/images/allow-dns-access.jpg b/security/docs/images/allow-dns-access.jpg new file mode 100644 index 000000000..343d28bc9 Binary files /dev/null and b/security/docs/images/allow-dns-access.jpg differ diff --git a/security/docs/images/allow-ingress-app-one.png b/security/docs/images/allow-ingress-app-one.png new file mode 100644 index 000000000..0e1e5fa49 Binary files /dev/null and b/security/docs/images/allow-ingress-app-one.png differ diff --git a/security/docs/images/default-deny.jpg b/security/docs/images/default-deny.jpg new file mode 100644 index 000000000..477cb149d Binary files /dev/null and b/security/docs/images/default-deny.jpg differ diff --git a/security/docs/images/default-istio-csr-flow.png b/security/docs/images/default-istio-csr-flow.png new file mode 100644 index 000000000..d937a9c66 Binary files /dev/null and b/security/docs/images/default-istio-csr-flow.png differ diff --git a/security/docs/images/istio-csr-requests.png b/security/docs/images/istio-csr-requests.png new file mode 100644 index 000000000..392541398 Binary files /dev/null and b/security/docs/images/istio-csr-requests.png differ diff --git a/security/docs/images/istio-csr-with-acm-private-ca.png b/security/docs/images/istio-csr-with-acm-private-ca.png new file mode 100644 index 000000000..a7bbfe0bb Binary files /dev/null and b/security/docs/images/istio-csr-with-acm-private-ca.png differ diff --git a/security/docs/images/multi-account-eks-decentralized.png b/security/docs/images/multi-account-eks-decentralized.png new file mode 100644 index 000000000..8c6044be1 Binary files /dev/null and b/security/docs/images/multi-account-eks-decentralized.png differ diff --git a/security/docs/images/multi-account-eks-shared-subnets.png b/security/docs/images/multi-account-eks-shared-subnets.png new file mode 100644 index 000000000..cdcfa2d07 Binary files /dev/null and b/security/docs/images/multi-account-eks-shared-subnets.png differ diff --git a/security/docs/images/multi-account-eks.jpg b/security/docs/images/multi-account-eks.jpg new file mode 100644 index 000000000..2ef82a719 Binary files /dev/null and b/security/docs/images/multi-account-eks.jpg differ diff --git a/security/docs/incidents/index.html b/security/docs/incidents/index.html new file mode 100644 index 000000000..24b01d9e2 --- /dev/null +++ b/security/docs/incidents/index.html @@ -0,0 +1,2536 @@ + + + + + + + + + + + + + + + + + + + + + + + Incident Response and Forensics - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Incident response and forensics

+

Your ability to react quickly to an incident can help minimize damage caused from a breach. Having a reliable alerting system that can warn you of suspicious behavior is the first step in a good incident response plan. When an incident does arise, you have to quickly decide whether to destroy and replace the effected container, or isolate and inspect the container. If you choose to isolate the container as part of a forensic investigation and root cause analysis, then the following set of activities should be followed:

+

Sample incident response plan

+

Identify the offending Pod and worker node

+

Your first course of action should be to isolate the damage. Start by identifying where the breach occurred and isolate that Pod and its node from the rest of the infrastructure.

+

Identify the offending Pods and worker nodes using workload name

+

If you know the name and namespace of the offending pod, you can identify the worker node running the pod as follows:

+
kubectl get pods <name> --namespace <namespace> -o=jsonpath='{.spec.nodeName}{"\n"}'   
+
+

If a Workload Resource such as a Deployment has been compromised, it is likely that all the pods that are part of the workload resource are compromised. Use the following command to list all the pods of the Workload Resource and the nodes they are running on:

+
selector=$(kubectl get deployments <name> \
+ --namespace <namespace> -o json | jq -j \
+'.spec.selector.matchLabels | to_entries | .[] | "\(.key)=\(.value)"')
+
+kubectl get pods --namespace <namespace> --selector=$selector \
+-o json | jq -r '.items[] | "\(.metadata.name) \(.spec.nodeName)"'
+
+

The above command is for deployments. You can run the same command for other workload resources such as replicasets,, statefulsets, etc.

+

Identify the offending Pods and worker nodes using service account name

+

In some cases, you may identify that a service account is compromised. It is likely that pods using the identified service account are compromised. You can identify all the pods using the service account and nodes they are running on with the following command:

+
kubectl get pods -o json --namespace <namespace> | \
+    jq -r '.items[] |
+    select(.spec.serviceAccount == "<service account name>") |
+    "\(.metadata.name) \(.spec.nodeName)"'
+
+

Identify Pods with vulnerable or compromised images and worker nodes

+

In some cases, you may discover that a container image being used in pods on your cluster is malicious or compromised. A container image is malicious or compromised, if it was found to contain malware, is a known bad image or has a CVE that has been exploited. You should consider all the pods using the container image compromised. You can identify the pods using the image and nodes they are running on with the following command:

+
IMAGE=<Name of the malicious/compromised image>
+
+kubectl get pods -o json --all-namespaces | \
+    jq -r --arg image "$IMAGE" '.items[] | 
+    select(.spec.containers[] | .image == $image) | 
+    "\(.metadata.name) \(.metadata.namespace) \(.spec.nodeName)"'
+
+

Isolate the Pod by creating a Network Policy that denies all ingress and egress traffic to the pod

+

A deny all traffic rule may help stop an attack that is already underway by severing all connections to the pod. The following Network Policy will apply to a pod with the label app=web.

+
apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: default-deny
+spec:
+  podSelector:
+    matchLabels: 
+      app: web
+  policyTypes:
+  - Ingress
+  - Egress
+
+
+

Attention

+

A Network Policy may prove ineffective if an attacker has gained access to underlying host. If you suspect that has happened, you can use AWS Security Groups to isolate a compromised host from other hosts. When changing a host's security group, be aware that it will impact all containers running on that host.

+
+

Revoke temporary security credentials assigned to the pod or worker node if necessary

+

If the worker node has been assigned an IAM role that allows Pods to gain access to other AWS resources, remove those roles from the instance to prevent further damage from the attack. Similarly, if the Pod has been assigned an IAM role, evaluate whether you can safely remove the IAM policies from the role without impacting other workloads.

+

Cordon the worker node

+

By cordoning the impacted worker node, you're informing the scheduler to avoid scheduling pods onto the affected node. This will allow you to remove the node for forensic study without disrupting other workloads.

+
+

Info

+

This guidance is not applicable to Fargate where each Fargate pod run in its own sandboxed environment. Instead of cordoning, sequester the affected Fargate pods by applying a network policy that denies all ingress and egress traffic.

+
+

Enable termination protection on impacted worker node

+

An attacker may attempt to erase their misdeeds by terminating an affected node. Enabling termination protection can prevent this from happening. Instance scale-in protection will protect the node from a scale-in event.

+
+

Warning

+

You cannot enable termination protection on a Spot instance.

+
+

Label the offending Pod/Node with a label indicating that it is part of an active investigation

+

This will serve as a warning to cluster administrators not to tamper with the affected Pods/Nodes until the investigation is complete.

+

Capture volatile artifacts on the worker node

+
    +
  • Capture the operating system memory. This will capture the Docker daemon (or other container runtime) and its subprocesses per container. This can be accomplished using tools like LiME and Volatility, or through higher-level tools such as Automated Forensics Orchestrator for Amazon EC2 that build on top of them.
  • +
  • Perform a netstat tree dump of the processes running and the open ports. This will capture the docker daemon and its subprocess per container.
  • +
  • Run commands to save container-level state before evidence is altered. You can use capabilities of the container runtime to capture information about currently running containers. For example, with Docker, you could do the following:
  • +
  • docker top CONTAINER for processes running.
  • +
  • docker logs CONTAINER for daemon level held logs.
  • +
  • +

    docker inspect CONTAINER for various information about the container.

    +

    The same could be achieved with containerd using the nerdctl CLI, in place of docker (e.g. nerdctl inspect). Some additional commands are available depending on the container runtime. For example, Docker has docker diff to see changes to the container filesystem or docker checkpoint to save all container state including volatile memory (RAM). See this Kubernetes blog post for discussion of similar capabilities with containerd or CRI-O runtimes.

    +
  • +
  • +

    Pause the container for forensic capture.

    +
  • +
  • Snapshot the instance's EBS volumes.
  • +
+

Redeploy compromised Pod or Workload Resource

+

Once you have gathered data for forensic analysis, you can redeploy the compromised pod or workload resource.

+

First roll out the fix for the vulnerability that was compromised and start new replacement pods. Then delete the vulnerable pods.

+

If the vulnerable pods are managed by a higher-level Kubernetes workload resource (for example, a Deployment or DaemonSet), deleting them will schedule new ones. So vulnerable pods will be launched again. In that case you should deploy a new replacement workload resource after fixing the vulnerability. Then you should delete the vulnerable workload.

+

Recommendations

+

Review the AWS Security Incident Response Whitepaper

+

While this section gives a brief overview along with a few recommendations for handling suspected security breaches, the topic is exhaustively covered in the white paper, AWS Security Incident Response.

+

Practice security game days

+

Divide your security practitioners into 2 teams: red and blue. The red team will be focused on probing different systems for vulnerabilities while the blue team will be responsible for defending against them. If you don't have enough security practitioners to create separate teams, consider hiring an outside entity that has knowledge of Kubernetes exploits.

+

Kubesploit is a penetration testing framework from CyberArk that you can use to conduct game days. Unlike other tools which scan your cluster for vulnerabilities, kubesploit simulates a real-world attack. This gives your blue team an opportunity to practice its response to an attack and gauge its effectiveness.

+

Run penetration tests against your cluster

+

Periodically attacking your own cluster can help you discover vulnerabilities and misconfigurations. Before getting started, follow the penetration test guidelines before conducting a test against your cluster.

+

Tools and resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/index.html b/security/docs/index.html new file mode 100644 index 000000000..46b74662d --- /dev/null +++ b/security/docs/index.html @@ -0,0 +1,2244 @@ + + + + + + + + + + + + + + + + + + + + + + + Home - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Amazon EKS Best Practices Guide for Security

+

This guide provides advice about protecting information, systems, and assets that are reliant on EKS while delivering business value through risk assessments and mitigation strategies. The guidance herein is part of a series of best practices guides that AWS is publishing to help customers implement EKS in accordance with best practices. Guides for Performance, Operational Excellence, Cost Optimization, and Reliability will be available in the coming months.

+

How to use this guide

+

This guide is meant for security practitioners who are responsible for implementing and monitoring the effectiveness of security controls for EKS clusters and the workloads they support. The guide is organized into different topic areas for easier consumption. Each topic starts with a brief overview, followed by a list of recommendations and best practices for securing your EKS clusters. The topics do not need to be read in a particular order.

+

Understanding the Shared Responsibility Model

+

Security and compliance are considered shared responsibilities when using a managed service like EKS. Generally speaking, AWS is responsible for security "of" the cloud whereas you, the customer, are responsible for security "in" the cloud. With EKS, AWS is responsible for managing of the EKS managed Kubernetes control plane. This includes the Kubernetes control plane nodes, the ETCD database, and other infrastructure necessary for AWS to deliver a secure and reliable service. As a consumer of EKS, you are largely responsible for the topics in this guide, e.g. IAM, pod security, runtime security, network security, and so forth.

+

When it comes to infrastructure security, AWS will assume additional responsibilities as you move from self-managed workers, to managed node groups, to Fargate. For example, with Fargate, AWS becomes responsible for securing the underlying instance/runtime used to run your Pods.

+

Shared Responsibility Model - Fargate

+

AWS will also assume responsibility of keeping the EKS optimized AMI up to date with Kubernetes patch versions and security patches. Customers using Managed Node Groups (MNG) are responsible for upgrading their Nodegroups to the latest AMI via EKS API, CLI, Cloudformation or AWS Console. Also unlike Fargate, MNGs will not automatically scale your infrastructure/cluster. That can be handled by the cluster-autoscaler or other technologies such as Karpenter, native AWS autoscaling, SpotInst's Ocean, or Atlassian's Escalator.

+

Shared Responsibility Model - MNG

+

Before designing your system, it is important to know where the line of demarcation is between your responsibilities and the provider of the service (AWS).

+

For additional information about the shared responsibility model, see https://aws.amazon.com/compliance/shared-responsibility-model/

+

Introduction

+

There are several security best practice areas that are pertinent when using a managed Kubernetes service like EKS:

+
    +
  • Identity and Access Management
  • +
  • Pod Security
  • +
  • Runtime Security
  • +
  • Network Security
  • +
  • Multi-tenancy
  • +
  • Multi Account for Multi-tenancy
  • +
  • Detective Controls
  • +
  • Infrastructure Security
  • +
  • Data Encryption and Secrets Management
  • +
  • Regulatory Compliance
  • +
  • Incident Response and Forensics
  • +
  • Image Security
  • +
+

As part of designing any system, you need to think about its security implications and the practices that can affect your security posture. For example, you need to control who can perform actions against a set of resources. You also need the ability to quickly identify security incidents, protect your systems and services from unauthorized access, and maintain the confidentiality and integrity of data through data protection. Having a well-defined and rehearsed set of processes for responding to security incidents will improve your security posture too. These tools and techniques are important because they support objectives such as preventing financial loss or complying with regulatory obligations.

+

AWS helps organizations achieve their security and compliance goals by offering a rich set of security services that have evolved based on feedback from a broad set of security conscious customers. By offering a highly secure foundation, customers can spend less time on “undifferentiated heavy lifting” and more time on achieving their business objectives.

+

Feedback

+

This guide is being released on GitHub so as to collect direct feedback and suggestions from the broader EKS/Kubernetes community. If you have a best practice that you feel we ought to include in the guide, please file an issue or submit a PR in the GitHub repository. Our intention is to update the guide periodically as new features are added to the service or when a new best practice evolves.

+

Further Reading

+

Kubernetes Security Whitepaper, sponsored by the Security Audit Working Group, this Whitepaper describes key aspects of the Kubernetes attack surface and security architecture with the aim of helping security practitioners make sound design and implementation decisions.

+

The CNCF published also a white paper on cloud native security. The paper examines how the technology landscape has evolved and advocates for the adoption of security practices that align with DevOps processes and agile methodologies.

+

Tools and resources

+

Amazon EKS Security Immersion Workshop

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/multiaccount/index.html b/security/docs/multiaccount/index.html new file mode 100644 index 000000000..f87b86da0 --- /dev/null +++ b/security/docs/multiaccount/index.html @@ -0,0 +1,2591 @@ + + + + + + + + + + + + + + + + + + + + + + + Multi Account Strategy - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Multi Account Strategy

+

AWS recommends using a multi account strategy and AWS organizations to help isolate and manage your business applications and data. There are many benefits to using a multi account strategy:

+
    +
  • Increased AWS API service quotas. Quotas are applied to AWS accounts, and using multiple accounts for your workloads increases the overall quota available to your workloads.
  • +
  • Simpler Identity and Access Management (IAM) policies. Granting workloads and the operators that support them access to only their own AWS accounts means less time crafting fine-grained IAM policies to achieve the principle of least privilege.
  • +
  • Improved Isolation of AWS resources. By design, all resources provisioned within an account are logically isolated from resources provisioned in other accounts. This isolation boundary provides you with a way to limit the risks of an application-related issue, misconfiguration, or malicious actions. If an issue occurs within one account, impacts to workloads contained in other accounts can be either reduced or eliminated.
  • +
  • More benefits, as described in the AWS Multi Account Strategy Whitepaper
  • +
+

The following sections will explain how to implement a multi account strategy for your EKS workloads using either a centralized, or de-centralized EKS cluster approach.

+

Planning for a Multi Workload Account Strategy for Multi Tenant Clusters

+

In a multi account AWS strategy, resources that belong to a given workload such as S3 buckets, ElastiCache clusters and DynamoDB Tables are all created in an AWS account that contains all the resources for that workload. These are referred to as a workload account, and the EKS cluster is deployed into an account referred to as the cluster account. Cluster accounts will be explored in the next section. Deploying resources into a dedicated workload account is similar to deploying kubernetes resources into a dedicated namespace.

+

Workload accounts can then be further broken down by software development lifecycle or other requirements if appropriate. For example a given workload can have a production account, a development account, or accounts for hosting instances of that workload in a specific region. More information is available in this AWS whitepaper.

+

You can adopt the following approaches when implementing EKS Multi account strategy:

+

Centralized EKS Cluster

+

In this approach, your EKS Cluster will be deployed in a single AWS account called the Cluster Account. Using IAM roles for Service Accounts (IRSA) or EKS Pod Identities to deliver temporary AWS credentials and AWS Resource Access Manager (RAM) to simplify network access, you can adopt a multi account strategy for your multi tenant EKS cluster. The cluster account will contain the VPC, subnets, EKS cluster, EC2/Fargate compute resources (worker nodes), and any additional networking configurations needed to run your EKS cluster.

+

In a multi workload account strategy for multi tenant cluster, AWS accounts typically align with kubernetes namespaces as a mechanism for isolating groups of resources. Best practices for tenant isolation within an EKS cluster should still be followed when implementing a multi account strategy for multi tenant EKS clusters.

+

It is possible to have multiple Cluster Accounts in your AWS organization, and it is a best practice to have multiple Cluster Accounts that align with your software development lifecycle needs. For workloads operating at a very large scale, you may require multiple Cluster Accounts to ensure that there are enough kubernetes and AWS service quotas available to all your workloads.

+ + + + + + + + + + + +
multi-account-eks
In the above diagram, AWS RAM is used to share subnets from a cluster account into a workload account. Then workloads running in EKS pods use IRSA or EKS Pod Identities and role chaining to assume a role in their workload account and access their AWS resources.
+

Implementing a Multi Workload Account Strategy for Multi Tenant Cluster

+

Sharing Subnets With AWS Resource Access Manager

+

AWS Resource Access Manager (RAM) allows you to share resources across AWS accounts.

+

If RAM is enabled for your AWS Organization, you can share the VPC Subnets from the Cluster account to your workload accounts. This will allow AWS resources owned by your workload accounts, such as Amazon ElastiCache Clusters or Amazon Relational Database Service (RDS) Databases to be deployed into the same VPC as your EKS cluster, and be consumable by the workloads running on your EKS cluster.

+

To share a resource via RAM, open up RAM in the AWS console of the cluster account and select "Resource Shares" and "Create Resource Share". Name your Resource Share and Select the subnets you want to share. Select Next again and enter the 12 digit account IDs for the workload accounts you wish to share the subnets with, select next again, and click Create resource share to finish. After this step, the workload account can deploy resources into those subnets.

+

RAM shares can also be created programmatically, or with infrastructure as code.

+

Choosing Between EKS Pod Identities and IRSA

+

At re:Invent 2023, AWS launched EKS Pod Identities as a simpler way of delivering temporary AWS credentials to your pods on EKS. Both IRSA and EKS Pod Identities are valid methods for delivering temporary AWS credentials to your EKS pods and will continue to be supported. You should consider which method of delivering best meets your needs.

+

When working with a EKS cluster and multiple AWS accounts, IRSA can directly assume roles in AWS accounts other than the account the EKS cluster is hosted in directly, while EKS Pod identities require you to configure role chaining. Refer EKS documentation for an in-depth comparison.

+
Accessing AWS API Resources with IAM Roles For Service Accounts
+

IAM Roles for Service Accounts (IRSA) allows you to deliver temporary AWS credentials to your workloads running on EKS. IRSA can be used to get temporary credentials for IAM roles in the workload accounts from the cluster account. This allows your workloads running on your EKS clusters in the cluster account to consume AWS API resources, such as S3 buckets hosted in the workload account seemlessly, and use IAM authentication for resources like Amazon RDS Databases or Amazon EFS FileSystems.

+

AWS API resources and other Resources that use IAM authentication in a workload account can only be accessed by credentials for IAM roles in that same workload account, except where cross account access is capable and has been explicity enabled.

+
Enabling IRSA for cross account access
+

To enable IRSA for workloads in your Cluster Account to access resources in your Workload accounts, you first must create an IAM OIDC identity provider in your workload account. This can be done with the same procedure for setting up IRSA, except the Identity Provider will be created in the workload account.

+

Then when configuring IRSA for your workloads on EKS, you can follow the same steps as the documentation, but use the 12 digit account id of the workload account as mentioned in the section "Example Create an identity provider from another account's cluster".

+

After this is configured, your application running in EKS will be able to directly use its service account to assume a role in the workload account, and use resources within it.

+
Accessing AWS API Resources with EKS Pod Identities
+

EKS Pod Identities is a new way of delivering AWS credentials to your workloads running on EKS. EKS pod identities simplifies the configuration of AWS resources as you no longer need to manage OIDC configurations to deliver AWS credentials to your pods on EKS.

+
Enabling EKS Pod Identities for cross account access
+

Unlike IRSA, EKS Pod Identities can only be used to directly grant access to a role in the same account as the EKS cluster. To access a role in another AWS account, pods that use EKS Pod Identities must perform Role Chaining.

+

Role chaining can be configured in an applications profile with their aws configuration file using the Process Credentials Provider available in various AWS SDKs. credential_process can be used as a credential source when configuring a profile, such as:

+
# Content of the AWS Config file
+[profile account_b_role] 
+source_profile = account_a_role 
+role_arn = arn:aws:iam::444455556666:role/account-b-role
+
+[profile account_a_role] 
+credential_process = /eks-credential-processrole.sh
+
+

The source of the script called by credential_process:

+
#!/bin/bash
+# Content of the eks-credential-processrole.sh
+# This will retreive the credential from the pod identities agent,
+# and return it to the AWS SDK when referenced in a profile
+curl -H "Authorization: $(cat $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)" $AWS_CONTAINER_CREDENTIALS_FULL_URI | jq -c '{AccessKeyId: .AccessKeyId, SecretAccessKey: .SecretAccessKey, SessionToken: .Token, Expiration: .Expiration, Version: 1}' 
+
+

You can create an aws config file as shown above with both Account A and B roles and specify the AWS_CONFIG_FILE and AWS_PROFILE env vars in your pod spec. EKS Pod identity webhook does not override if the env vars already exists in the pod spec.

+
# Snippet of the PodSpec
+containers: 
+  - name: container-name
+    image: container-image:version
+    env:
+    - name: AWS_CONFIG_FILE
+      value: path-to-customer-provided-aws-config-file
+    - name: AWS_PROFILE
+      value: account_b_role
+
+

When configuring role trust policies for role chaining with EKS pod identities, you can reference EKS specific attributes as session tags and use attribute based access control(ABAC) to limit access to your IAM roles to only specific EKS Pod identity sessions, such as the Kubernetes Service Account a pod belongs to.

+

Please note that some of these attributes may not be universally unique, for example two EKS clusters may have identical namespaces, and one cluster may have identically named service accounts across namespaces. So when granting access via EKS Pod Identities and ABAC, it is a best practice to always consider the cluster arn and namespace when granting access to a service account.

+
ABAC and EKS Pod Identities for cross account access
+

When using EKS Pod Identities to assume roles (role chaining) in other accounts as part of a multi account strategy, you have the option to assign a unique IAM role for each service account that needs to access another account, or use a common IAM role across multiple service accounts and use ABAC to control what accounts it can access.

+

To use ABAC to control what service accounts can assume a role into another account with role chaining, you create a role trust policy statement that only allows a role to be assumed by a role session when the expected values are present. The following role trust policy will only let a role from the EKS cluster account (account ID 111122223333) assume a role if the kubernetes-service-account, eks-cluster-arn and kubernetes-namespace tags all have the expected value.

+
{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Principal": {
+                "AWS": "arn:aws:iam::111122223333:root"
+            },
+            "Action": "sts:AssumeRole",
+            "Condition": {
+                "StringEquals": {
+                    "aws:PrincipalTag/kubernetes-service-account": "PayrollApplication",
+                    "aws:PrincipalTag/eks-cluster-arn": "arn:aws:eks:us-east-1:111122223333:cluster/ProductionCluster",
+                    "aws:PrincipalTag/kubernetes-namespace": "PayrollNamespace"
+                }
+            }
+        }
+    ]
+}
+
+

When using this strategy it is a best practice to ensure that the common IAM role only has sts:AssumeRole permissions and no other AWS access.

+

It is important when using ABAC that you control who has the ability to tag IAM roles and users to only those who have a strict need to do so. Someone with the ability to tag an IAM role or user would be able to set tags on roles/users identical to what would be set by EKS Pod Identities and may be able to escalate their privileges. You can restrict who has the access to set tags the kubernetes- and eks- tags on IAM role and users using IAM policy, or Service Control Policy (SCP).

+

De-centralized EKS Clusters

+

In this approach, EKS clusters are deployed to respective workload AWS Accounts and live along side with other AWS resources like Amazon S3 buckets, VPCs, Amazon DynamoDB tables, etc., Each workload account is independent, self-sufficient, and operated by respective Business Unit/Application teams. This model allows the creation of reusuable blueprints for various cluster capabilities (AI/ML cluster, Batch processing, General purpose, etc.,) and vend the clusters based on the application team requirements. Both application and platform teams operate out of their respective GitOps repositories to manage the deployments to the workload clusters.

+ + + + + + + + + + + +
De-centralized EKS Cluster Architecture
In the above diagram, Amazon EKS clusters and other AWS resources are deployed to respective workload accounts. Then workloads running in EKS pods use IRSA or EKS Pod Identities to access their AWS resources.
+

GitOps is a way of managing application and infrastructure deployment so that the whole system is described declaratively in a Git repository. It’s an operational model that offers you the ability to manage the state of multiple Kubernetes clusters using the best practices of version control, immutable artifacts, and automation. In this multi cluster model, each workload cluster is bootstrapped with multiple Git repos, allowing each team (application, platform, security, etc.,) to deploy their respective changes on the cluster.

+

You would utilize IAM roles for Service Accounts (IRSA) or EKS Pod Identities in each account to allow your EKS workloads to get temporary aws credentials to securely access other AWS resources. IAM roles are created in respective workload AWS Accounts and map them to k8s service accounts to provide temporary IAM access. So, no cross-account access is required in this approach. Follow the IAM roles for Service Accounts documentation on how to setup in each workload for IRSA, and EKS Pod Identities documentation on how to setup EKS pod identities in each account.

+

Centralized Networking

+

You can also utilize AWS RAM to share the VPC Subnets to workload accounts and launch Amazon EKS clusters and other AWS resources in them. This enables centralized network managment/administration, simplified network connectivity, and de-centralized EKS clusters. Refer this AWS blog for a detailed walkthrough and considerations of this approach.

+ + + + + + + + + + + +
De-centralized EKS Cluster Architecture using VPC Shared Subnets
In the above diagram, AWS RAM is used to share subnets from a central networking account into a workload account. Then EKS cluster and other AWS resources are launched in those subnets in respective workload accounts. EKS pods use IRSA or EKS Pod Identities to access their AWS resources.
+

Centralized vs De-centralized EKS clusters

+

The decision to run with a Centralized or De-centralized will depend on your requirements. This table demonstrates the key differences with each strategy.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#Centralized EKS clusterDe-centralized EKS clusters
Cluster Management:Managing a single EKS cluster is easier than administrating multiple clustersAn Efficient cluster management automation is necessary to reduce the operational overhead of managing multiple EKS clusters
Cost Efficiency:Allows reuse of EKS cluster and network resources, which promotes cost efficiencyRequires networking and cluster setups per workload, which requires additional resources
Resilience:Multiple workloads on the centralized cluster may be impacted if a cluster becomes impairedIf a cluster becomes impaired, the damage is limited to only the workloads that run on that cluster. All other workloads are unaffected
Isolation & Security:Isolation/Soft Multi-tenancy is achieved using k8s native constructs like Namespaces. Workloads may share the underlying resources like CPU, memory, etc. AWS resources are isolated into their own workload accounts which by default are not accessible from other AWS accounts.Stronger isolation on compute resources as the workloads run in individual clusters and nodes that don't share any resources. AWS resources are isolated into their own workload accounts which by default are not accessible from other AWS accounts.
Performance & Scalabity:As workloads grow to very large scales you may encounter kubernetes and AWS service quotas in the cluster account. You can deploy addtional cluster accounts to scale even furtherAs more clusters and VPCs are present, each workload has more available k8s and AWS service quota
Networking:Single VPC is used per cluster, allowing for simpler connectivity for applications on that clusterRouting must be established between the de-centralized EKS cluster VPCs
Kubernetes Access Management:Need to maintain many different roles and users in the cluster to provide access to all workload teams and ensure kubernetes resources are properly segregatedSimplified access management as each cluster is dedicated to a workload/team
AWS Access Management:AWS resources are deployed into to their own account which can only be accessed by default with IAM roles in the workload account. IAM roles in the workload accounts are assumed cross account either with IRSA or EKS Pod Identities.AWS resources are deployed into to their own account which can only be accessed by default with IAM roles in the workload account. IAM roles in the workload accounts are delivered directly to pods with IRSA or EKS Pod Identities
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/multitenancy/index.html b/security/docs/multitenancy/index.html new file mode 100644 index 000000000..41ffb5051 --- /dev/null +++ b/security/docs/multitenancy/index.html @@ -0,0 +1,2802 @@ + + + + + + + + + + + + + + + + + + + + + + + Multi-tenancy - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Tenant Isolation

+

When we think of multi-tenancy, we often want to isolate a user or application from other users or applications running on a shared infrastructure.

+

Kubernetes is a single tenant orchestrator, i.e. a single instance of the control plane is shared among all the tenants within a cluster. There are, however, various Kubernetes objects that you can use to create the semblance of multi-tenancy. For example, Namespaces and Role-based access controls (RBAC) can be implemented to logically isolate tenants from each other. Similarly, Quotas and Limit Ranges can be used to control the amount of cluster resources each tenant can consume. Nevertheless, the cluster is the only construct that provides a strong security boundary. This is because an attacker that manages to gain access to a host within the cluster can retrieve all Secrets, ConfigMaps, and Volumes, mounted on that host. They could also impersonate the Kubelet which would allow them to manipulate the attributes of the node and/or move laterally within the cluster.

+

The following sections will explain how to implement tenant isolation while mitigating the risks of using a single tenant orchestrator like Kubernetes.

+

Soft multi-tenancy

+

With soft multi-tenancy, you use native Kubernetes constructs, e.g. namespaces, roles and role bindings, and network policies, to create logical separation between tenants. RBAC, for example, can prevent tenants from accessing or manipulate each other's resources. Quotas and limit ranges control the amount of cluster resources each tenant can consume while network policies can help prevent applications deployed into different namespaces from communicating with each other.

+

None of these controls, however, prevent pods from different tenants from sharing a node. If stronger isolation is required, you can use a node selector, anti-affinity rules, and/or taints and tolerations to force pods from different tenants to be scheduled onto separate nodes; often referred to as sole tenant nodes. This could get rather complicated, and cost prohibitive, in an environment with many tenants.

+
+

Attention

+

Soft multi-tenancy implemented with Namespaces does not allow you to provide tenants with a filtered list of Namespaces because Namespaces are a globally scoped Type. If a tenant has the ability to view a particular Namespace, it can view all Namespaces within the cluster.

+
+
+

Warning

+

With soft-multi-tenancy, tenants retain the ability to query CoreDNS for all services that run within the cluster by default. An attacker could exploit this by running dig SRV *.*.svc.cluster.local from any pod in the cluster. If you need to restrict access to DNS records of services that run within your clusters, consider using the Firewall or Policy plugins for CoreDNS. For additional information, see https://github.com/coredns/policy#kubernetes-metadata-multi-tenancy-policy.

+
+

Kiosk is an open source project that can aid in the implementation of soft multi-tenancy. It is implemented as a series of CRDs and controllers that provide the following capabilities:

+
    +
  • Accounts & Account Users to separate tenants in a shared Kubernetes cluster
  • +
  • Self-Service Namespace Provisioning for account users
  • +
  • Account Limits to ensure quality of service and fairness when sharing a cluster
  • +
  • Namespace Templates for secure tenant isolation and self-service namespace initialization
  • +
+

Loft is a commercial offering from the maintainers of Kiosk and DevSpace that adds the following capabilities:

+
    +
  • Multi-cluster access for granting access to spaces in different clusters
  • +
  • Sleep mode scales down deployments in a space during periods of inactivity
  • +
  • Single sign-on with OIDC authentication providers like GitHub
  • +
+

There are three primary use cases that can be addressed by soft multi-tenancy.

+

Enterprise Setting

+

The first is in an Enterprise setting where the "tenants" are semi-trusted in that they are employees, contractors, or are otherwise authorized by the organization. Each tenant will typically align to an administrative division such as a department or team.

+

In this type of setting, a cluster administrator will usually be responsible for creating namespaces and managing policies. They may also implement a delegated administration model where certain individuals are given oversight of a namespace, allowing them to perform CRUD operations for non-policy related objects like deployments, services, pods, jobs, etc.

+

The isolation provided by a container runtime may be acceptable within this setting or it may need to be augmented with additional controls for pod security. It may also be necessary to restrict communication between services in different namespaces if stricter isolation is required.

+

Kubernetes as a Service

+

By contrast, soft multi-tenancy can be used in settings where you want to offer Kubernetes as a service (KaaS). With KaaS, your application is hosted in a shared cluster along with a collection of controllers and CRDs that provide a set of PaaS services. Tenants interact directly with the Kubernetes API server and are permitted to perform CRUD operations on non-policy objects. There is also an element of self-service in that tenants may be allowed to create and manage their own namespaces. In this type of environment, tenants are assumed to be running untrusted code.

+

To isolate tenants in this type of environment, you will likely need to implement strict network policies as well as pod sandboxing. Sandboxing is where you run the containers of a pod inside a micro VM like Firecracker or in a user-space kernel. Today, you can create sandboxed pods with EKS Fargate.

+

Software as a Service (SaaS)

+

The final use case for soft multi-tenancy is in a Software-as-a-Service (SaaS) setting. In this environment, each tenant is associated with a particular instance of an application that's running within the cluster. Each instance often has its own data and uses separate access controls that are usually independent of Kubernetes RBAC.

+

Unlike the other use cases, the tenant in a SaaS setting does not directly interface with the Kubernetes API. Instead, the SaaS application is responsible for interfacing with the Kubernetes API to create the necessary objects to support each tenant.

+

Kubernetes Constructs

+

In each of these instances the following constructs are used to isolate tenants from each other:

+

Namespaces

+

Namespaces are fundamental to implementing soft multi-tenancy. They allow you to divide the cluster into logical partitions. Quotas, network policies, service accounts, and other objects needed to implement multi-tenancy are scoped to a namespace.

+

Network policies

+

By default, all pods in a Kubernetes cluster are allowed to communicate with each other. This behavior can be altered using network policies.

+

Network policies restrict communication between pods using labels or IP address ranges. In a multi-tenant environment where strict network isolation between tenants is required, we recommend starting with a default rule that denies communication between pods, and another rule that allows all pods to query the DNS server for name resolution. With that in place, you can begin adding more permissive rules that allow for communication within a namespace. This can be further refined as required.

+
+

Note

+

Amazon VPC CNI now supports Kubernetes Network Policies to create policies that can isolate sensitive workloads and protect them from unauthorized access when running Kubernetes on AWS. This means that you can use all the capabilities of the Network Policy API within your Amazon EKS cluster. This level of granular control enables you to implement the principle of least privilege, which ensures that only authorized pods are allowed to communicate with each other.

+
+
+

Attention

+

Network policies are necessary but not sufficient. The enforcement of network policies requires a policy engine such as Calico or Cilium.

+
+

Role-based access control (RBAC)

+

Roles and role bindings are the Kubernetes objects used to enforce role-based access control (RBAC) in Kubernetes. Roles contain lists of actions that can be performed against objects in your cluster. Role bindings specify the individuals or groups to whom the roles apply. In the enterprise and KaaS settings, RBAC can be used to permit administration of objects by selected groups or individuals.

+

Quotas

+

Quotas are used to define limits on workloads hosted in your cluster. With quotas, you can specify the maximum amount of CPU and memory that a pod can consume, or you can limit the number of resources that can be allocated in a cluster or namespace. Limit ranges allow you to declare minimum, maximum, and default values for each limit.

+

Overcommitting resources in a shared cluster is often beneficial because it allows you maximize your resources. However, unbounded access to a cluster can cause resource starvation, which can lead to performance degradation and loss of application availability. If a pod's requests are set too low and the actual resource utilization exceeds the capacity of the node, the node will begin to experience CPU or memory pressure. When this happens, pods may be restarted and/or evicted from the node.

+

To prevent this from happening, you should plan to impose quotas on namespaces in a multi-tenant environment to force tenants to specify requests and limits when scheduling their pods on the cluster. It will also mitigate a potential denial of service by constraining the amount of resources a pod can consume.

+

You can also use quotas to apportion the cluster's resources to align with a tenant's spend. This is particularly useful in the KaaS scenario.

+

Pod priority and preemption

+

Pod priority and preemption can be useful when you want to provide more importance to a Pod relative to other Pods. For example, with pod priority you can configure pods from customer A to run at a higher priority than customer B. When there's insufficient capacity available, the scheduler will evict the lower-priority pods from customer B to accommodate the higher-priority pods from customer A. This can be especially handy in a SaaS environment where customers willing to pay a premium receive a higher priority.

+
+

Attention

+

Pods priority can have an undesired effect on other Pods with lower priority. For example, although the victim pods are terminated gracefully but the PodDisruptionBudget is not guaranteed, which could break a application with lower priority that relies on a quorum of Pods, see Limitations of preemption.

+
+

Mitigating controls

+

Your chief concern as an administrator of a multi-tenant environment is preventing an attacker from gaining access to the underlying host. The following controls should be considered to mitigate this risk:

+

Sandboxed execution environments for containers

+

Sandboxing is a technique by which each container is run in its own isolated virtual machine. Technologies that perform pod sandboxing include Firecracker and Weave's Firekube.

+

For additional information about the effort to make Firecracker a supported runtime for EKS, see +https://threadreaderapp.com/thread/1238496944684597248.html.

+

Open Policy Agent (OPA) & Gatekeeper

+

Gatekeeper is a Kubernetes admission controller that enforces policies created with OPA. With OPA you can create a policy that runs pods from tenants on separate instances or at a higher priority than other tenants. A collection of common OPA policies can be found in the GitHub repository for this project.

+

There is also an experimental OPA plugin for CoreDNS that allows you to use OPA to filter/control the records returned by CoreDNS.

+

Kyverno

+

Kyverno is a Kubernetes native policy engine that can validate, mutate, and generate configurations with policies as Kubernetes resources. Kyverno uses Kustomize-style overlays for validation, supports JSON Patch and strategic merge patch for mutation, and can clone resources across namespaces based on flexible triggers.

+

You can use Kyverno to isolate namespaces, enforce pod security and other best practices, and generate default configurations such as network policies. Several examples are included in the GitHub repository for this project. Many others are included in the policy library on the Kyverno website.

+

Isolating tenant workloads to specific nodes

+

Restricting tenant workloads to run on specific nodes can be used to increase isolation in the soft multi-tenancy model. With this approach, tenant-specific workloads are only run on nodes provisioned for the respective tenants. To achieve this isolation, native Kubernetes properties (node affinity, and taints and tolerations) are used to target specific nodes for pod scheduling, and prevent pods, from other tenants, from being scheduled on the tenant-specific nodes.

+

Part 1 - Node affinity

+

Kubernetes node affinity is used to target nodes for scheduling, based on node labels. With node affinity rules, the pods are attracted to specific nodes that match the selector terms. In the below pod specification, the requiredDuringSchedulingIgnoredDuringExecution node affinity is applied to the respective pod. The result is that the pod will target nodes that are labeled with the following key/value: node-restriction.kubernetes.io/tenant: tenants-x.

+
...
+spec:
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: node-restriction.kubernetes.io/tenant
+            operator: In
+            values:
+            - tenants-x
+...
+
+

With this node affinity, the label is required during scheduling, but not during execution; if the underlying nodes' labels change, the pods will not be evicted due solely to that label change. However, future scheduling could be impacted.

+
+

Warning

+

The label prefix of node-restriction.kubernetes.io/ has special meaning in Kubernetes. NodeRestriction which is enabled for EKS clusters prevents kubelet from adding/removing/updating labels with this prefix. Attackers aren't able to use the kubelet's credentials to update the node object or modify the system setup to pass these labels into kubelet as kubelet isn't allowed to modify these labels. If this prefix is used for all pod to node scheduling, it prevents scenarios where an attacker may want to attract a different set of workloads to a node by modifying the node labels.

+
+
+

Info

+

Instead of node affinity, we could have used the node selector. However, node affinity is more expressive and allows for more conditions to be considered during pod scheduling. For additional information about the differences and more advanced scheduling choices, please see this CNCF blog post on Advanced Kubernetes pod to node scheduling.

+
+

Part 2 - Taints and tolerations

+

Attracting pods to nodes is just the first part of this three-part approach. For this approach to work, we must repel pods from scheduling onto nodes for which the pods are not authorized. To repel unwanted or unauthorized pods, Kubernetes uses node taints. Taints are used to place conditions on nodes that prevent pods from being scheduled. The below taint uses a key-value pair of tenant: tenants-x.

+
...
+    taints:
+      - key: tenant
+        value: tenants-x
+        effect: NoSchedule
+...
+
+

Given the above node taint, only pods that tolerate the taint will be allowed to be scheduled on the node. To allow authorized pods to be scheduled onto the node, the respective pod specifications must include a toleration to the taint, as seen below.

+
...
+  tolerations:
+  - effect: NoSchedule
+    key: tenant
+    operator: Equal
+    value: tenants-x
+...
+
+

Pods with the above toleration will not be stopped from scheduling on the node, at least not because of that specific taint. Taints are also used by Kubernetes to temporarily stop pod scheduling during certain conditions, like node resource pressure. With node affinity, and taints and tolerations, we can effectively attract the desired pods to specific nodes and repel unwanted pods.

+
+

Attention

+

Certain Kubernetes pods are required to run on all nodes. Examples of these pods are those started by the Container Network Interface (CNI) and kube-proxy daemonsets. To that end, the specifications for these pods contain very permissive tolerations, to tolerate different taints. Care should be taken to not change these tolerations. Changing these tolerations could result in incorrect cluster operation. Additionally, policy-management tools, such as OPA/Gatekeeper and Kyverno can be used to write validating policies that prevent unauthorized pods from using these permissive tolerations.

+
+

Part 3 - Policy-based management for node selection

+

There are several tools that can be used to help manage the node affinity and tolerations of pod specifications, including enforcement of rules in CICD pipelines. However, enforcement of isolation should also be done at the Kubernetes cluster level. For this purpose, policy-management tools can be used to mutate inbound Kubernetes API server requests, based on request payloads, to apply the respective node affinity rules and tolerations mentioned above.

+

For example, pods destined for the tenants-x namespace can be stamped with the correct node affinity and toleration to permit scheduling on the tenants-x nodes. Utilizing policy-management tools configured using the Kubernetes Mutating Admission Webhook, policies can be used to mutate the inbound pod specifications. The mutations add the needed elements to allow desired scheduling. An example OPA/Gatekeeper policy that adds a node affinity is seen below.

+
apiVersion: mutations.gatekeeper.sh/v1alpha1
+kind: Assign
+metadata:
+  name: mutator-add-nodeaffinity-pod
+  annotations:
+    aws-eks-best-practices/description: >-
+      Adds Node affinity - https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
+spec:
+  applyTo:
+  - groups: [""]
+    kinds: ["Pod"]
+    versions: ["v1"]
+  match:
+    namespaces: ["tenants-x"]
+  location: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms"
+  parameters:
+    assign:
+      value: 
+        - matchExpressions:
+          - key: "tenant"
+            operator: In
+            values:
+            - "tenants-x"
+
+

The above policy is applied to a Kubernetes API server request, to apply a pod to the tenants-x namespace. The policy adds the requiredDuringSchedulingIgnoredDuringExecution node affinity rule, so that pods are attracted to nodes with the tenant: tenants-x label.

+

A second policy, seen below, adds the toleration to the same pod specification, using the same matching criteria of target namespace and groups, kinds, and versions.

+
apiVersion: mutations.gatekeeper.sh/v1alpha1
+kind: Assign
+metadata:
+  name: mutator-add-toleration-pod
+  annotations:
+    aws-eks-best-practices/description: >-
+      Adds toleration - https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
+spec:
+  applyTo:
+  - groups: [""]
+    kinds: ["Pod"]
+    versions: ["v1"]
+  match:
+    namespaces: ["tenants-x"]
+  location: "spec.tolerations"
+  parameters:
+    assign:
+      value: 
+      - key: "tenant"
+        operator: "Equal"
+        value: "tenants-x"
+        effect: "NoSchedule"
+
+

The above policies are specific to pods; this is due to the paths to the mutated elements in the policies' location elements. Additional policies could be written to handle resources that create pods, like Deployment and Job resources. The listed policies and other examples can been seen in the companion GitHub project for this guide.

+

The result of these two mutations is that pods are attracted to the desired node, while at the same time, not repelled by the specific node taint. To verify this, we can see the snippets of output from two kubectl calls to get the nodes labeled with tenant=tenants-x, and get the pods in the tenants-x namespace.

+
kubectl get nodes -l tenant=tenants-x
+NAME                                        
+ip-10-0-11-255...
+ip-10-0-28-81...
+ip-10-0-43-107...
+
+kubectl -n tenants-x get pods -owide
+NAME                                  READY   STATUS    RESTARTS   AGE   IP            NODE
+tenant-test-deploy-58b895ff87-2q7xw   1/1     Running   0          13s   10.0.42.143   ip-10-0-43-107...
+tenant-test-deploy-58b895ff87-9b6hg   1/1     Running   0          13s   10.0.18.145   ip-10-0-28-81...
+tenant-test-deploy-58b895ff87-nxvw5   1/1     Running   0          13s   10.0.30.117   ip-10-0-28-81...
+tenant-test-deploy-58b895ff87-vw796   1/1     Running   0          13s   10.0.3.113    ip-10-0-11-255...
+tenant-test-pod                       1/1     Running   0          13s   10.0.35.83    ip-10-0-43-107...
+
+

As we can see from the above outputs, all the pods are scheduled on the nodes labeled with tenant=tenants-x. Simply put, the pods will only run on the desired nodes, and the other pods (without the required affinity and tolerations) will not. The tenant workloads are effectively isolated.

+

An example mutated pod specification is seen below.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: tenant-test-pod
+  namespace: tenants-x
+spec:
+  affinity:
+    nodeAffinity:
+      requiredDuringSchedulingIgnoredDuringExecution:
+        nodeSelectorTerms:
+        - matchExpressions:
+          - key: tenant
+            operator: In
+            values:
+            - tenants-x
+...
+  tolerations:
+  - effect: NoSchedule
+    key: tenant
+    operator: Equal
+    value: tenants-x
+...
+
+
+

Attention

+

Policy-management tools that are integrated to the Kubernetes API server request flow, using mutating and validating admission webhooks, are designed to respond to the API server's request within a specified timeframe. This is usually 3 seconds or less. If the webhook call fails to return a response within the configured time, the mutation and/or validation of the inbound API sever request may or may not occur. This behavior is based on whether the admission webhook configurations are set to Fail Open or Fail Close.

+
+

In the above examples, we used policies written for OPA/Gatekeeper. However, there are other policy management tools that handle our node-selection use case as well. For example, this Kyverno policy could be used to handle the node affinity mutation.

+
+

Tip

+

If operating correctly, mutating policies will effect the desired changes to inbound API server request payloads. However, validating policies should also be included to verify that the desired changes occur, before changes are allowed to persist. This is especially important when using these policies for tenant-to-node isolation. It is also a good idea to include Audit policies to routinely check your cluster for unwanted configurations.

+
+

References

+ +

Hard multi-tenancy

+

Hard multi-tenancy can be implemented by provisioning separate clusters for each tenant. While this provides very strong isolation between tenants, it has several drawbacks.

+

First, when you have many tenants, this approach can quickly become expensive. Not only will you have to pay for the control plane costs for each cluster, you will not be able to share compute resources between clusters. This will eventually cause fragmentation where a subset of your clusters are underutilized while others are overutilized.

+

Second, you will likely need to buy or build special tooling to manage all of these clusters. In time, managing hundreds or thousands of clusters may simply become too unwieldy.

+

Finally, creating a cluster per tenant will be slow relative to a creating a namespace. Nevertheless, a hard-tenancy approach may be necessary in highly-regulated industries or in SaaS environments where strong isolation is required.

+

Future directions

+

The Kubernetes community has recognized the current shortcomings of soft multi-tenancy and the challenges with hard multi-tenancy. The Multi-Tenancy Special Interest Group (SIG) is attempting to address these shortcomings through several incubation projects, including Hierarchical Namespace Controller (HNC) and Virtual Cluster.

+

The HNC proposal (KEP) describes a way to create parent-child relationships between namespaces with [policy] object inheritance along with an ability for tenant administrators to create sub-namespaces.

+

The Virtual Cluster proposal describes a mechanism for creating separate instances of the control plane services, including the API server, the controller manager, and scheduler, for each tenant within the cluster (also known as "Kubernetes on Kubernetes").

+

The Multi-Tenancy Benchmarks proposal provides guidelines for sharing clusters using namespaces for isolation and segmentation, and a command line tool kubectl-mtb to validate conformance to the guidelines.

+

Multi-cluster management tools and resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/network/index.html b/security/docs/network/index.html new file mode 100644 index 000000000..1a05f6bff --- /dev/null +++ b/security/docs/network/index.html @@ -0,0 +1,3380 @@ + + + + + + + + + + + + + + + + + + + + + + + Network Security - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Network security

+

Network security has several facets. The first involves the application of rules which restrict the flow of network traffic between services. The second involves the encryption of traffic while it is in transit. The mechanisms to implement these security measures on EKS are varied but often include the following items:

+

Traffic control

+
    +
  • Network Policies
  • +
  • Security Groups
  • +
+

Network encryption

+
    +
  • Service Mesh
  • +
  • Container Network Interfaces (CNIs)
  • +
  • Ingress Controllers and Load Balancers
  • +
  • Nitro Instances
  • +
  • ACM Private CA with cert-manager
  • +
+

Network policy

+

Within a Kubernetes cluster, all Pod to Pod communication is allowed by default. While this flexibility may help promote experimentation, it is not considered secure. Kubernetes network policies give you a mechanism to restrict network traffic between Pods (often referred to as East/West traffic) as well as between Pods and external services. Kubernetes network policies operate at layers 3 and 4 of the OSI model. Network policies use pod, namespace selectors and labels to identify source and destination pods, but can also include IP addresses, port numbers, protocols, or a combination of these. Network Policies can be applied to both Inbound or Outbound connections to the pod, often called Ingress and Egress rules.

+

With native network policy support of Amazon VPC CNI Plugin, you can implement network policies to secure network traffic in kubernetes clusters. This integrates with the upstream Kubernetes Network Policy API, ensuring compatibility and adherence to Kubernetes standards. You can define policies using different identifiers supported by the upstream API. By default, all ingress and egress traffic is allowed to a pod. When a network policy with a policyType Ingress is specified, only allowed connections into the pod are those from the pod's node and those allowed by the ingress rules. Same applies for egress rules. If multiple rules are defined, then union of all rules are taken into account when making the decision. Thus, order of evaluation does not affect the policy result.

+
+

Attention

+

When you first provision an EKS cluster, VPC CNI Network Policy functionality is not enabled by default. Ensure you deployed supported VPC CNI Add-on version and set ENABLE_NETWORK_POLICY flag to true on the vpc-cni add-on to enable this. Refer Amazon EKS User guide for detailed instructions.

+
+

Recommendations

+

Getting Started with Network Policies - Follow Principle of Least Privilege

+

Create a default deny policy

+

As with RBAC policies, it is recommended to follow least privileged access principles with network policies. Start by creating a deny all policy that restricts all inbound and outbound traffic with in a namespace.

+
apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: default-deny
+  namespace: default
+spec:
+  podSelector: {}
+  policyTypes:
+  - Ingress
+  - Egress
+
+

default-deny

+
+

Tip

+

The image above was created by the network policy viewer from Tufin.

+
+

Create a rule to allow DNS queries

+

Once you have the default deny all rule in place, you can begin layering on additional rules, such as a rule that allows pods to query CoreDNS for name resolution.

+
apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: allow-dns-access
+  namespace: default
+spec:
+  podSelector:
+    matchLabels: {}
+  policyTypes:
+  - Egress
+  egress:
+  - to:
+    - namespaceSelector:
+        matchLabels:
+          kubernetes.io/metadata.name: kube-system
+      podSelector:
+        matchLabels:
+          k8s-app: kube-dns
+    ports:
+    - protocol: UDP
+      port: 53
+
+

allow-dns-access

+

Incrementally add rules to selectively allow the flow of traffic between namespaces/pods

+

Understand the application requirements and create fine-grained ingress and egress rules as needed. Below example shows how to restrict ingress traffic on port 80 to app-one from client-one. This helps minimize the attack surface and reduces the risk of unauthorized access.

+
apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: allow-ingress-app-one
+  namespace: default
+spec:
+  podSelector:
+    matchLabels:
+      k8s-app: app-one
+  policyTypes:
+  - Ingress
+  ingress:
+  - from:
+    - podSelector:
+        matchLabels:
+          k8s-app: client-one
+    ports:
+    - protocol: TCP
+      port: 80
+
+

allow-ingress-app-one

+

Monitoring network policy enforcement

+
    +
  • Use Network Policy editor
  • +
  • Network policy editor helps with visualizations, security score, autogenerates from network flow logs
  • +
  • Build network policies in an interactive way
  • +
  • Audit Logs
  • +
  • Regularly review audit logs of your EKS cluster
  • +
  • Audit logs provide wealth of information about what actions have been performed on your cluster including changes to network policies
  • +
  • Use this information to track changes to your network policies over time and detect any unauthorized or unexpected changes
  • +
  • Automated testing
  • +
  • Implement automated testing by creating a test environment that mirrors your production environment and periodically deploy workloads that attempt to violate your network policies.
  • +
  • Monitoring metrics
  • +
  • Configure your observability agents to scrape the prometheus metrics from the VPC CNI node agents, that allows to monitor the agent health, and sdk errors.
  • +
  • Audit Network Policies regularly
  • +
  • Periodically audit your Network Policies to make sure that they meet your current application requirements. As your application evolves, an audit gives you the opportunity to remove redundant ingress, egress rules and make sure that your applications don’t have excessive permissions.
  • +
  • Ensure Network Policies exists using Open Policy Agent (OPA)
  • +
  • Use OPA Policy like shown below to ensure Network Policy always exists before onboarding application pods. This policy denies onboarding k8s pods with a label k8s-app: sample-app if corresponding network policy does not exist.
  • +
+
package kubernetes.admission
+import data.kubernetes.networkpolicies
+
+deny[msg] {
+    input.request.kind.kind == "Pod"
+    pod_label_value := {v["k8s-app"] | v := input.request.object.metadata.labels}
+    contains_label(pod_label_value, "sample-app")
+    np_label_value := {v["k8s-app"] | v := networkpolicies[_].spec.podSelector.matchLabels}
+    not contains_label(np_label_value, "sample-app")
+    msg:= sprintf("The Pod %v could not be created because it is missing an associated Network Policy.", [input.request.object.metadata.name])
+}
+contains_label(arr, val) {
+    arr[_] == val
+}
+
+

Troubleshooting

+

Monitor the vpc-network-policy-controller, node-agent logs

+

Enable the EKS Control plane controller manager logs to diagnose the network policy functionality. You can stream the control plane logs to a CloudWatch log group and use CloudWatch Log insights to perform advanced queries. From the logs, you can view what pod endpoint objects are resolved to a Network Policy, reconcilation status of the policies, and debug if the policy is working as expected.

+

In addition, Amazon VPC CNI allows you to enable the collection and export of policy enforcement logs to Amazon Cloudwatch from the EKS worker nodes. Once enabled, you can leverage CloudWatch Container Insights to provide insights on your usage related to Network Policies.

+

Amazon VPC CNI also ships an SDK that provides an interface to interact with eBPF programs on the node. The SDK is installed when the aws-node is deployed onto the nodes. You can find the SDK binary installed under /opt/cni/bin directory on the node. At launch, the SDK provides support for fundamental functionalities such as inspecting eBPF programs and maps.

+
sudo /opt/cni/bin/aws-eks-na-cli ebpf progs
+
+

Log network traffic metadata

+

AWS VPC Flow Logs captures metadata about the traffic flowing through a VPC, such as source and destination IP address and port along with accepted/dropped packets. This information could be analyzed to look for suspicious or unusual activity between resources within the VPC, including Pods. However, since the IP addresses of pods frequently change as they are replaced, Flow Logs may not be sufficient on its own. Calico Enterprise extends the Flow Logs with pod labels and other metadata, making it easier to decipher the traffic flows between pods.

+

Security groups

+

EKS uses AWS VPC Security Groups (SGs) to control the traffic between the Kubernetes control plane and the cluster's worker nodes. Security groups are also used to control the traffic between worker nodes, and other VPC resources, and external IP addresses. When you provision an EKS cluster (with Kubernetes version 1.14-eks.3 or greater), a cluster security group is automatically created for you. This security group allows unfettered communication between the EKS control plane and the nodes from managed node groups. For simplicity, it is recommended that you add the cluster SG to all node groups, including unmanaged node groups.

+

Prior to Kubernetes version 1.14 and EKS version eks.3, there were separate security groups configured for the EKS control plane and node groups. The minimum and suggested rules for the control plane and node group security groups can be found at https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html. The minimum rules for the control plane security group allows port 443 inbound from the worker node SG. This rule is what allows the kubelets to communicate with the Kubernetes API server. It also includes port 10250 for outbound traffic to the worker node SG; 10250 is the port that the kubelets listen on. Similarly, the minimum node group rules allow port 10250 inbound from the control plane SG and 443 outbound to the control plane SG. Finally there is a rule that allows unfettered communication between nodes within a node group.

+

If you need to control communication between services that run within the cluster and service the run outside the cluster such as an RDS database, consider security groups for pods. With security groups for pods, you can assign an existing security group to a collection of pods.

+
+

Warning

+

If you reference a security group that does not exist prior to the creation of the pods, the pods will not get scheduled.

+
+

You can control which pods are assigned to a security group by creating a SecurityGroupPolicy object and specifying a PodSelector or a ServiceAccountSelector. Setting the selectors to {} will assign the SGs referenced in the SecurityGroupPolicy to all pods in a namespace or all Service Accounts in a namespace. Be sure you've familiarized yourself with all the considerations before implementing security groups for pods.

+
+

Important

+

If you use SGs for pods you must create SGs that allow port 53 outbound to the cluster security group. Similarly, you must update the cluster security group to accept port 53 inbound traffic from the pod security group.

+
+
+

Important

+

The limits for security groups still apply when using security groups for pods so use them judiciously.

+
+
+

Important

+

You must create rules for inbound traffic from the cluster security group (kubelet) for all of the probes configured for pod.

+
+
+

Important

+

Security groups for pods relies on a feature known as ENI trunking which was created to increase the ENI density of an EC2 instance. When a pod is assigned to an SG, a VPC controller associates a branch ENI from the node group with the pod. If there aren't enough branch ENIs available in a node group at the time the pod is scheduled, the pod will stay in pending state. The number of branch ENIs an instance can support varies by instance type/family. See https://docs.aws.amazon.com/eks/latest/userguide/security-groups-for-pods.html#supported-instance-types for further details.

+
+

While security groups for pods offers an AWS-native way to control network traffic within and outside of your cluster without the overhead of a policy daemon, other options are available. For example, the Cilium policy engine allows you to reference a DNS name in a network policy. Calico Enterprise includes an option for mapping network policies to AWS security groups. If you've implemented a service mesh like Istio, you can use an egress gateway to restrict network egress to specific, fully qualified domains or IP addresses. For further information about this option, read the three part series on egress traffic control in Istio.

+

When to use Network Policy vs Security Group for Pods?

+

When to use Kubernetes network policy

+
    +
  • Controlling pod-to-pod traffic
  • +
  • Suitable for controlling network traffic between pods inside a cluster (east-west traffic)
  • +
  • Control traffic at the IP address or port level (OSI layer 3 or 4)
  • +
+

When to use AWS Security groups for pods (SGP)

+
    +
  • Leverage existing AWS configurations
  • +
  • If you already have complex set of EC2 security groups that manage access to AWS services and you are migrating applications from EC2 instances to EKS, SGPs can be a very good choice allowing you to reuse security group resources and apply them to your pods.
  • +
  • Control access to AWS services
  • +
  • Your applications running within an EKS cluster wants to communicate with other AWS services (RDS database), use SGPs as an efficient mechanism to control the traffic from the pods to AWS services.
  • +
  • Isolation of Pod & Node traffic
  • +
  • If you want to completely separate pod traffic from the rest of the node traffic, use SGP in POD_SECURITY_GROUP_ENFORCING_MODE=strict mode.
  • +
+

Best practices using Security groups for pods and Network Policy

+
    +
  • Layered security
  • +
  • Use a combination of SGP and kubernetes network policy for a layered security approach
  • +
  • Use SGPs to limit network level access to AWS services that are not part of a cluster, while kubernetes network policies can restrict network traffic between pods inside the cluster
  • +
  • Principle of least privilege
  • +
  • Only allow necessary traffic between pods or namespaces
  • +
  • Segment your applications
  • +
  • Wherever possible, segment applications by the network policy to reduce the blast radius if an application is compromised
  • +
  • Keep policies simple and clear
  • +
  • Kubernetes network policies can be quite granular and complex, its best to keep them as simple as possible to reduce the risk of misconfiguration and ease the management overhead
  • +
  • Reduce the attack surface
  • +
  • Minimize the attack surface by limiting the exposure of your applications
  • +
+
+

Attention

+

Security Groups for pods provides two enforcing modes: strict and standard. You must use standard mode when using both Network Policy and Security Groups for pods features in an EKS cluster.

+
+

When it comes to network security, a layered approach is often the most effective solution. Using kubernetes network policy and SGP in combination can provide a robust defense-in-depth strategy for your applications running in EKS.

+

Service Mesh Policy Enforcement or Kubernetes network policy

+

A service mesh is a dedicated infrastructure layer that you can add to your applications. It allows you to transparently add capabilities like observability, traffic management, and security, without adding them to your own code.

+

Service mesh enforces policies at Layer 7 (application) of OSI model whereas kubernetes network policies operate at Layer 3 (network) and Layer 4 (transport). There are many offerings in this space like AWS AppMesh, Istio, Linkerd, etc.,

+

When to use Service mesh for policy enforcement

+
    +
  • Have existing investment in a service mesh
  • +
  • Need more advanced capabilities like traffic management, observability & security
  • +
  • Traffic control, load balancing, circuit breaking, rate limiting, timeouts etc.
  • +
  • Detailed insights into how your services are performing (latency, error rates, requests per second, request volumes etc.)
  • +
  • You want to implement and leverage service mesh for security features like mTLS
  • +
+

Choose Kubernetes network policy for simpler use cases

+
    +
  • Limit which pods can communicate with each other
  • +
  • Network policies require fewer resources than a service mesh making them a good fit for simpler use cases or for smaller clusters where the overhead of running and managing a service mesh might not be justified
  • +
+
+

Tip

+

Network policies and Service mesh can also be used together. Use network policies to provide a baseline level of security and isolation between your pods and then use a service mesh to add additional capabilities like traffic management, observability and security.

+
+

ThirdParty Network Policy Engines

+

Consider a Third Party Network Policy Engine when you have advanced policy requirements like Global Network Policies, support for DNS Hostname based rules, Layer 7 rules, ServiceAccount based rules, and explicit deny/log actions, etc., Calico, is an open source policy engine from Tigera that works well with EKS. In addition to implementing the full set of Kubernetes network policy features, Calico supports extended network polices with a richer set of features, including support for layer 7 rules, e.g. HTTP, when integrated with Istio. Calico policies can be scoped to Namespaces, Pods, service accounts, or globally. When policies are scoped to a service account, it associates a set of ingress/egress rules with that service account. With the proper RBAC rules in place, you can prevent teams from overriding these rules, allowing IT security professionals to safely delegate administration of namespaces. Isovalent, the maintainers of Cilium, have also extended the network policies to include partial support for layer 7 rules, e.g. HTTP. Cilium also has support for DNS hostnames which can be useful for restricting traffic between Kubernetes Services/Pods and resources that run within or outside of your VPC. By contrast, Calico Enterprise includes a feature that allows you to map a Kubernetes network policy to an AWS security group, as well as DNS hostnames.

+

You can find a list of common Kubernetes network policies at https://github.com/ahmetb/kubernetes-network-policy-recipes. A similar set of rules for Calico are available at https://docs.projectcalico.org/security/calico-network-policy.

+

Migration to Amazon VPC CNI Network Policy Engine

+

To maintain consistency and avoid unexpected pod communication behavior, it is recommended to deploy only one Network Policy Engine in your cluster. If you want to migrate from 3P to VPC CNI Network Policy Engine, we recommend converting your existing 3P NetworkPolicy CRDs to the Kubernetes NetworkPolicy resources before enabling VPC CNI network policy support. And, test the migrated policies in a separate test cluster before applying them in you production environment. This allows you to identify and address any potential issues or inconsistencies in pod communication behavior.

+

Migration Tool

+

To assist in your migration process, we have developed a tool called K8s Network Policy Migrator that converts your existing Calico/Cilium network policy CRDs to Kubernetes native network policies. After conversion you can directly test the converted network policies on your new clusters running VPC CNI network policy controller. The tool is designed to help you streamline the migration process and ensure a smooth transition.

+
+

Important

+

Migration tool will only convert 3P policies that are compatible with native kubernetes network policy api. If you are using advanced network policy features offered by 3P plugins, Migration tool will skip and report them.

+
+

Please note that migration tool is currently not supported by AWS VPC CNI Network policy engineering team, it is made available to customers on a best-effort basis. We encourage you to utilize this tool to facilitate your migration process. In the event that you encounter any issues or bugs with the tool, we kindly ask you create a GitHub issue. Your feedback is invaluable to us and will assist in the continuous improvement of our services.

+

Additional Resources

+ +

Encryption in transit

+

Applications that need to conform to PCI, HIPAA, or other regulations may need to encrypt data while it is in transit. Nowadays TLS is the de facto choice for encrypting traffic on the wire. TLS, like it's predecessor SSL, provides secure communications over a network using cryptographic protocols. TLS uses symmetric encryption where the keys to encrypt the data are generated based on a shared secret that is negotiated at the beginning of the session. The following are a few ways that you can encrypt data in a Kubernetes environment.

+

Nitro Instances

+

Traffic exchanged between the following Nitro instance types, e.g. C5n, G4, I3en, M5dn, M5n, P3dn, R5dn, and R5n, is automatically encrypted by default. When there's an intermediate hop, like a transit gateway or a load balancer, the traffic is not encrypted. See Encryption in transit for further details on encryption in transit as well as the complete list of instances types that support network encryption by default.

+

Container Network Interfaces (CNIs)

+

WeaveNet can be configured to automatically encrypt all traffic using NaCl encryption for sleeve traffic, and IPsec ESP for fast datapath traffic.

+

Service Mesh

+

Encryption in transit can also be implemented with a service mesh like App Mesh, Linkerd v2, and Istio. AppMesh supports mTLS with X.509 certificates or Envoy's Secret Discovery Service(SDS). Linkerd and Istio both have support for mTLS.

+

The aws-app-mesh-examples GitHub repository provides walkthroughs for configuring mTLS using X.509 certificates and SPIRE as SDS provider with your Envoy container:

+ +

App Mesh also supports TLS encryption with a private certificate issued by AWS Certificate Manager (ACM) or a certificate stored on the local file system of the virtual node.

+

The aws-app-mesh-examples GitHub repository provides walkthroughs for configuring TLS using certificates issued by ACM and certificates that are packaged with your Envoy container:

+ +

Ingress Controllers and Load Balancers

+

Ingress controllers are a way for you to intelligently route HTTP/S traffic that emanates from outside the cluster to services running inside the cluster. Oftentimes, these Ingresses are fronted by a layer 4 load balancer, like the Classic Load Balancer or the Network Load Balancer (NLB). Encrypted traffic can be terminated at different places within the network, e.g. at the load balancer, at the ingress resource, or the Pod. How and where you terminate your SSL connection will ultimately be dictated by your organization's network security policy. For instance, if you have a policy that requires end-to-end encryption, you will have to decrypt the traffic at the Pod. This will place additional burden on your Pod as it will have to spend cycles establishing the initial handshake. Overall SSL/TLS processing is very CPU intensive. Consequently, if you have the flexibility, try performing the SSL offload at the Ingress or the load balancer.

+

Use encryption with AWS Elastic load balancers

+

The AWS Application Load Balancer (ALB) and Network Load Balancer (NLB) both have support for transport encryption (SSL and TLS). The alb.ingress.kubernetes.io/certificate-arn annotation for the ALB lets you to specify which certificates to add to the ALB. If you omit the annotation the controller will attempt to add certificates to listeners that require it by matching the available AWS Certificate Manager (ACM) certificates using the host field. Starting with EKS v1.15 you can use the service.beta.kubernetes.io/aws-load-balancer-ssl-cert annotation with the NLB as shown in the example below.

+
apiVersion: v1
+kind: Service
+metadata:
+  name: demo-app
+  namespace: default
+  labels:
+    app: demo-app
+  annotations:
+     service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
+     service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "<certificate ARN>"
+     service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
+     service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
+spec:
+  type: LoadBalancer
+  ports:
+  - port: 443
+    targetPort: 80
+    protocol: TCP
+  selector:
+    app: demo-app
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+  name: nginx
+  namespace: default
+  labels:
+    app: demo-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: demo-app
+  template:
+    metadata:
+      labels:
+        app: demo-app
+    spec:
+      containers:
+        - name: nginx
+          image: nginx
+          ports:
+            - containerPort: 443
+              protocol: TCP
+            - containerPort: 80
+              protocol: TCP
+
+

Following are additional examples for SSL/TLS termination.

+ +
+

Attention

+

Some Ingresses, like the AWS LB controller, implement the SSL/TLS using Annotations instead of as part of the Ingress Spec.

+
+

ACM Private CA with cert-manager

+

You can enable TLS and mTLS to secure your EKS application workloads at the ingress, on the pod, and between pods using ACM Private Certificate Authority (CA) and cert-manager, a popular Kubernetes add-on to distribute, renew, and revoke certificates. ACM Private CA is a highly-available, secure, managed CA without the upfront and maintenance costs of managing your own CA. If you are using the default Kubernetes certificate authority, there is an opportunity to improve your security and meet compliance requirements with ACM Private CA. ACM Private CA secures private keys in FIPS 140-2 Level 3 hardware security modules (very secure), compared with the default CA storing keys encoded in memory (less secure). A centralized CA also gives you more control and improved auditability for private certificates both inside and outside of a Kubernetes environment.

+

Short-Lived CA Mode for Mutual TLS Between Workloads

+

When using ACM Private CA for mTLS in EKS, it is recommended that you use short lived certificates with short-lived CA mode. Although it is possible to issue out short-lived certificates in the general-purpose CA mode, using short-lived CA mode works out more cost-effective (~75% cheaper than general mode) for use cases where new certificates need to be issued frequently. In addition to this, you should try to align the validity period of the private certificates with the lifetime of the pods in your EKS cluster. Learn more about ACM Private CA and its benefits here.

+

ACM Setup Instructions

+

Start by creating a Private CA by following procedures provided in the ACM Private CA tech docs. Once you have a Private CA, install cert-manager using regular installation instructions. After installing cert-manager, install the Private CA Kubernetes cert-manager plugin by following the setup instructions in GitHub. The plugin lets cert-manager request private certificates from ACM Private CA.

+

Now that you have a Private CA and an EKS cluster with cert-manager and the plugin installed, it’s time to set permissions and create the issuer. Update IAM permissions of the EKS node role to allow access to ACM Private CA. Replace the <CA_ARN> with the value from your Private CA:

+
{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "awspcaissuer",
+            "Action": [
+                "acm-pca:DescribeCertificateAuthority",
+                "acm-pca:GetCertificate",
+                "acm-pca:IssueCertificate"
+            ],
+            "Effect": "Allow",
+            "Resource": "<CA_ARN>"
+        }
+    ]
+}
+
+

Service Roles for IAM Accounts, or IRSA can also be used. Please see the Additional Resources section below for complete examples.

+

Create an Issuer in Amazon EKS by creating a Custom Resource Definition file named cluster-issuer.yaml with the following text in it, replacing <CA_ARN> and <Region> information with your Private CA.

+
apiVersion: awspca.cert-manager.io/v1beta1
+kind: AWSPCAClusterIssuer
+metadata:
+          name: demo-test-root-ca
+spec:
+          arn: <CA_ARN>
+          region: <Region>
+
+

Deploy the Issuer you created.

+
kubectl apply -f cluster-issuer.yaml
+
+

Your EKS cluster is configured to request certificates from Private CA. You can now use cert-manager's Certificate resource to issue certificates by changing the issuerRef field's values to the Private CA Issuer you created above. For more details on how to specify and request Certificate resources, please check cert-manager's Certificate Resources guide. See examples here.

+

ACM Private CA with Istio and cert-manager

+

If you are running Istio in your EKS cluster, you can disable the Istio control plane (specifically istiod) from functioning as the root Certificate Authority (CA), and configure ACM Private CA as the root CA for mTLS between workloads. If you're going with this approach, consider using the short-lived CA mode in ACM Private CA. Refer to the previous section and this blog post for more details.

+

How Certificate Signing Works in Istio (Default)

+

Workloads in Kubernetes are identified using service accounts. If you don't specify a service account, Kubernetes will automatically assign one to your workload. Also, service accounts automatically mount an associated token. This token is used by the service account for workloads to authenticate against the Kubernetes API. The service account may be sufficient as an identity for Kubernetes but Istio has its own identity management system and CA. When a workload starts up with its envoy sidecar proxy, it needs an identity assigned from Istio in order for it to be deemed as trustworthy and allowed to communicate with other services in the mesh.

+

To get this identity from Istio, the istio-agent sends a request known as a certificate signing request (or CSR) to the Istio control plane. This CSR contains the service account token so that the workload's identity can be verified before being processed. This verification process is handled by istiod, which acts as both the Registration Authority (or RA) and the CA. The RA serves as a gatekeeper that makes sure only verified CSR makes it through to the CA. Once the CSR is verified, it will be forwarded to the CA which will then issue a certificate containing a SPIFFE identity with the service account. This certificate is called a SPIFFE verifiable identity document (or SVID). The SVID is assigned to the requesting service for identification purposes and to encrypt the traffic in transit between the communicating services.

+

Default flow for Istio Certificate Signing Requests

+

How Certificate Signing Works in Istio with ACM Private CA

+

You can use a cert-manager add-on called the Istio Certificate Signing Request agent (istio-csr) to integrate Istio with ACM Private CA. This agent allows Istio workloads and control plane components to be secured with cert manager issuers, in this case ACM Private CA. The istio-csr agent exposes the same service that istiod serves in the default config of validating incoming CSRs. Except, after verification, it will convert the requests into resources that cert manager supports (i.e. integrations with external CA issuers).

+

Whenever there's a CSR from a workload, it will be forwarded to istio-csr, which will request certificates from ACM Private CA. This communication between istio-csr and ACM Private CA is enabled by the AWS Private CA issuer plugin. Cert manager uses this plugin to request TLS certificates from ACM Private CA. The issuer plugin will communicate with the ACM Private CA service to request a signed certificate for the workload. Once the certificate has been signed, it will be returned to istio-csr, which will read the signed request, and return it to the workload that initiated the CSR.

+

Flow for Istio Certificate Signing Requests with istio-csr

+

Istio with Private CA Setup Instructions

+
    +
  1. Start by following the same setup instructions in this section to complete the following:
  2. +
  3. Create a Private CA
  4. +
  5. Install cert-manager
  6. +
  7. Install the issuer plugin
  8. +
  9. Set permissions and create an issuer. The issuer represents the CA and is used to sign istiod and mesh workload certificates. It will communicate with ACM Private CA.
  10. +
  11. Create an istio-system namespace. This is where the istiod certificate and other Istio resources will be deployed.
  12. +
  13. +

    Install Istio CSR configured with AWS Private CA Issuer Plugin. You can preserve the certificate signing requests for workloads to verify that they get approved and signed (preserveCertificateRequests=true).

    +
    helm install -n cert-manager cert-manager-istio-csr jetstack/cert-manager-istio-csr \
    +--set "app.certmanager.issuer.group=awspca.cert-manager.io" \
    +--set "app.certmanager.issuer.kind=AWSPCAClusterIssuer" \
    +--set "app.certmanager.issuer.name=<the-name-of-the-issuer-you-created>" \
    +--set "app.certmanager.preserveCertificateRequests=true" \
    +--set "app.server.maxCertificateDuration=48h" \
    +--set "app.tls.certificateDuration=24h" \
    +--set "app.tls.istiodCertificateDuration=24h" \
    +--set "app.tls.rootCAFile=/var/run/secrets/istio-csr/ca.pem" \
    +--set "volumeMounts[0].name=root-ca" \
    +--set "volumeMounts[0].mountPath=/var/run/secrets/istio-csr" \
    +--set "volumes[0].name=root-ca" \
    +--set "volumes[0].secret.secretName=istio-root-ca"
    +
    +
  14. +
  15. +

    Install Istio with custom configurations to replace istiod with cert-manager istio-csr as the certificate provider for the mesh. This process can be carried out using the Istio Operator.

    +
    apiVersion: install.istio.io/v1alpha1
    +kind: IstioOperator
    +metadata:
    +  name: istio
    +  namespace: istio-system
    +spec:
    +  profile: "demo"
    +  hub: gcr.io/istio-release
    +  values:
    +  global:
    +    # Change certificate provider to cert-manager istio agent for istio agent
    +    caAddress: cert-manager-istio-csr.cert-manager.svc:443
    +  components:
    +    pilot:
    +      k8s:
    +        env:
    +          # Disable istiod CA Sever functionality
    +        - name: ENABLE_CA_SERVER
    +          value: "false"
    +        overlays:
    +        - apiVersion: apps/v1
    +          kind: Deployment
    +          name: istiod
    +          patches:
    +
    +            # Mount istiod serving and webhook certificate from Secret mount
    +          - path: spec.template.spec.containers.[name:discovery].args[7]
    +            value: "--tlsCertFile=/etc/cert-manager/tls/tls.crt"
    +          - path: spec.template.spec.containers.[name:discovery].args[8]
    +            value: "--tlsKeyFile=/etc/cert-manager/tls/tls.key"
    +          - path: spec.template.spec.containers.[name:discovery].args[9]
    +            value: "--caCertFile=/etc/cert-manager/ca/root-cert.pem"
    +
    +          - path: spec.template.spec.containers.[name:discovery].volumeMounts[6]
    +            value:
    +              name: cert-manager
    +              mountPath: "/etc/cert-manager/tls"
    +              readOnly: true
    +          - path: spec.template.spec.containers.[name:discovery].volumeMounts[7]
    +            value:
    +              name: ca-root-cert
    +              mountPath: "/etc/cert-manager/ca"
    +              readOnly: true
    +
    +          - path: spec.template.spec.volumes[6]
    +            value:
    +              name: cert-manager
    +              secret:
    +                secretName: istiod-tls
    +          - path: spec.template.spec.volumes[7]
    +            value:
    +              name: ca-root-cert
    +              configMap:
    +                defaultMode: 420
    +                name: istio-ca-root-cert
    +
    +
  16. +
  17. +

    Deploy the above custom resource you created.

    +
    istioctl operator init
    +kubectl apply -f istio-custom-config.yaml
    +
    +
  18. +
  19. +

    Now you can deploy a workload to the mesh in your EKS cluster and enforce mTLS.

    +
  20. +
+

Istio certificate signing requests

+

Tools and resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/pods/index.html b/security/docs/pods/index.html new file mode 100644 index 000000000..e0c17169e --- /dev/null +++ b/security/docs/pods/index.html @@ -0,0 +1,3013 @@ + + + + + + + + + + + + + + + + + + + + + + + Pod Security - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Pod Security

+

The pod specification includes a variety of different attributes that can strengthen or weaken your overall security posture. As a Kubernetes practitioner your chief concern should be preventing a process that’s running in a container from escaping the isolation boundaries of the container runtime and gaining access to the underlying host.

+

Linux Capabilities

+

The processes that run within a container run under the context of the [Linux] root user by default. Although the actions of root within a container are partially constrained by the set of Linux capabilities that the container runtime assigns to the containers, these default privileges could allow an attacker to escalate their privileges and/or gain access to sensitive information bound to the host, including Secrets and ConfigMaps. Below is a list of the default capabilities assigned to containers. For additional information about each capability, see http://man7.org/linux/man-pages/man7/capabilities.7.html.

+

CAP_AUDIT_WRITE, CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_FOWNER, CAP_FSETID, CAP_KILL, CAP_MKNOD, CAP_NET_BIND_SERVICE, CAP_NET_RAW, CAP_SETGID, CAP_SETUID, CAP_SETFCAP, CAP_SETPCAP, CAP_SYS_CHROOT

+
+

Info

+
+

EC2 and Fargate pods are assigned the aforementioned capabilities by default. Additionally, Linux capabilities can only be dropped from Fargate pods.

+

Pods that are run as privileged, inherit all of the Linux capabilities associated with root on the host. This should be avoided if possible.

+

Node Authorization

+

All Kubernetes worker nodes use an authorization mode called Node Authorization. Node Authorization authorizes all API requests that originate from the kubelet and allows nodes to perform the following actions:

+

Read operations:

+
    +
  • services
  • +
  • endpoints
  • +
  • nodes
  • +
  • pods
  • +
  • secrets, configmaps, persistent volume claims and persistent volumes related to pods bound to the kubelet’s node
  • +
+

Write operations:

+
    +
  • nodes and node status (enable the NodeRestriction admission plugin to limit a kubelet to modify its own node)
  • +
  • pods and pod status (enable the NodeRestriction admission plugin to limit a kubelet to modify pods bound to itself)
  • +
  • events
  • +
+

Auth-related operations:

+
    +
  • Read/write access to the CertificateSigningRequest (CSR) API for TLS bootstrapping
  • +
  • the ability to create TokenReview and SubjectAccessReview for delegated authentication/authorization checks
  • +
+

EKS uses the node restriction admission controller which only allows the node to modify a limited set of node attributes and pod objects that are bound to the node. Nevertheless, an attacker who manages to get access to the host will still be able to glean sensitive information about the environment from the Kubernetes API that could allow them to move laterally within the cluster.

+

Pod Security Solutions

+

Pod Security Policy (PSP)

+

In the past, Pod Security Policy (PSP) resources were used to specify a set of requirements that pods had to meet before they could be created. As of Kubernetes version 1.21, PSP have been deprecated. They are scheduled for removal in Kubernetes version 1.25.

+
+

Attention

+
+

PSPs are deprecated in Kubernetes version 1.21. You will have until version 1.25 or roughly 2 years to transition to an alternative. This document explains the motivation for this deprecation.

+

Migrating to a new pod security solution

+

Since PSPs have been removed as of Kubernetes v1.25, cluster administrators and operators must replace those security controls. Two solutions can fill this need:

+ +

Both the PAC and PSS solutions can coexist with PSP; they can be used in clusters before PSP is removed. This eases adoption when migrating from PSP. Please see this document when considering migrating from PSP to PSS.

+

Kyverno, one of the PAC solutions outlined below, has specific guidance outlined in a blog post when migrating from PSPs to its solution including analogous policies, feature comparisons, and a migration procedure. Additional information and guidance on migration to Kyverno with respect to Pod Security Admission (PSA) has been published on the AWS blog here.

+

Policy-as-code (PAC)

+

Policy-as-code (PAC) solutions provide guardrails to guide cluster users, and prevent unwanted behaviors, through prescribed and automated controls. PAC uses Kubernetes Dynamic Admission Controllers to intercept the Kubernetes API server request flow, via a webhook call, and mutate and validate request payloads, based on policies written and stored as code. Mutation and validation happens before the API server request results in a change to the cluster. PAC solutions use policies to match and act on API server request payloads, based on taxonomy and values.

+

There are several open source PAC solutions available for Kubernetes. These solutions are not part of the Kubernetes project; they are sourced from the Kubernetes ecosystem. Some PAC solutions are listed below.

+ +

For further information about PAC solutions and how to help you select the appropriate solution for your needs, see the links below.

+ +

Pod Security Standards (PSS) and Pod Security Admission (PSA)

+

In response to the PSP deprecation and the ongoing need to control pod security out-of-the-box, with a built-in Kubernetes solution, the Kubernetes Auth Special Interest Group created the Pod Security Standards (PSS) and Pod Security Admission (PSA). The PSA effort includes an admission controller webhook project that implements the controls defined in the PSS. This admission controller approach resembles that used in the PAC solutions.

+

According to the Kubernetes documentation, the PSS "define three different policies to broadly cover the security spectrum. These policies are cumulative and range from highly-permissive to highly-restrictive."

+

These policies are defined as:

+
    +
  • +

    Privileged: Unrestricted (unsecure) policy, providing the widest possible level of permissions. This policy allows for known privilege escalations. It is the absence of a policy. This is good for applications such as logging agents, CNIs, storage drivers, and other system wide applications that need privileged access.

    +
  • +
  • +

    Baseline: Minimally restrictive policy which prevents known privilege escalations. Allows the default (minimally specified) Pod configuration. The baseline policy prohibits use of hostNetwork, hostPID, hostIPC, hostPath, hostPort, the inability to add Linux capabilities, along with several other restrictions.

    +
  • +
  • +

    Restricted: Heavily restricted policy, following current Pod hardening best practices. This policy inherits from the baseline and adds further restrictions such as the inability to run as root or a root-group. Restricted policies may impact an application's ability to function. They are primarily targeted at running security critical applications.

    +
  • +
+

These policies define profiles for pod execution, arranged into three levels of privileged vs. restricted access.

+

To implement the controls defined by the PSS, PSA operates in three modes:

+
    +
  • +

    enforce: Policy violations will cause the pod to be rejected.

    +
  • +
  • +

    audit: Policy violations will trigger the addition of an audit annotation to the event recorded in the audit log, but are otherwise allowed.

    +
  • +
  • +

    warn: Policy violations will trigger a user-facing warning, but are otherwise allowed.

    +
  • +
+

These modes and the profile (restriction) levels are configured at the Kubernetes Namespace level, using labels, as seen in the below example.

+
apiVersion: v1
+kind: Namespace
+metadata:
+  name: policy-test
+  labels:
+    pod-security.kubernetes.io/enforce: restricted
+
+

When used independently, these operational modes have different responses that result in different user experiences. The enforce mode will prevent pods from being created if respective podSpecs violate the configured restriction level. However, in this mode, non-pod Kubernetes objects that create pods, such as Deployments, will not be prevented from being applied to the cluster, even if the podSpec therein violates the applied PSS. In this case the Deployment will be applied, while the pod(s) will be prevented from being applied.

+

This is a difficult user experience, as there is no immediate indication that the successfully applied Deployment object belies failed pod creation. The offending podSpecs will not create pods. Inspecting the Deployment resource with kubectl get deploy <DEPLOYMENT_NAME> -oyaml will expose the message from the failed pod(s) .status.conditions element, as seen below.

+
...
+status:
+  conditions:
+    - lastTransitionTime: "2022-01-20T01:02:08Z"
+      lastUpdateTime: "2022-01-20T01:02:08Z"
+      message: 'pods "test-688f68dc87-tw587" is forbidden: violates PodSecurity "restricted:latest":
+        allowPrivilegeEscalation != false (container "test" must set securityContext.allowPrivilegeEscalation=false),
+        unrestricted capabilities (container "test" must set securityContext.capabilities.drop=["ALL"]),
+        runAsNonRoot != true (pod or container "test" must set securityContext.runAsNonRoot=true),
+        seccompProfile (pod or container "test" must set securityContext.seccompProfile.type
+        to "RuntimeDefault" or "Localhost")'
+      reason: FailedCreate
+      status: "True"
+      type: ReplicaFailure
+...
+
+

In both the audit and warn modes, the pod restrictions do not prevent violating pods from being created and started. However, in these modes audit annotations on API server audit log events and warnings to API server clients, such as kubectl, are triggered, respectively, when pods, as well as objects that create pods, contain podSpecs with violations. A kubectl Warning message is seen below.

+
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "test" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "test" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "test" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "test" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
+deployment.apps/test created
+
+

The PSA audit and warn modes are useful when introducing the PSS without negatively impacting cluster operations.

+

The PSA operational modes are not mutually exclusive, and can be used in a cumulative manner. As seen below, the multiple modes can be configured in a single namespace.

+
apiVersion: v1
+kind: Namespace
+metadata:
+  name: policy-test
+  labels:
+    pod-security.kubernetes.io/audit: restricted
+    pod-security.kubernetes.io/enforce: restricted
+    pod-security.kubernetes.io/warn: restricted
+
+

In the above example, the user-friendly warnings and audit annotations are provided when applying Deployments, while the enforce of violations are also provided at the pod level. In fact multiple PSA labels can use different profile levels, as seen below.

+
apiVersion: v1
+kind: Namespace
+metadata:
+  name: policy-test
+  labels:
+    pod-security.kubernetes.io/enforce: baseline
+    pod-security.kubernetes.io/warn: restricted
+
+

In the above example, PSA is configured to allow the creation of all pods that satisfy the baseline profile level, and then warn on pods (and objects that create pods) that violate the restricted profile level. This is a useful approach to determine the possible impacts when changing from the baseline to restricted profiles.

+

Existing Pods

+

If a namespace with existing pods is modified to use a more restrictive PSS profile, the audit and warn modes will produce appropriate messages; however, enforce mode will not delete the pods. The warning messages are seen below.

+
Warning: existing pods in namespace "policy-test" violate the new PodSecurity enforce level "restricted:latest"
+Warning: test-688f68dc87-htm8x: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile
+namespace/policy-test configured
+
+

Exemptions

+

PSA uses Exemptions to exclude enforcement of violations against pods that would have otherwise been applied. These exemptions are listed below.

+
    +
  • +

    Usernames: requests from users with an exempt authenticated (or impersonated) username are ignored.

    +
  • +
  • +

    RuntimeClassNames: pods and workload resources specifying an exempt runtime class name are ignored.

    +
  • +
  • +

    Namespaces: pods and workload resources in an exempt namespace are ignored.

    +
  • +
+

These exemptions are applied statically in the PSA admission controller configuration as part of the API server configuration.

+

In the Validating Webhook implementation the exemptions can be configured within a Kubernetes ConfigMap resource that gets mounted as a volume into the pod-security-webhook container.

+
apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: pod-security-webhook
+  namespace: pod-security-webhook
+data:
+  podsecurityconfiguration.yaml: |
+    apiVersion: pod-security.admission.config.k8s.io/v1
+    kind: PodSecurityConfiguration
+    defaults:
+      enforce: "restricted"
+      enforce-version: "latest"
+      audit: "restricted"
+      audit-version: "latest"
+      warn: "restricted"
+      warn-version: "latest"
+    exemptions:
+      # Array of authenticated usernames to exempt.
+      usernames: []
+      # Array of runtime class names to exempt.
+      runtimeClasses: []
+      # Array of namespaces to exempt.
+      namespaces: ["kube-system","policy-test1"]
+
+

As seen in the above ConfigMap YAML the cluster-wide default PSS level has been set to restricted for all PSA modes, audit, enforce, and warn. This affects all namespaces, except those exempted: namespaces: ["kube-system","policy-test1"]. Additionally, in the ValidatingWebhookConfiguration resource, seen below, the pod-security-webhook namespace is also exempted from configured PSS.

+
...
+webhooks:
+  # Audit annotations will be prefixed with this name
+  - name: "pod-security-webhook.kubernetes.io"
+    # Fail-closed admission webhooks can present operational challenges.
+    # You may want to consider using a failure policy of Ignore, but should 
+    # consider the security tradeoffs.
+    failurePolicy: Fail
+    namespaceSelector:
+      # Exempt the webhook itself to avoid a circular dependency.
+      matchExpressions:
+        - key: kubernetes.io/metadata.name
+          operator: NotIn
+          values: ["pod-security-webhook"]
+...
+
+
+

Attention

+
+

Pod Security Admissions graduated to stable in Kubernetes v1.25. If you wanted to use the Pod Security Admission feature prior to it being enabled by default, you needed to install the dynamic admission controller (mutating webhook). The instructions for installing and configuring the webhook can be found here.

+

Choosing between policy-as-code and Pod Security Standards

+

The Pod Security Standards (PSS) were developed to replace the Pod Security Policy (PSP), by providing a solution that was built-in to Kubernetes and did not require solutions from the Kubernetes ecosystem. That being said, policy-as-code (PAC) solutions are considerably more flexible.

+

The following list of Pros and Cons is designed help you make a more informed decision about your pod security solution.

+

Policy-as-code (as compared to Pod Security Standards)

+

Pros:

+
    +
  • More flexible and more granular (down to attributes of resources if need be)
  • +
  • Not just focused on pods, can be used against different resources and actions
  • +
  • Not just applied at the namespace level
  • +
  • More mature than the Pod Security Standards
  • +
  • Decisions can be based on anything in the API server request payload, as well as existing cluster resources and external data (solution dependent)
  • +
  • Supports mutating API server requests before validation (solution dependent)
  • +
  • Can generate complementary policies and Kubernetes resources (solution dependent - From pod policies, Kyverno can auto-gen policies for higher-level controllers, such as Deployments. Kyverno can also generate additional Kubernetes resources "when a new resource is created or when the source is updated" by using Generate Rules.)
  • +
  • Can be used to shift left, into CICD pipelines, before making calls to the Kubernetes API server (solution dependent)
  • +
  • Can be used to implement behaviors that are not necessarily security related, such as best practices, organizational standards, etc.
  • +
  • Can be used in non-Kubernetes use cases (solution dependent)
  • +
  • Because of flexibility, the user experience can be tuned to users' needs
  • +
+

Cons:

+
    +
  • Not built into Kubernetes
  • +
  • More complex to learn, configure, and support
  • +
  • Policy authoring may require new skills/languages/capabilities
  • +
+

Pod Security Admission (as compared to policy-as-code)

+

Pros:

+
    +
  • Built into Kubernetes
  • +
  • Simpler to configure
  • +
  • No new languages to use or policies to author
  • +
  • If the cluster default admission level is configured to privileged, namespace labels can be used to opt namespaces into the pod security profiles.
  • +
+

Cons:

+
    +
  • Not as flexible or granular as policy-as-code
  • +
  • Only 3 levels of restrictions
  • +
  • Primarily focused on pods
  • +
+

Summary

+

If you currently do not have a pod security solution, beyond PSP, and your required pod security posture fits the model defined in the Pod Security Standards (PSS), then an easier path may be to adopt the PSS, in lieu of a policy-as-code solution. However, if your pod security posture does not fit the PSS model, or you envision adding additional controls, beyond that defined by PSS, then a policy-as-code solution would seem a better fit.

+

Recommendations

+

Use multiple Pod Security Admission (PSA) modes for a better user experience

+

As mentioned earlier, PSA enforce mode prevents pods with PSS violations from being applied, but does not stop higher-level controllers, such as Deployments. In fact, the Deployment will be applied successfully without any indication that the pods failed to be applied. While you can use kubectl to inspect the Deployment object, and discover the failed pods message from the PSA, the user experience could be better. To make the user experience better, multiple PSA modes (audit, enforce, warn) should be used.

+
apiVersion: v1
+kind: Namespace
+metadata:
+  name: policy-test
+  labels:
+    pod-security.kubernetes.io/audit: restricted
+    pod-security.kubernetes.io/enforce: restricted
+    pod-security.kubernetes.io/warn: restricted
+
+

In the above example, with enforce mode defined, when a Deployment manifest with PSS violations in the respective podSpec is attempted to be applied to the Kubernetes API server, the Deployment will be successfully applied, but the pods will not. And, since the audit and warn modes are also enabled, the API server client will receive a warning message and the API server audit log event will be annotated with a message as well.

+

Restrict the containers that can run as privileged

+

As mentioned, containers that run as privileged inherit all of the Linux capabilities assigned to root on the host. Seldom do containers need these types of privileges to function properly. There are multiple methods that can be used to restrict the permissions and capabilities of containers.

+
+

Attention

+
+

Fargate is a launch type that enables you to run "serverless" container(s) where the containers of a pod are run on infrastructure that AWS manages. With Fargate, you cannot run a privileged container or configure your pod to use hostNetwork or hostPort.

+

Do not run processes in containers as root

+

All containers run as root by default. This could be problematic if an attacker is able to exploit a vulnerability in the application and get shell access to the running container. You can mitigate this risk a variety of ways. First, by removing the shell from the container image. Second, adding the USER directive to your Dockerfile or running the containers in the pod as a non-root user. The Kubernetes podSpec includes a set of fields, under spec.securityContext, that let you specify the user and/or group under which to run your application. These fields are runAsUser and runAsGroup respectively.

+

To enforce the use of the spec.securityContext, and its associated elements, within the Kubernetes podSpec, policy-as-code or Pod Security Standards can be added to clusters. These solutions allow you to write and/or use policies or profiles that can validate inbound Kubernetes API server request payloads, before they are persisted into etcd. Furthermore, policy-as-code solutions can mutate inbound requests, and in some cases, generate new requests.

+

Never run Docker in Docker or mount the socket in the container

+

While this conveniently lets you to build/run images in Docker containers, you're basically relinquishing complete control of the node to the process running in the container. If you need to build container images on Kubernetes use Kaniko, buildah, or a build service like CodeBuild instead.

+
+

Tip

+
+

Kubernetes clusters used for CICD processing, such as building container images, should be isolated from clusters running more generalized workloads.

+

Restrict the use of hostPath or if hostPath is necessary restrict which prefixes can be used and configure the volume as read-only

+

hostPath is a volume that mounts a directory from the host directly to the container. Rarely will pods need this type of access, but if they do, you need to be aware of the risks. By default pods that run as root will have write access to the file system exposed by hostPath. This could allow an attacker to modify the kubelet settings, create symbolic links to directories or files not directly exposed by the hostPath, e.g. /etc/shadow, install ssh keys, read secrets mounted to the host, and other malicious things. To mitigate the risks from hostPath, configure the spec.containers.volumeMounts as readOnly, for example:

+
volumeMounts:
+- name: hostPath-volume
+    readOnly: true
+    mountPath: /host-path
+
+

You should also use policy-as-code solutions to restrict the directories that can be used by hostPath volumes, or prevent hostPath usage altogether. You can use the Pod Security Standards Baseline or Restricted policies to prevent the use of hostPath.

+

For further information about the dangers of privileged escalation, read Seth Art's blog Bad Pods: Kubernetes Pod Privilege Escalation.

+

Set requests and limits for each container to avoid resource contention and DoS attacks

+

A pod without requests or limits can theoretically consume all of the resources available on a host. As additional pods are scheduled onto a node, the node may experience CPU or memory pressure which can cause the Kubelet to terminate or evict pods from the node. While you can’t prevent this from happening all together, setting requests and limits will help minimize resource contention and mitigate the risk from poorly written applications that consume an excessive amount of resources.

+

The podSpec allows you to specify requests and limits for CPU and memory. CPU is considered a compressible resource because it can be oversubscribed. Memory is incompressible, i.e. it cannot be shared among multiple containers.

+

When you specify requests for CPU or memory, you’re essentially designating the amount of memory that containers are guaranteed to get. Kubernetes aggregates the requests of all the containers in a pod to determine which node to schedule the pod onto. If a container exceeds the requested amount of memory it may be subject to termination if there’s memory pressure on the node.

+

Limits are the maximum amount of CPU and memory resources that a container is allowed to consume and directly corresponds to the memory.limit_in_bytes value of the cgroup created for the container. A container that exceeds the memory limit will be OOM killed. If a container exceeds its CPU limit, it will be throttled.

+
+

Tip

+
+

When using container resources.limits it is strongly recommended that container resource usage (a.k.a. Resource Footprints) be data-driven and accurate, based on load testing. Absent an accurate and trusted resource footprint, container resources.limits can be padded. For example, resources.limits.memory could be padded 20-30% higher than observable maximums, to account for potential memory resource limit inaccuracies.

+

Kubernetes uses three Quality of Service (QoS) classes to prioritize the workloads running on a node. These include:

+
    +
  • guaranteed
  • +
  • burstable
  • +
  • best-effort
  • +
+

If limits and requests are not set, the pod is configured as best-effort (lowest priority). Best-effort pods are the first to get killed when there is insufficient memory. If limits are set on all containers within the pod, or if the requests and limits are set to the same values and not equal to 0, the pod is configured as guaranteed (highest priority). Guaranteed pods will not be killed unless they exceed their configured memory limits. If the limits and requests are configured with different values and not equal to 0, or one container within the pod sets limits and the others don’t or have limits set for different resources, the pods are configured as burstable (medium priority). These pods have some resource guarantees, but can be killed once they exceed their requested memory.

+
+

Attention

+
+

Requests don't affect the memory_limit_in_bytes value of the container's cgroup; the cgroup limit is set to the amount of memory available on the host. Nevertheless, setting the requests value too low could cause the pod to be targeted for termination by the kubelet if the node undergoes memory pressure.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassPriorityConditionKill Condition
Guaranteedhighestlimit = request != 0Only exceed memory limits
Burstablemediumlimit != request != 0Can be killed if exceed request memory
Best-Effortlowestlimit & request Not SetFirst to get killed when there's insufficient memory
+

For additional information about resource QoS, please refer to the Kubernetes documentation.

+

You can force the use of requests and limits by setting a resource quota on a namespace or by creating a limit range. A resource quota allows you to specify the total amount of resources, e.g. CPU and RAM, allocated to a namespace. When it’s applied to a namespace, it forces you to specify requests and limits for all containers deployed into that namespace. By contrast, limit ranges give you more granular control of the allocation of resources. With limit ranges you can min/max for CPU and memory resources per pod or per container within a namespace. You can also use them to set default request/limit values if none are provided.

+

Policy-as-code solutions can be used enforce requests and limits. or to even create the resource quotas and limit ranges when namespaces are created.

+

Do not allow privileged escalation

+

Privileged escalation allows a process to change the security context under which its running. Sudo is a good example of this as are binaries with the SUID or SGID bit. Privileged escalation is basically a way for users to execute a file with the permissions of another user or group. You can prevent a container from using privileged escalation by implementing a policy-as-code mutating policy that sets allowPrivilegeEscalation to false or by setting securityContext.allowPrivilegeEscalation in the podSpec. Policy-as-code policies can also be used to prevent API server requests from succeeding if incorrect settings are detected. Pod Security Standards can also be used to prevent pods from using privilege escalation.

+

Disable ServiceAccount token mounts

+

For pods that do not need to access the Kubernetes API, you can disable the +automatic mounting of a ServiceAccount token on a pod spec, or for all pods that +use a particular ServiceAccount.

+
+

Attention

+
+

Disabling ServiceAccount mounting does not prevent a pod from having network access to the Kubernetes API. To prevent a pod from having any network access to the Kubernetes API, you will need to modify the EKS cluster endpoint access and use a NetworkPolicy to block pod access.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-no-automount
+spec:
+  automountServiceAccountToken: false
+
+
apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: sa-no-automount
+automountServiceAccountToken: false
+
+

Disable service discovery

+

For pods that do not need to lookup or call in-cluster services, you can +reduce the amount of information given to a pod. You can set the Pod's DNS +policy to not use CoreDNS, and not expose services in the pod's namespace as +environment variables. See the Kubernetes docs on environment +variables for more information on service links. The default +value for a pod's DNS policy is "ClusterFirst" which uses in-cluster DNS, while +the non-default value "Default" uses the underlying node's DNS resolution. See +the Kubernetes docs on Pod DNS policy for more information.

+
+

Attention

+
+

Disabling service links and changing the pod's DNS policy does not prevent a pod from having network access to the in-cluster DNS service. An attacker can still enumerate services in a cluster by reaching the in-cluster DNS service. (ex: dig SRV *.*.svc.cluster.local @$CLUSTER_DNS_IP) To prevent in-cluster service discovery, use a NetworkPolicy to block pod access

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-no-service-info
+spec:
+    dnsPolicy: Default # "Default" is not the true default value
+    enableServiceLinks: false
+
+

Configure your images with read-only root file system

+

Configuring your images with a read-only root file system prevents an attacker from overwriting a binary on the file system that your application uses. If your application has to write to the file system, consider writing to a temporary directory or attach and mount a volume. You can enforce this by setting the pod's SecurityContext as follows:

+
...
+securityContext:
+  readOnlyRootFilesystem: true
+...
+
+

Policy-as-code and Pod Security Standards can be used to enforce this behavior.

+
+

Info

+
+

As per Windows containers in Kubernetes securityContext.readOnlyRootFilesystem cannot be set to true for a container running on Windows as write access is required for registry and system processes to run inside the container.

+

Tools and resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/security/docs/runtime/index.html b/security/docs/runtime/index.html new file mode 100644 index 000000000..8f008df37 --- /dev/null +++ b/security/docs/runtime/index.html @@ -0,0 +1,2355 @@ + + + + + + + + + + + + + + + + + + + + + + + Runtime Security - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Runtime security

+

Runtime security provides active protection for your containers while they're running. The idea is to detect and/or prevent malicious activity from occurring inside the container. This can be achieved with a number of mechanisms in the Linux kernel or kernel extensions that are integrated with Kubernetes, such as Linux capabilities, secure computing (seccomp), AppArmor, or SELinux. There are also options like Amazon GuardDuty and third party tools that can assist with establishing baselines and detecting anomalous activity with less manual configuration of Linux kernel mechanisms.

+
+

Attention

+

Kubernetes does not currently provide any native mechanisms for loading seccomp, AppArmor, or SELinux profiles onto Nodes. They either have to be loaded manually or installed onto Nodes when they are bootstrapped. This has to be done prior to referencing them in your Pods because the scheduler is unaware of which nodes have profiles. See below how tools like Security Profiles Operator can help automate provisioning of profiles onto nodes.

+
+

Security contexts and built-in Kubernetes controls

+

Many Linux runtime security mechanisms are tightly integrated with Kubernetes and can be configured through Kubernetes security contexts. One such option is the privileged flag, which is false by default and if enabled is essentially equivalent to root on the host. It is nearly always inappropriate to enable privileged mode in production workloads, but there are many more controls that can provide more granular privileges to containers as appropriate.

+

Linux capabilities

+

Linux capabilities allow you to grant certain capabilities to a Pod or container without providing all the abilities of the root user. Examples include CAP_NET_ADMIN, which allows configuring network interfaces or firewalls, or CAP_SYS_TIME, which allows manipulation of the system clock.

+

Seccomp

+

With secure computing (seccomp) you can prevent a containerized application from making certain syscalls to the underlying host operating system's kernel. While the Linux operating system has a few hundred system calls, the lion's share of them are not necessary for running containers. By restricting what syscalls can be made by a container, you can effectively decrease your application's attack surface.

+

Seccomp works by intercepting syscalls and only allowing those that have been allowlisted to pass through. Docker has a default seccomp profile which is suitable for a majority of general purpose workloads, and other container runtimes like containerd provide comparable defaults. You can configure your container or Pod to use the container runtime's default seccomp profile by adding the following to the securityContext section of the Pod spec:

+
securityContext:
+  seccompProfile:
+    type: RuntimeDefault
+
+

As of 1.22 (in alpha, stable as of 1.27), the above RuntimeDefault can be used for all Pods on a Node using a single kubelet flag, --seccomp-default. Then the profile specified in securityContext is only needed for other profiles.

+

It's also possible to create your own profiles for things that require additional privileges. This can be very tedious to do manually, but there are tools like Inspektor Gadget (also recommended in the network security section for generating network policies) and Security Profiles Operator that support using tools like eBPF or logs to record baseline privilege requirements as seccomp profiles. Security Profiles Operator further allows automating the deployment of recorded profiles to nodes for use by Pods and containers.

+

AppArmor and SELinux

+

AppArmor and SELinux are known as mandatory access control or MAC systems. They are similar in concept to seccomp but with different APIs and abilities, allowing access control for e.g. specific filesystem paths or network ports. Support for these tools depends on the Linux distribution, with Debian/Ubuntu supporting AppArmor and RHEL/CentOS/Bottlerocket/Amazon Linux 2023 supporting SELinux. Also see the infrastructure security section for further discussion of SELinux.

+

Both AppArmor and SELinux are integrated with Kubernetes, but as of Kubernetes 1.28 AppArmor profiles must be specified via annotations while SELinux labels can be set through the SELinuxOptions field on the security context directly.

+

As with seccomp profiles, the Security Profiles Operator mentioned above can assist with deploying profiles onto nodes in the cluster. (In the future, the project also aims to generate profiles for AppArmor and SELinux as it does for seccomp.)

+

Recommendations

+

Use Amazon GuardDuty for runtime monitoring and detecting threats to your EKS environments

+

If you do not currently have a solution for continuously monitoring EKS runtimes and analyzing EKS audit logs, and scanning for malware and other suspicious activity, Amazon strongly recommends the use of Amazon GuardDuty for customers who want a simple, fast, secure, scalable, and cost-effective one-click way to protect their AWS environments. Amazon GuardDuty is a security monitoring service that analyzes and processes foundational data sources, such as AWS CloudTrail management events, AWS CloudTrail event logs, VPC flow logs (from Amazon EC2 instances), Kubernetes audit logs, and DNS logs. It also includes EKS runtime monitoring. It uses continuously updated threat intelligence feeds, such as lists of malicious IP addresses and domains, and machine learning to identify unexpected, potentially unauthorized, and malicious activity within your AWS environment. This can include issues like escalation of privileges, use of exposed credentials, or communication with malicious IP addresses, domains, presence of malware on your Amazon EC2 instances and EKS container workloads, or discovery of suspicious API activity. GuardDuty informs you of the status of your AWS environment by producing security findings that you can view in the GuardDuty console or through Amazon EventBridge. GuardDuty also provides support for you to export your findings to an Amazon Simple Storage Service (S3) bucket, and integrate with other services such as AWS Security Hub and Detective.

+

Watch this AWS Online Tech Talk "Enhanced threat detection for Amazon EKS with Amazon GuardDuty - AWS Online Tech Talks" to see how to enable these additional EKS security features step-by-step in minutes.

+

Optionally: Use a 3rd party solution for runtime monitoring

+

Creating and managing seccomp and Apparmor profiles can be difficult if you're not familiar with Linux security. If you don't have the time to become proficient, consider using a 3rd party commercial solution. A lot of them have moved beyond static profiles like Apparmor and seccomp and have begun using machine learning to block or alert on suspicious activity. A handful of these solutions can be found below in the tools section. Additional options can be found on the AWS Marketplace for Containers.

+

Consider add/dropping Linux capabilities before writing seccomp policies

+

Capabilities involve various checks in kernel functions reachable by syscalls. If the check fails, the syscall typically returns an error. The check can be done either right at the beginning of a specific syscall, or deeper in the kernel in areas that might be reachable through multiple different syscalls (such as writing to a specific privileged file). Seccomp, on the other hand, is a syscall filter which is applied to all syscalls before they are run. A process can set up a filter which allows them to revoke their right to run certain syscalls, or specific arguments for certain syscalls.

+

Before using seccomp, consider whether adding/removing Linux capabilities gives you the control you need. See Setting capabilities for- containers for further information.

+

See whether you can accomplish your aims by using Pod Security Policies (PSPs)

+

Pod Security Policies offer a lot of different ways to improve your security posture without introducing undue complexity. Explore the options available in PSPs before venturing into building seccomp and Apparmor profiles.

+
+

Warning

+

As of Kubernetes 1.25, PSPs have been removed and replaced with the Pod Security Admission controller. Third-party alternatives which exist include OPA/Gatekeeper and Kyverno. A collection of Gatekeeper constraints and constraint templates for implementing policies commonly found in PSPs can be pulled from the Gatekeeper library repository on GitHub. And many replacements for PSPs can be found in the Kyverno policy library including the full collection of Pod Security Standards.

+
+

Tools and Resources

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..68cf79c40 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,913 @@ + + + + None./ + 2024-10-18 + daily + + + + + Nonecluster-autoscaling/ + 2024-10-18 + daily + + + + + Nonecost_optimization/awareness/ + 2024-10-18 + daily + + + + + Nonecost_optimization/cfm_framework/ + 2024-10-18 + daily + + + + + Nonecost_optimization/cost_opt_compute/ + 2024-10-18 + daily + + + + + Nonecost_optimization/cost_opt_networking/ + 2024-10-18 + daily + + + + + Nonecost_optimization/cost_opt_observability/ + 2024-10-18 + daily + + + + + Nonecost_optimization/cost_opt_storage/ + 2024-10-18 + daily + + + + + Nonecost_optimization/cost_optimization_index/ + 2024-10-18 + daily + + + + + Nonecost_optimization/optimizing_WIP/ + 2024-10-18 + daily + + + + + Nonekarpenter/ + 2024-10-18 + daily + + + + + Nonenetworking/custom-networking/ + 2024-10-18 + daily + + + + + Nonenetworking/index/ + 2024-10-18 + daily + + + + + Nonenetworking/ip-optimization-strategies/ + 2024-10-18 + daily + + + + + Nonenetworking/ipv6/ + 2024-10-18 + daily + + + + + Nonenetworking/ipvs/ + 2024-10-18 + daily + + + + + Nonenetworking/loadbalancing/loadbalancing/ + 2024-10-18 + daily + + + + + Nonenetworking/monitoring/ + 2024-10-18 + daily + + + + + Nonenetworking/prefix-mode/index_linux/ + 2024-10-18 + daily + + + + + Nonenetworking/prefix-mode/index_windows/ + 2024-10-18 + daily + + + + + Nonenetworking/sgpp/ + 2024-10-18 + daily + + + + + Nonenetworking/subnets/ + 2024-10-18 + daily + + + + + Nonenetworking/vpc-cni/ + 2024-10-18 + daily + + + + + Noneperformance/performance_WIP/ + 2024-10-18 + daily + + + + + Nonereliability/docs/ + 2024-10-18 + daily + + + + + Nonereliability/docs/application/ + 2024-10-18 + daily + + + + + Nonereliability/docs/controlplane/ + 2024-10-18 + daily + + + + + Nonereliability/docs/dataplane/ + 2024-10-18 + daily + + + + + Nonescalability/docs/ + 2024-10-18 + daily + + + + + Nonescalability/docs/cluster-services/ + 2024-10-18 + daily + + + + + Nonescalability/docs/control-plane/ + 2024-10-18 + daily + + + + + Nonescalability/docs/data-plane/ + 2024-10-18 + daily + + + + + Nonescalability/docs/kcp_monitoring/ + 2024-10-18 + daily + + + + + Nonescalability/docs/kubernetes_slos/ + 2024-10-18 + daily + + + + + Nonescalability/docs/node_efficiency/ + 2024-10-18 + daily + + + + + Nonescalability/docs/quotas/ + 2024-10-18 + daily + + + + + Nonescalability/docs/scaling_theory/ + 2024-10-18 + daily + + + + + Nonescalability/docs/workloads/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/compliance/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/data/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/detective/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/hosts/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/iam/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/image/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/incidents/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/multiaccount/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/multitenancy/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/network/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/pods/ + 2024-10-18 + daily + + + + + Nonesecurity/docs/runtime/ + 2024-10-18 + daily + + + + + Noneupgrades/ + 2024-10-18 + daily + + + + + Nonewindows/docs/ami/ + 2024-10-18 + daily + + + + + Nonewindows/docs/gmsa/ + 2024-10-18 + daily + + + + + Nonewindows/docs/hardening/ + 2024-10-18 + daily + + + + + Nonewindows/docs/images/ + 2024-10-18 + daily + + + + + Nonewindows/docs/licensing/ + 2024-10-18 + daily + + + + + Nonewindows/docs/logging/ + 2024-10-18 + daily + + + + + Nonewindows/docs/monitoring/ + 2024-10-18 + daily + + + + + Nonewindows/docs/networking/ + 2024-10-18 + daily + + + + + Nonewindows/docs/oom/ + 2024-10-18 + daily + + + + + Nonewindows/docs/patching/ + 2024-10-18 + daily + + + + + Nonewindows/docs/scheduling/ + 2024-10-18 + daily + + + + + Nonewindows/docs/security/ + 2024-10-18 + daily + + + + + Nonewindows/docs/storage/ + 2024-10-18 + daily + + + + + Noneko/ + 2024-10-18 + daily + + + + + Noneko/cluster-autoscaling/ + 2024-10-18 + daily + + + + + Noneko/cost_optimization/awareness/ + 2024-10-18 + daily + + + + + Noneko/cost_optimization/cfm_framework/ + 2024-10-18 + daily + + + + + Noneko/cost_optimization/cost_opt_compute/ + 2024-10-18 + daily + + + + + Noneko/cost_optimization/cost_opt_networking/ + 2024-10-18 + daily + + + + + Noneko/cost_optimization/cost_opt_observability/ + 2024-10-18 + daily + + + + + Noneko/cost_optimization/cost_opt_storage/ + 2024-10-18 + daily + + + + + Noneko/cost_optimization/cost_optimization_index/ + 2024-10-18 + daily + + + + + Noneko/cost_optimization/optimizing_WIP/ + 2024-10-18 + daily + + + + + Noneko/karpenter/ + 2024-10-18 + daily + + + + + Noneko/networking/custom-networking/ + 2024-10-18 + daily + + + + + Noneko/networking/index/ + 2024-10-18 + daily + + + + + Noneko/networking/ip-optimization-strategies/ + 2024-10-18 + daily + + + + + Noneko/networking/ipv6/ + 2024-10-18 + daily + + + + + Noneko/networking/ipvs/ + 2024-10-18 + daily + + + + + Noneko/networking/loadbalancing/loadbalancing/ + 2024-10-18 + daily + + + + + Noneko/networking/monitoring/ + 2024-10-18 + daily + + + + + Noneko/networking/prefix-mode/index_linux/ + 2024-10-18 + daily + + + + + Noneko/networking/prefix-mode/index_windows/ + 2024-10-18 + daily + + + + + Noneko/networking/sgpp/ + 2024-10-18 + daily + + + + + Noneko/networking/subnets/ + 2024-10-18 + daily + + + + + Noneko/networking/vpc-cni/ + 2024-10-18 + daily + + + + + Noneko/performance/performance_WIP/ + 2024-10-18 + daily + + + + + Noneko/reliability/docs/ + 2024-10-18 + daily + + + + + Noneko/reliability/docs/application/ + 2024-10-18 + daily + + + + + Noneko/reliability/docs/controlplane/ + 2024-10-18 + daily + + + + + Noneko/reliability/docs/dataplane/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/cluster-services/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/control-plane/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/data-plane/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/kcp_monitoring/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/kubernetes_slos/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/node_efficiency/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/quotas/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/scaling_theory/ + 2024-10-18 + daily + + + + + Noneko/scalability/docs/workloads/ + 2024-10-18 + daily + + + + + Noneko/security/docs/ + 2024-10-18 + daily + + + + + Noneko/security/docs/compliance/ + 2024-10-18 + daily + + + + + Noneko/security/docs/data/ + 2024-10-18 + daily + + + + + Noneko/security/docs/detective/ + 2024-10-18 + daily + + + + + Noneko/security/docs/hosts/ + 2024-10-18 + daily + + + + + Noneko/security/docs/iam/ + 2024-10-18 + daily + + + + + Noneko/security/docs/image/ + 2024-10-18 + daily + + + + + Noneko/security/docs/incidents/ + 2024-10-18 + daily + + + + + Noneko/security/docs/multiaccount/ + 2024-10-18 + daily + + + + + Noneko/security/docs/multitenancy/ + 2024-10-18 + daily + + + + + Noneko/security/docs/network/ + 2024-10-18 + daily + + + + + Noneko/security/docs/pods/ + 2024-10-18 + daily + + + + + Noneko/security/docs/runtime/ + 2024-10-18 + daily + + + + + Noneko/upgrades/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/ami/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/gmsa/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/hardening/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/images/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/licensing/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/logging/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/monitoring/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/networking/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/oom/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/patching/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/scheduling/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/security/ + 2024-10-18 + daily + + + + + Noneko/windows/docs/storage/ + 2024-10-18 + daily + + + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 000000000..b76b10e0a Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/upgrades/index.html b/upgrades/index.html new file mode 100644 index 000000000..fcfb074c9 --- /dev/null +++ b/upgrades/index.html @@ -0,0 +1,3206 @@ + + + + + + + + + + + + + + + + + + + + + + + Cluster Upgrades - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Best Practices for Cluster Upgrades

+

This guide shows cluster administrators how to plan and execute their Amazon EKS upgrade strategy. It also describes how to upgrade self-managed nodes, managed node groups, Karpenter nodes, and Fargate nodes. It does not include guidance on EKS Anywhere, self-managed Kubernetes, AWS Outposts, or AWS Local Zones.

+

Overview

+

A Kubernetes version encompasses both the control plane and the data plane. To ensure smooth operation, both the control plane and the data plane should run the same Kubernetes minor version, such as 1.24. While AWS manages and upgrades the control plane, updating the worker nodes in the data plane is your responsibility.

+
    +
  • Control plane — The version of the control plane is determined by the Kubernetes API server. In Amazon EKS clusters, AWS takes care of managing this component. Control plane upgrades can be initiated via the AWS API.
  • +
  • Data plane — The data plane version is associated with the Kubelet versions running on your individual nodes. It's possible to have nodes in the same cluster running different versions. You can check the versions of all nodes by running kubectl get nodes.
  • +
+

Before Upgrading

+

If you're planning to upgrade your Kubernetes version in Amazon EKS, there are a few important policies, tools, and procedures you should put in place before starting an upgrade.

+
    +
  • Understand Deprecation Policies — Gain a deep understanding of how the Kubernetes deprecation policy works. Be aware of any upcoming changes that may affect your existing applications. Newer versions of Kubernetes often phase out certain APIs and features, potentially causing issues for running applications.
  • +
  • Review Kubernetes Change Log — Thoroughly review the Kubernetes change log alongside Amazon EKS Kubernetes versions to understand any possible impact to your cluster, such as breaking changes that may affect your workloads.
  • +
  • Assess Cluster Add-Ons Compatibility — Amazon EKS doesn't automatically update an add-on when new versions are released or after you update your cluster to a new Kubernetes minor version. Review Updating an add-on to understand the compatibility of any existing cluster add-ons with the cluster version you intend to upgrade to.
  • +
  • Enable Control Plane Logging — Enable control plane logging to capture logs, errors, or issues that can arise during the upgrade process. Consider reviewing these logs for any anomalies. Test cluster upgrades in a non-production environment, or integrate automated tests into your continuous integration workflow to assess version compatibility with your applications, controllers, and custom integrations.
  • +
  • Explore eksctl for Cluster Management — Consider using eksctl to manage your EKS cluster. It provides you with the ability to upgrade the control plane, manage add-ons, and handle worker node upgrades out-of-the-box.
  • +
  • Opt for Managed Node Groups or EKS on Fargate — Streamline and automate worker node upgrades by using EKS managed node groups or EKS on Fargate. These options simplify the process and reduce manual intervention.
  • +
  • Utilize kubectl Convert Plugin — Leverage the kubectl convert plugin to facilitate the conversion of Kubernetes manifest files between different API versions. This can help ensure that your configurations remain compatible with the new Kubernetes version.
  • +
+

Keep your cluster up-to-date

+

Staying current with Kubernetes updates is paramount for a secure and efficient EKS environment, reflecting the shared responsibility model in Amazon EKS. By integrating these strategies into your operational workflow, you're positioning yourself to maintain up-to-date, secure clusters that take full advantage of the latest features and improvements. Tactics:

+
    +
  • Supported Version Policy — Aligned with the Kubernetes community, Amazon EKS typically provides three active Kubernetes versions. A Kubernetes minor version is under standard support in Amazon EKS for the first 14 months after it's released. Once a version is past the end of standard support date, it enters extended support for the next 12 months. Deprecation notices are issued at least 60 days before a version reaches its end of standard support date. For more details, refer to the EKS Version Lifecycle docs.
  • +
  • Auto-Upgrade Policy — We strongly recommend staying in sync with Kubernetes updates in your EKS cluster. Clusters running on a Kubernetes version that has completed its 26-month lifecycle (14 months of standard support plus 12 months of extended support) will be auto-upgraded to the next version. Note that you can disable extended support. Failure to proactively upgrade before a version's end-of-life triggers an automatic upgrade, which could disrupt your workloads and systems. For additional information, consult the EKS Version FAQs.
  • +
  • Create Upgrade Runbooks — Establish a well-documented process for managing upgrades. As part of your proactive approach, develop runbooks and specialized tools tailored to your upgrade process. This not only enhances your preparedness but also simplifies complex transitions. Make it a standard practice to upgrade your clusters at least once a year. This practice aligns you with ongoing technological advancements, thereby boosting the efficiency and security of your environment.
  • +
+

Review the EKS release calendar

+

Review the EKS Kubernetes release calendar to learn when new versions are coming, and when support for specific versions end. Generally, EKS releases three minor versions of Kubernetes annually, and each minor version is supported for about 14 months.

+

Additionally, review the upstream Kubernetes release information.

+

Understand how the shared responsibility model applies to cluster upgrades

+

You are responsible for initiating upgrade for both cluster control plane as well as the data plane. Learn how to initiate an upgrade. When you initiate a cluster upgrade, AWS manages upgrading the cluster control plane. You are responsible for upgrading the data plane, including Fargate pods and other add-ons. You must validate and plan upgrades for workloads running on your cluster to ensure their availability and operations are not impacted after cluster upgrade

+

Upgrade clusters in-place

+

EKS supports an in-place cluster upgrade strategy. This maintains cluster resources, and keeps cluster configuration consistent (e.g., API endpoint, OIDC, ENIs, load balancers). This is less disruptive for cluster users, and it will use the existing workloads and resources in the cluster without requiring you to redeploy workloads or migrate external resources (e.g., DNS, storage).

+

When performing an in-place cluster upgrade, it is important to note that only one minor version upgrade can be executed at a time (e.g., from 1.24 to 1.25).

+

This means that if you need to update multiple versions, a series of sequential upgrades will be required. Planning sequential upgrades is more complicated, and has a higher risk of downtime. In this situation, evaluate a blue/green cluster upgrade strategy.

+

Upgrade your control plane and data plane in sequence

+

To upgrade a cluster you will need to take the following actions:

+
    +
  1. Review the Kubernetes and EKS release notes.
  2. +
  3. Take a backup of the cluster. (optional)
  4. +
  5. Identify and remediate deprecated and removed API usage in your workloads.
  6. +
  7. Ensure Managed Node Groups, if used, are on the same Kubernetes version as the control plane. EKS managed node groups and nodes created by EKS Fargate Profiles support 2 minor version skew between the control plane and data plane for Kubernetes version 1.27 and below. Starting 1.28 and above, EKS managed node groups and nodes created by EKS Fargate Profiles support 3 minor version skew betweeen control plane and data plane. For example, if your EKS control plane version is 1.28, you can safely use kubelet versions as old as 1.25. If your EKS version is 1.27, the oldest kubelet version you can use is 1.25.
  8. +
  9. Upgrade the cluster control plane using the AWS console or cli.
  10. +
  11. Review add-on compatibility. Upgrade your Kubernetes add-ons and custom controllers, as required.
  12. +
  13. Update kubectl.
  14. +
  15. Upgrade the cluster data plane. Upgrade your nodes to the same Kubernetes minor version as your upgraded cluster.
  16. +
+

Use the EKS Documentation to create an upgrade checklist

+

The EKS Kubernetes version documentation includes a detailed list of changes for each version. Build a checklist for each upgrade.

+

For specific EKS version upgrade guidance, review the documentation for notable changes and considerations for each version.

+ +

Upgrade add-ons and components using the Kubernetes API

+

Before you upgrade a cluster, you should understand what versions of Kubernetes components you are using. Inventory cluster components, and identify components that use the Kubernetes API directly. This includes critical cluster components such as monitoring and logging agents, cluster autoscalers, container storage drivers (e.g. EBS CSI, EFS CSI), ingress controllers, and any other workloads or add-ons that rely on the Kubernetes API directly.

+
+

Tip

+

Critical cluster components are often installed in a *-system namespace

+
kubectl get ns | grep '-system'
+
+
+

Once you have identified components that rely the Kubernetes API, check their documentation for version compatibility and upgrade requirements. For example, see the AWS Load Balancer Controller documentation for version compatibility. Some components may need to be upgraded or configuration changed before proceeding with a cluster upgrade. Some critical components to check include CoreDNS, kube-proxy, VPC CNI, and storage drivers.

+

Clusters often contain many workloads that use the Kubernetes API and are required for workload functionality such as ingress controllers, continuous delivery systems, and monitoring tools. When you upgrade an EKS cluster, you must also upgrade your add-ons and third-party tools to make sure they are compatible.

+

See the following examples of common add-ons and their relevant upgrade documentation:

+ +

Verify basic EKS requirements before upgrading

+

AWS requires certain resources in your account to complete the upgrade process. If these resources aren’t present, the cluster cannot be upgraded. A control plane upgrade requires the following resources:

+
    +
  1. Available IP addresses: Amazon EKS requires up to five available IP addresses from the subnets you specified when you created the cluster in order to update the cluster. If not, update your cluster configuration to include new cluster subnets prior to performing the version update.
  2. +
  3. EKS IAM role: The control plane IAM role is still present in the account with the necessary permissions.
  4. +
  5. If your cluster has secret encryption enabled, then make sure that the cluster IAM role has permission to use the AWS Key Management Service (AWS KMS) key.
  6. +
+

Verify available IP addresses

+

To update the cluster, Amazon EKS requires up to five available IP addresses from the subnets that you specified when you created your cluster.

+

To verify that your subnets have enough IP addresses to upgrade the cluster you can run the following command:

+
CLUSTER=<cluster name>
+aws ec2 describe-subnets --subnet-ids \
+  $(aws eks describe-cluster --name ${CLUSTER} \
+  --query 'cluster.resourcesVpcConfig.subnetIds' \
+  --output text) \
+  --query 'Subnets[*].[SubnetId,AvailabilityZone,AvailableIpAddressCount]' \
+  --output table
+
+----------------------------------------------------
+|                  DescribeSubnets                 |
++---------------------------+--------------+-------+
+|  subnet-067fa8ee8476abbd6 |  us-east-1a  |  8184 |
+|  subnet-0056f7403b17d2b43 |  us-east-1b  |  8153 |
+|  subnet-09586f8fb3addbc8c |  us-east-1a  |  8120 |
+|  subnet-047f3d276a22c6bce |  us-east-1b  |  8184 |
++---------------------------+--------------+-------+
+
+

The VPC CNI Metrics Helper may be used to create a CloudWatch dashboard for VPC metrics. +Amazon EKS recommends updating the cluster subnets using the "UpdateClusterConfiguration" API prior to beginning a Kubernetes version upgrade if you are running out of IP addresses in the subnets initially specified during cluster creation. Please verify that the new subnets you will be provided:

+
    +
  • belong to same set of AZs that are selected during cluster creation.
  • +
  • belong to the same VPC provided during cluster creation
  • +
+

Please consider associating additional CIDR blocks if the IP addresses in the existing VPC CIDR block run out. AWS enables the association of additional CIDR blocks with your existing cluster VPC, effectively expanding your IP address pool. This expansion can be accomplished by introducing additional private IP ranges (RFC 1918) or, if necessary, public IP ranges (non-RFC 1918). You must add new VPC CIDR blocks and allow VPC refresh to complete before Amazon EKS can use the new CIDR. After that, you can update the subnets based on the newly set up CIDR blocks to the VPC.

+

Verify EKS IAM role

+

To verify that the IAM role is available and has the correct assume role policy in your account you can run the following commands:

+
CLUSTER=<cluster name>
+ROLE_ARN=$(aws eks describe-cluster --name ${CLUSTER} \
+  --query 'cluster.roleArn' --output text)
+aws iam get-role --role-name ${ROLE_ARN##*/} \
+  --query 'Role.AssumeRolePolicyDocument'
+
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Principal": {
+                "Service": "eks.amazonaws.com"
+            },
+            "Action": "sts:AssumeRole"
+        }
+    ]
+}
+
+

Migrate to EKS Add-ons

+

Amazon EKS automatically installs add-ons such as the Amazon VPC CNI plugin for Kubernetes, kube-proxy, and CoreDNS for every cluster. Add-ons may be self-managed, or installed as Amazon EKS Add-ons. Amazon EKS Add-ons is an alternate way to manage add-ons using the EKS API.

+

You can use Amazon EKS Add-ons to update versions with a single command. For Example:

+
aws eks update-addon —cluster-name my-cluster —addon-name vpc-cni —addon-version version-number \
+--service-account-role-arn arn:aws:iam::111122223333:role/role-name —configuration-values '{}' —resolve-conflicts PRESERVE
+
+

Check if you have any EKS Add-ons with:

+
aws eks list-addons --cluster-name <cluster name>
+
+
+

Warning

+

EKS Add-ons are not automatically upgraded during a control plane upgrade. You must initiate EKS add-on updates, and select the desired version.

+ +
+

Learn more about what components are available as EKS Add-ons, and how to get started.

+

Learn how to supply a custom configuration to an EKS Add-on.

+

Identify and remediate removed API usage before upgrading the control plane

+

You should identify API usage of removed APIs before upgrading your EKS control plane. To do that we recommend using tools that can check a running cluster or static, rendered Kubernetes manifest files.

+

Running the check against static manifest files is generally more accurate. If run against live clusters, these tools may return false positives.

+

A deprecated Kubernetes API does not mean the API has been removed. You should check the Kubernetes Deprecation Policy to understand how API removal affects your workloads.

+

Cluster Insights

+

Cluster Insights is a feature that provides findings on issues that may impact the ability to upgrade an EKS cluster to newer versions of Kubernetes. These findings are curated and managed by Amazon EKS and offer recommendations on how to remediate them. By leveraging Cluster Insights, you can minimize the effort spent to upgrade to newer Kubernetes versions.

+

To view insights of an EKS cluster, you can run the command: +

aws eks list-insights --region <region-code> --cluster-name <my-cluster>
+
+{
+    "insights": [
+        {
+            "category": "UPGRADE_READINESS", 
+            "name": "Deprecated APIs removed in Kubernetes v1.29", 
+            "insightStatus": {
+                "status": "PASSING", 
+                "reason": "No deprecated API usage detected within the last 30 days."
+            }, 
+            "kubernetesVersion": "1.29", 
+            "lastTransitionTime": 1698774710.0, 
+            "lastRefreshTime": 1700157422.0, 
+            "id": "123e4567-e89b-42d3-a456-579642341238", 
+            "description": "Checks for usage of deprecated APIs that are scheduled for removal in Kubernetes v1.29. Upgrading your cluster before migrating to the updated APIs supported by v1.29 could cause application impact."
+        }
+    ]
+}
+

+

For a more descriptive output about the insight received, you can run the command: +

aws eks describe-insight --region <region-code> --id <insight-id> --cluster-name <my-cluster>
+

+

You also have the option to view insights in the Amazon EKS Console. After selecting your cluster from the cluster list, insight findings are located under the Upgrade Insights tab.

+

If you find a cluster insight with "status": ERROR, you must address the issue prior to performing the cluster upgrade. Run the aws eks describe-insight command which will share the following remediation advice:

+

Resources affected: +

"resources": [
+      {
+        "insightStatus": {
+          "status": "ERROR"
+        },
+        "kubernetesResourceUri": "/apis/policy/v1beta1/podsecuritypolicies/null"
+      }
+]
+

+

APIs deprecated: +

"deprecationDetails": [
+      {
+        "usage": "/apis/flowcontrol.apiserver.k8s.io/v1beta2/flowschemas", 
+        "replacedWith": "/apis/flowcontrol.apiserver.k8s.io/v1beta3/flowschemas", 
+        "stopServingVersion": "1.29", 
+        "clientStats": [], 
+        "startServingReplacementVersion": "1.26"
+      }
+]
+

+

Recommended action to take: +

"recommendation": "Update manifests and API clients to use newer Kubernetes APIs if applicable before upgrading to Kubernetes v1.26."
+

+

Utilizing cluster insights through the EKS Console or CLI help speed the process of successfully upgrading EKS cluster versions. Learn more with the following resources: +* Official EKS Docs +* Cluster Insights launch blog.

+

Kube-no-trouble

+

Kube-no-trouble is an open source command line utility with the command kubent. When you run kubent without any arguments it will use your current KubeConfig context and scan the cluster and print a report with what APIs will be deprecated and removed.

+
kubent
+
+4:17PM INF >>> Kube No Trouble `kubent` <<<
+4:17PM INF version 0.7.0 (git sha d1bb4e5fd6550b533b2013671aa8419d923ee042)
+4:17PM INF Initializing collectors and retrieving data
+4:17PM INF Target K8s version is 1.24.8-eks-ffeb93d
+4:l INF Retrieved 93 resources from collector name=Cluster
+4:17PM INF Retrieved 16 resources from collector name="Helm v3"
+4:17PM INF Loaded ruleset name=custom.rego.tmpl
+4:17PM INF Loaded ruleset name=deprecated-1-16.rego
+4:17PM INF Loaded ruleset name=deprecated-1-22.rego
+4:17PM INF Loaded ruleset name=deprecated-1-25.rego
+4:17PM INF Loaded ruleset name=deprecated-1-26.rego
+4:17PM INF Loaded ruleset name=deprecated-future.rego
+__________________________________________________________________________________________
+>>> Deprecated APIs removed in 1.25 <<<
+------------------------------------------------------------------------------------------
+KIND                NAMESPACE     NAME             API_VERSION      REPLACE_WITH (SINCE)
+PodSecurityPolicy   <undefined>   eks.privileged   policy/v1beta1   <removed> (1.21.0)
+
+

It can also be used to scan static manifest files and helm packages. It is recommended to run kubent as part of a continuous integration (CI) process to identify issues before manifests are deployed. Scanning manifests is also more accurate than scanning live clusters.

+

Kube-no-trouble provides a sample Service Account and Role with the appropriate permissions for scanning the cluster.

+

Pluto

+

Another option is pluto which is similar to kubent because it supports scanning a live cluster, manifest files, helm charts and has a GitHub Action you can include in your CI process.

+
pluto detect-all-in-cluster
+
+NAME             KIND                VERSION          REPLACEMENT   REMOVED   DEPRECATED   REPL AVAIL  
+eks.privileged   PodSecurityPolicy   policy/v1beta1                 false     true         true
+
+

Resources

+

To verify that your cluster don't use deprecated APIs before the upgrade, you should monitor:

+
    +
  • metric apiserver_requested_deprecated_apis since Kubernetes v1.19:
  • +
+
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis
+
+apiserver_requested_deprecated_apis{group="policy",removed_release="1.25",resource="podsecuritypolicies",subresource="",version="v1beta1"} 1
+
+
    +
  • events in the audit logs with k8s.io/deprecated set to true:
  • +
+
CLUSTER="<cluster_name>"
+QUERY_ID=$(aws logs start-query \
+ --log-group-name /aws/eks/${CLUSTER}/cluster \
+ --start-time $(date -u --date="-30 minutes" "+%s") # or date -v-30M "+%s" on MacOS \
+ --end-time $(date "+%s") \
+ --query-string 'fields @message | filter `annotations.k8s.io/deprecated`="true"' \
+ --query queryId --output text)
+
+echo "Query started (query id: $QUERY_ID), please hold ..." && sleep 5 # give it some time to query
+
+aws logs get-query-results --query-id $QUERY_ID
+
+

Which will output lines if deprecated APIs are in use:

+
{
+    "results": [
+        [
+            {
+                "field": "@message",
+                "value": "{\"kind\":\"Event\",\"apiVersion\":\"audit.k8s.io/v1\",\"level\":\"Request\",\"auditID\":\"8f7883c6-b3d5-42d7-967a-1121c6f22f01\",\"stage\":\"ResponseComplete\",\"requestURI\":\"/apis/policy/v1beta1/podsecuritypolicies?allowWatchBookmarks=true\\u0026resourceVersion=4131\\u0026timeout=9m19s\\u0026timeoutSeconds=559\\u0026watch=true\",\"verb\":\"watch\",\"user\":{\"username\":\"system:apiserver\",\"uid\":\"8aabfade-da52-47da-83b4-46b16cab30fa\",\"groups\":[\"system:masters\"]},\"sourceIPs\":[\"::1\"],\"userAgent\":\"kube-apiserver/v1.24.16 (linux/amd64) kubernetes/af930c1\",\"objectRef\":{\"resource\":\"podsecuritypolicies\",\"apiGroup\":\"policy\",\"apiVersion\":\"v1beta1\"},\"responseStatus\":{\"metadata\":{},\"code\":200},\"requestReceivedTimestamp\":\"2023-10-04T12:36:11.849075Z\",\"stageTimestamp\":\"2023-10-04T12:45:30.850483Z\",\"annotations\":{\"authorization.k8s.io/decision\":\"allow\",\"authorization.k8s.io/reason\":\"\",\"k8s.io/deprecated\":\"true\",\"k8s.io/removed-release\":\"1.25\"}}"
+            },
+[...]
+
+

Update Kubernetes workloads. Use kubectl-convert to update manifests

+

After you have identified what workloads and manifests need to be updated, you may need to change the resource type in your manifest files (e.g. PodSecurityPolicies to PodSecurityStandards). This will require updating the resource specification and additional research depending on what resource is being replaced.

+

If the resource type is staying the same but API version needs to be updated you can use the kubectl-convert command to automatically convert your manifest files. For example, to convert an older Deployment to apps/v1. For more information, see Install kubectl convert pluginon the Kubernetes website.

+

kubectl-convert -f <file> --output-version <group>/<version>

+

Configure PodDisruptionBudgets and topologySpreadConstraints to ensure availability of your workloads while the data plane is upgraded

+

Ensure your workloads have the proper PodDisruptionBudgets and topologySpreadConstraints to ensure availability of your workloads while the data plane is upgraded. Not every workload requires the same level of availability so you need to validate the scale and requirements of your workload.

+

Make sure workloads are spread in multiple Availability Zones and on multiple hosts with topology spreads will give a higher level of confidence that workloads will migrate to the new data plane automatically without incident.

+

Here is an example workload that will always have 80% of replicas available and spread replicas across zones and hosts

+
apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: myapp
+spec:
+  minAvailable: "80%"
+  selector:
+    matchLabels:
+      app: myapp
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: myapp
+spec:
+  replicas: 10
+  selector:
+    matchLabels:
+      app: myapp
+  template:
+    metadata:
+      labels:
+        app: myapp
+    spec:
+      containers:
+      - image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
+        name: myapp
+        resources:
+          requests:
+            cpu: "1"
+            memory: 256M
+      topologySpreadConstraints:
+      - labelSelector:
+          matchLabels:
+            app: host-zone-spread
+        maxSkew: 2
+        topologyKey: kubernetes.io/hostname
+        whenUnsatisfiable: DoNotSchedule
+      - labelSelector:
+          matchLabels:
+            app: host-zone-spread
+        maxSkew: 2
+        topologyKey: topology.kubernetes.io/zone
+        whenUnsatisfiable: DoNotSchedule
+
+

AWS Resilience Hub has added Amazon Elastic Kubernetes Service (Amazon EKS) as a supported resource. Resilience Hub provides a single place to define, validate, and track the resilience of your applications so that you can avoid unnecessary downtime caused by software, infrastructure, or operational disruptions.

+

Use Managed Node Groups or Karpenter to simplify data plane upgrades

+

Managed Node Groups and Karpenter both simplify node upgrades, but they take different approaches.

+

Managed node groups automate the provisioning and lifecycle management of nodes. This means that you can create, automatically update, or terminate nodes with a single operation.

+

For Karpenter data plane upgrades, refer to below sections: +* Use Drift for Karpenter managed nodes +* Use ExpireAfter for Karpenter managed nodes

+

Confirm version compatibility with existing nodes and the control plane

+

Before proceeding with a Kubernetes upgrade in Amazon EKS, it's vital to ensure compatibility between your managed node groups, self-managed nodes, and the control plane. Compatibility is determined by the Kubernetes version you are using, and it varies based on different scenarios. Tactics:

+
    +
  • Kubernetes v1.28+ — **** Starting from Kubernetes version 1.28 and onwards, there's a more lenient version policy for core components. Specifically, the supported skew between the Kubernetes API server and the kubelet has been extended by one minor version, going from n-2 to n-3. For example, if your EKS control plane version is 1.28, you can safely use kubelet versions as old as 1.25. This version skew is supported across AWS Fargate, managed node groups, and self-managed nodes. We highly recommend keeping your Amazon Machine Image (AMI) versions up-to-date for security reasons. Older kubelet versions might pose security risks due to potential Common Vulnerabilities and Exposures (CVEs), which could outweigh the benefits of using older kubelet versions.
  • +
  • Kubernetes < v1.28 — If you are using a version older than v1.28, the supported skew between the API server and the kubelet is n-2. For example, if your EKS version is 1.27, the oldest kubelet version you can use is 1.25. This version skew is applicable across AWS Fargate, managed node groups, and self-managed nodes.
  • +
+

Use Drift for Karpenter managed nodes

+

Karpenter’s Drift can automatically upgrade the Karpenter-provisioned nodes to stay in-sync with the EKS control plane. Refer to How to upgrade an EKS Cluster with Karpenter for more details.

+

This means that if the AMI ID specified in the Karpenter EC2 Nodeclass is updated, Karpenter will detect the drift and start replacing the nodes with the new AMI. +To understand how Karpenter manages AMIs and the different options available to Karpenter users to control the AMI upgrade process see the documentation on how to manage AMIs in Karpenter.

+

Use ExpireAfter for Karpenter managed nodes

+

Karpenter will mark nodes as expired and disrupt them after they have lived the duration specified in `spec.disruption.expireAfter. This node expiry helps to reduce security vulnerabilities and issues that can arise from long-running nodes, such as file fragmentation or memory leaks. When you set a value for expireAfter in your NodePool, this activates node expiry. For more information, see Disruption on the Karpenter website.

+

If you're using automatic AMI upgrades, ExpireAfter can periodically refresh and upgrade your nodes.

+

If it’s happened that the node is drifted, but hasn’t been cleaned up, node expiration will also replace the instance with the new AMI in EC2NodeClass.

+

Use eksctl to automate upgrades for self-managed node groups

+

Self managed node groups are EC2 instances that were deployed in your account and attached to the cluster outside of the EKS service. These are usually deployed and managed by some form of automation tooling. To upgrade self-managed node groups you should refer to your tools documentation.

+

For example, eksctl supports deleting and draining self-managed nodes.

+

Some common tools include:

+ +

Backup the cluster before upgrading

+

New versions of Kubernetes introduce significant changes to your Amazon EKS cluster. After you upgrade a cluster, you can’t downgrade it.

+

Velero is an community supported open-source tool that can be used to take backups of existing clusters and apply the backups to a new cluster.

+

Note that you can only create new clusters for Kubernetes versions currently supported by EKS. If the version your cluster is currently running is still supported and an upgrade fails, you can create a new cluster with the original version and restore the data plane. Note that AWS resources, including IAM, are not included in the backup by Velero. These resources would need to be recreated.

+

Restart Fargate deployments after upgrading the control plane

+

To upgrade Fargate data plane nodes you need to redeploy the workloads. You can identify which workloads are running on fargate nodes by listing all pods with the -o wide option. Any node name that begins with fargate- will need to be redeployed in the cluster.

+

Evaluate Blue/Green Clusters as an alternative to in-place cluster upgrades

+

Some customers prefer to do a blue/green upgrade strategy. This can have benefits, but also includes downsides that should be considered.

+

Benefits include:

+
    +
  • Possible to change multiple EKS versions at once (e.g. 1.23 to 1.25)
  • +
  • Able to switch back to the old cluster
  • +
  • Creates a new cluster which may be managed with newer systems (e.g. terraform)
  • +
  • Workloads can be migrated individually
  • +
+

Some downsides include:

+
    +
  • API endpoint and OIDC change which requires updating consumers (e.g. kubectl and CI/CD)
  • +
  • Requires 2 clusters to be run in parallel during the migration, which can be expensive and limit region capacity
  • +
  • More coordination is needed if workloads depend on each other to be migrated together
  • +
  • Load balancers and external DNS cannot easily span multiple clusters
  • +
+

While this strategy is possible to do, it is more expensive than an in-place upgrade and requires more time for coordination and workload migrations. It may be required in some situations and should be planned carefully.

+

With high degrees of automation and declarative systems like GitOps, this may be easier to do. You will need to take additional precautions for stateful workloads so data is backed up and migrated to new clusters.

+

Review these blogs posts for more information:

+ +

Track planned major changes in the Kubernetes project — Think ahead

+

Don’t look only at the next version. Review new versions of Kubernetes as they are released, and identify major changes. For example, some applications directly used the docker API, and support for Container Runtime Interface (CRI) for Docker (also known as Dockershim) was removed in Kubernetes 1.24. This kind of change requires more time to prepare for.

+

Review all documented changes for the version that you’re upgrading to, and note any required upgrade steps. Also, note any requirements or procedures that are specific to Amazon EKS managed clusters.

+ +

Specific Guidance on Feature Removals

+

Removal of Dockershim in 1.25 - Use Detector for Docker Socket (DDS)

+

The EKS Optimized AMI for 1.25 no longer includes support for Dockershim. If you have a dependency on Dockershim, e.g. you are mounting the Docker socket, you will need to remove those dependencies before upgrading your worker nodes to 1.25.

+

Find instances where you have a dependency on the Docker socket before upgrading to 1.25. We recommend using Detector for Docker Socket (DDS), a kubectl plugin..

+

Removal of PodSecurityPolicy in 1.25 - Migrate to Pod Security Standards or a policy-as-code solution

+

PodSecurityPolicy was deprecated in Kubernetes 1.21, and has been removed in Kubernetes 1.25. If you are using PodSecurityPolicy in your cluster, then you must migrate to the built-in Kubernetes Pod Security Standards (PSS) or to a policy-as-code solution before upgrading your cluster to version 1.25 to avoid interruptions to your workloads.

+

AWS published a detailed FAQ in the EKS documentation.

+

Review the Pod Security Standards (PSS) and Pod Security Admission (PSA) best practices.

+

Review the PodSecurityPolicy Deprecation blog post on the Kubernetes website.

+

Deprecation of In-Tree Storage Driver in 1.23 - Migrate to Container Storage Interface (CSI) Drivers

+

The Container Storage Interface (CSI) was designed to help Kubernetes replace its existing, in-tree storage driver mechanisms. The Amazon EBS container storage interface (CSI) migration feature is enabled by default in Amazon EKS 1.23 and later clusters. If you have pods running on a version 1.22 or earlier cluster, then you must install the Amazon EBS CSI driver before updating your cluster to version 1.23 to avoid service interruption.

+

Review the Amazon EBS CSI migration frequently asked questions.

+

Additional Resources

+

ClowdHaus EKS Upgrade Guidance

+

ClowdHaus EKS Upgrade Guidance is a CLI to aid in upgrading Amazon EKS clusters. It can analyze a cluster for any potential issues to remediate prior to upgrade.

+

GoNoGo

+

GoNoGo is an alpha-stage tool to determine the upgrade confidence of your cluster add-ons.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/ami/index.html b/windows/docs/ami/index.html new file mode 100644 index 000000000..e103ee36f --- /dev/null +++ b/windows/docs/ami/index.html @@ -0,0 +1,2221 @@ + + + + + + + + + + + + + + + + + + + + + + + AMI Management - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Amazon EKS optimized Windows AMI management

+

Windows Amazon EKS optimized AMIs are built on top of Windows Server 2019 and Windows Server 2022. They are configured to serve as the base image for Amazon EKS nodes. By default, the AMIs include the following components: +- kubelet +- kube-proxy +- AWS IAM Authenticator for Kubernetes +- csi-proxy +- containerd

+

You can programmatically retrieve the Amazon Machine Image (AMI) ID for Amazon EKS optimized AMIs by querying the AWS Systems Manager Parameter Store API. This parameter eliminates the need for you to manually look up Amazon EKS optimized AMI IDs. For more information about the Systems Manager Parameter Store API, see GetParameter. Your user account must have the ssm:GetParameter IAM permission to retrieve the Amazon EKS optimized AMI metadata.

+

The following example retrieves the AMI ID for the latest Amazon EKS optimized AMI for Windows Server 2019 LTSC Core. The version number listed in the AMI name relates to the corresponding Kubernetes build it is prepared for.

+
aws ssm get-parameter --name /aws/service/ami-windows-latest/Windows_Server-2019-English-Core-EKS_Optimized-1.21/image_id --region us-east-1 --query "Parameter.Value" --output text
+
+

Example output:

+
ami-09770b3eec4552d4e
+
+

Managing your own Amazon EKS optimized Windows AMI

+

An essential step towards production environments is maintaining the same Amazon EKS optimized Windows AMI and kubelet version across the Amazon EKS cluster.

+

Using the same version across the Amazon EKS cluster reduces the time during troubleshooting and increases cluster consistency. Amazon EC2 Image Builder helps create and maintain custom Amazon EKS optimized Windows AMIs to be used across an Amazon EKS cluster.

+

Use Amazon EC2 Image Builder to select between Windows Server versions, AWS Windows Server AMI release dates, and/or OS build version. The build components step, allows you to select between existing EKS Optimized Windows Artifacts as well as the kubelet versions. For more information: https://docs.aws.amazon.com/eks/latest/userguide/eks-custom-ami-windows.html

+

+

NOTE: Prior to selecting a base image, consult the Windows Server Version and License section for important details pertaining to release channel updates.

+

Configuring faster launching for custom EKS optimized AMIs

+

When using a custom Windows Amazon EKS optimized AMI, Windows worker nodes can be launched up to 65% faster by enabling the Fast Launch feature. This feature maintains a set of pre-provisioned snapshots which have the Sysprep specialize, Windows Out of Box Experience (OOBE) steps and required reboots already completed. These snapshots are then used on subsequent launches, reducing the time to scale-out or replace nodes. Fast Launch can be only enabled for AMIs you own through the EC2 console or in the AWS CLI and the number of snapshots maintained is configurable.

+

NOTE: Fast Launch is not compatible with the default Amazon-provided EKS optimized AMI, create a custom AMI as above before attempting to enable it.

+

For more information: AWS Windows AMIs - Configure your AMI for faster launching

+

Caching Windows base layers on custom AMIs

+

Windows container images are larger than their Linux counterparts. If you are running any containerized .NET Framework-based application, the average image size is around 8.24GB. During pod scheduling, the container image must be fully pulled and extracted in the disk before the pod reaches Running status.

+

During this process, the container runtime (containerd) pulls and extracts the entire container image in the disk. The pull operation is a parallel process, meaning the container runtime pulls the container image layers in parallel. In contrast, the extraction operation occurs in a sequential process, and it is I/O intensive. Due to that, the container image can take more than 8 minutes to be fully extracted and ready to be used by the container runtime (containerd), and as a result, the pod startup time can take several minutes.

+

As mentioned in the Patching Windows Server and Container topic, there is an option to build a custom AMI with EKS. During the AMI preparation, you can add an additional EC2 Image builder component to pull all the necessary Windows container images locally and then generate the AMI. This strategy will drastically reduce the time a pod reaches the status Running.

+

On Amazon EC2 Image Builder, create a component to download the necessary images and attach it to the Image recipe. The following example pulls a specific image from a ECR repository.

+
name: ContainerdPull
+description: This component pulls the necessary containers images for a cache strategy.
+schemaVersion: 1.0
+
+phases:
+  - name: build
+    steps:
+      - name: containerdpull
+        action: ExecutePowerShell
+        inputs:
+          commands:
+            - Set-ExecutionPolicy Unrestricted -Force
+            - (Get-ECRLoginCommand).Password | docker login --username AWS --password-stdin 111000111000.dkr.ecr.us-east-1.amazonaws.com
+            - ctr image pull mcr.microsoft.com/dotnet/framework/aspnet:latest
+            - ctr image pull 111000111000.dkr.ecr.us-east-1.amazonaws.com/myappcontainerimage:latest
+
+

To make sure the following component works as expected, check if the IAM role used by EC2 Image builder (EC2InstanceProfileForImageBuilder) has the attached policies:

+

+

Blog post

+

In the following blog post, you will find a step by step on how to implement caching strategy for custom Amazon EKS Windows AMIs:

+

Speeding up Windows container launch times with EC2 Image builder and image cache strategy

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/gmsa/index.html b/windows/docs/gmsa/index.html new file mode 100644 index 000000000..785c66261 --- /dev/null +++ b/windows/docs/gmsa/index.html @@ -0,0 +1,2227 @@ + + + + + + + + + + + + + + + + + + + + + + + gMSA for Windows Containers - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Configure gMSA for Windows Pods and containers

+

What is a gMSA account

+

Windows-based applications such as .NET applications often use Active Directory as an identity provider, providing authorization/authentication using NTLM or Kerberos protocol.

+

An application server to exchange Kerberos tickets with Active Directory requires to be domain-joined. Windows containers don’t support domain joins and would not make much sense as containers are ephemeral resources, creating a burden on the Active Directory RID pool.

+

However, administrators can leverage gMSA Active Directory accounts to negotiate a Windows authentication for resources such as Windows containers, NLB, and server farms.

+

Windows container and gMSA use case

+

Applications that leverage on Windows authentication, and run as Windows containers, benefit from gMSA because the Windows Node is used to exchange the Kerberos ticket on behalf of the container.There are two options available to setup the Windows worker node to support gMSA integration:

+

1 - Domain-joined Windows worker nodes

+

In this setup, the Windows worker node is domain-joined in the Active Directory domain, and the AD Computer account of the Windows worker nodes is used to authenticate against Active Directory and retrieve the gMSA identity to be used with the pod.

+

In the domain-joined approach, you can easily manage and harden your Windows worker nodes using existing Active Directory GPOs; however, it generates additional operational overhead and delays during Windows worker node joining in the Kubernetes cluster, as it requires additional reboots during node startup and Active Directory garage cleaning after the Kubernetes cluster terminates nodes.

+

In the following blog post, you will find a detailed step-by-step on how to implement the Domain-joined Windows worker node approach:

+

Windows Authentication on Amazon EKS Windows pods

+

2 - Domainless Windows worker nodes

+

In this setup, the Windows worker node isn't joined in the Active Directory domain, and a "portable" identity (user/password) is used to authenticate against Active Directory and retrieve the gMSA identity to be used with the pod.

+

+

The portable identity is an Active Directory user; the identity (user/password) is stored on AWS Secrets Manager or AWS System Manager Parameter Store, and an AWS-developed plugin called ccg_plugin will be used to retrieve this identity from AWS Secrets Manager or AWS System Manager Parameter Store and pass it to containerd to retrieve the gMSA identity and made it available for the pod.

+

In this domainless approach, you can benefit from not having any Active Directory interaction during Windows worker node startup when using gMSA and reducing the operational overhead for Active Directory administrators.

+

In the following blog post, you will find a detailed step-by-step on how to implement the Domainless Windows worker node approach:

+

Domainless Windows Authentication for Amazon EKS Windows pods

+

Important note

+

Despite the pod being able to use a gMSA account, it is necessary to also setup the application or service accordingly to support Windows authentication, for instance, in order to setup Microsoft IIS to support Windows authentication, you should prepared it via dockerfile:

+
RUN Install-WindowsFeature -Name Web-Windows-Auth -IncludeAllSubFeature
+RUN Import-Module WebAdministration; Set-ItemProperty 'IIS:\AppPools\SiteName' -name processModel.identityType -value 2
+RUN Import-Module WebAdministration; Set-WebConfigurationProperty -Filter '/system.webServer/security/authentication/anonymousAuthentication' -Name Enabled -Value False -PSPath 'IIS:\' -Location 'SiteName'
+RUN Import-Module WebAdministration; Set-WebConfigurationProperty -Filter '/system.webServer/security/authentication/windowsAuthentication' -Name Enabled -Value True -PSPath 'IIS:\' -Location 'SiteName'
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/hardening/index.html b/windows/docs/hardening/index.html new file mode 100644 index 000000000..d35964870 --- /dev/null +++ b/windows/docs/hardening/index.html @@ -0,0 +1,2249 @@ + + + + + + + + + + + + + + + + + + + + + + + Windows Server Hardening - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Windows worker nodes hardening

+

OS Hardening is a combination of OS configuration, patching, and removing unnecessary software packages, which aim to lock down a system and reduce the attack surface. It is a best practice to prepare your own EKS Optimized Windows AMI with the hardening configurations required by your company.

+

AWS provides a new EKS Optimized Windows AMI every month containing the latest Windows Server Security Patches. However, it is still the user's responsibility to harden their AMI by applying the necessary OS configurations regardless of whether they use self-managed or managed node groups.

+

Microsoft offers a range of tools like Microsoft Security Compliance Toolkit and Security Baselines that helps you to achieve hardening based on your security policies needs. CIS Benchmarks are also available and should be implemented on top of an Amazon EKS Optimized Windows AMI for production environments.

+

Reducing attack surface with Windows Server Core

+

Windows Server Core is a minimal installation option that is available as part of the EKS Optimized Windows AMI. Deploying Windows Server Core has a couple of benefits. First, it has a relatively small disk footprint, being 6GB on Server Core against 10GB on Windows Server with Desktop experience. Second, it has a smaller attack surface because of its smaller code base and available APIs.

+

AWS provides customers with new Amazon EKS Optimized Windows AMIs every month, containing the latest Microsoft security patches, regardless of the Amazon EKS-supported version. As a best practice, Windows worker nodes must be replaced with new ones based on the latest Amazon EKS-optimized AMI. Any node running for more than 45 days without an update in place or node replacement lacks security best practices.

+

Avoiding RDP connections

+

Remote Desktop Protocol (RDP) is a connection protocol developed by Microsoft to provide users with a graphical interface to connect to another Windows computer over a network.

+

As a best practice, you should treat your Windows worker nodes as if they were ephemeral hosts. That means no management connections, no updates, and no troubleshooting. Any modification and update should be implemented as a new custom AMI and replaced by updating an Auto Scaling group. See Patching Windows Servers and Containers and Amazon EKS optimized Windows AMI management.

+

Disable RDP connections on Windows nodes during the deployment by passing the value false on the ssh property, as the example below:

+
nodeGroups:
+- name: windows-ng
+  instanceType: c5.xlarge
+  minSize: 1
+  volumeSize: 50
+  amiFamily: WindowsServer2019CoreContainer
+  ssh:
+    allow: false
+
+

If access to the Windows node is needed, use AWS System Manager Session Manager to establish a secure PowerShell session through the AWS Console and SSM agent. To see how to implement the solution watch Securely Access Windows Instances Using AWS Systems Manager Session Manager

+

In order to use System Manager Session Manager an additional IAM policy must be applied to the IAM role used to launch the Windows worker node. Below is an example where the AmazonSSMManagedInstanceCore is specified in the eksctl cluster manifest:

+
 nodeGroups:
+- name: windows-ng
+  instanceType: c5.xlarge
+  minSize: 1
+  volumeSize: 50
+  amiFamily: WindowsServer2019CoreContainer
+  ssh:
+    allow: false
+  iam:
+    attachPolicyARNs:
+      - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
+      - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
+      - arn:aws:iam::aws:policy/ElasticLoadBalancingFullAccess
+      - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
+      - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
+
+

Amazon Inspector

+
+

Amazon Inspector is an automated security assessment service that helps improve the security and compliance of applications deployed on AWS. Amazon Inspector automatically assesses applications for exposure, vulnerabilities, and deviations from best practices. After performing an assessment, Amazon Inspector produces a detailed list of security findings prioritized by level of severity. These findings can be reviewed directly or as part of detailed assessment reports which are available via the Amazon Inspector console or API.

+
+

Amazon Inspector can be used to run CIS Benchmark assessment on the Windows worker node and it can be installed on a Windows Server Core by performing the following tasks:

+
    +
  1. Download the following .exe file: +https://inspector-agent.amazonaws.com/windows/installer/latest/AWSAgentInstall.exe
  2. +
  3. Transfer the agent to the Windows worker node.
  4. +
  5. Run the following command on PowerShell to install the Amazon Inspector Agent: .\AWSAgentInstall.exe /install
  6. +
+

Below is the ouput after the first run. As you can see, it generated findings based on the CVE database. You can use this to harden your Worker nodes or create an AMI based on the hardened configurations.

+

+

For more information on Amazon Inspector, including how to install Amazon Inspector agents, set up the CIS Benchmark assessment, and generate reports, watch the Improving the security and compliance of Windows Workloads with Amazon Inspector video.

+

Amazon GuardDuty

+
+

Amazon GuardDuty is a threat detection service that continuously monitors for malicious activity and unauthorized behavior to protect your AWS accounts, workloads, and data stored in Amazon S3. With the cloud, the collection and aggregation of account and network activities is simplified, but it can be time consuming for security teams to continuously analyze event log data for potential threats.

+
+

By using Amazon GuardDuty you have visilitiby on malicious actitivy against Windows worker nodes, like RDP brute force and Port Probe attacks.

+

Watch the Threat Detection for Windows Workloads using Amazon GuardDuty video to learn how to implement and run CIS Benchmarks on Optimized EKS Windows AMI

+

Security in Amazon EC2 for Windows

+

Read up on the Security best practices for Amazon EC2 Windows instances to implement security controls at every layer.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/images/associated-components.png b/windows/docs/images/associated-components.png new file mode 100644 index 000000000..26a2fffb9 Binary files /dev/null and b/windows/docs/images/associated-components.png differ diff --git a/windows/docs/images/build-components.png b/windows/docs/images/build-components.png new file mode 100644 index 000000000..580526e28 Binary files /dev/null and b/windows/docs/images/build-components.png differ diff --git a/windows/docs/images/domainless_gmsa.png b/windows/docs/images/domainless_gmsa.png new file mode 100644 index 000000000..41a02017f Binary files /dev/null and b/windows/docs/images/domainless_gmsa.png differ diff --git a/windows/docs/images/dsr.png b/windows/docs/images/dsr.png new file mode 100644 index 000000000..fedc7a8f6 Binary files /dev/null and b/windows/docs/images/dsr.png differ diff --git a/windows/docs/images/ecr-image.png b/windows/docs/images/ecr-image.png new file mode 100644 index 000000000..dc25129f1 Binary files /dev/null and b/windows/docs/images/ecr-image.png differ diff --git a/windows/docs/images/images.png b/windows/docs/images/images.png new file mode 100644 index 000000000..0be2324bf Binary files /dev/null and b/windows/docs/images/images.png differ diff --git a/windows/docs/images/index.html b/windows/docs/images/index.html new file mode 100644 index 000000000..456e06dd8 --- /dev/null +++ b/windows/docs/images/index.html @@ -0,0 +1,2075 @@ + + + + + + + + + + + + + + + + + + + + + + + Scanning Windows Images - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Container image scanning

+

Image Scanning is an automated vulnerability assessment feature that helps improve the security of your application’s container images by scanning them for a broad range of operating system vulnerabilities.

+

Currently, the Amazon Elastic Container Registry (ECR) is only able to scan Linux container image for vulnerabilities. However; there are third-party tools which can be integrated with an existing CI/CD pipeline for Windows container image scanning.

+ +

To learn more about how to integrate these solutions with Amazon Elastic Container Repository (ECR), check:

+ + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/images/inspector-agent.png b/windows/docs/images/inspector-agent.png new file mode 100644 index 000000000..72f85dca4 Binary files /dev/null and b/windows/docs/images/inspector-agent.png differ diff --git a/windows/docs/images/permissions-policies.png b/windows/docs/images/permissions-policies.png new file mode 100644 index 000000000..16d7477f8 Binary files /dev/null and b/windows/docs/images/permissions-policies.png differ diff --git a/windows/docs/images/prom.png b/windows/docs/images/prom.png new file mode 100644 index 000000000..d56026dd5 Binary files /dev/null and b/windows/docs/images/prom.png differ diff --git a/windows/docs/images/selected-components.png b/windows/docs/images/selected-components.png new file mode 100644 index 000000000..2df638d59 Binary files /dev/null and b/windows/docs/images/selected-components.png differ diff --git a/windows/docs/images/windows-networking.png b/windows/docs/images/windows-networking.png new file mode 100644 index 000000000..65e137c57 Binary files /dev/null and b/windows/docs/images/windows-networking.png differ diff --git a/windows/docs/licensing/index.html b/windows/docs/licensing/index.html new file mode 100644 index 000000000..447c9a0fc --- /dev/null +++ b/windows/docs/licensing/index.html @@ -0,0 +1,2172 @@ + + + + + + + + + + + + + + + + + + + + + + + Windows Versions and Licensing - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Windows Server version and License

+

Windows Server version

+

An Amazon EKS Optimized Windows AMI is based on Windows Server 2019 and 2022 Datacenter edition on the Long-Term Servicing Channel (LTSC). The Datacenter version doesn't have a limitation on the number of containers running on a worker node. For more information: https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/faq

+

Long-Term Servicing Channel (LTSC)

+

Formerly called the "Long-Term Servicing Branch", this is the release model you are already familiar with, where a new major version of Windows Server is released every 2-3 years. Users are entitled to 5 years of mainstream support and 5 years of extended support.

+

Licensing

+

When launching an Amazon EC2 instance with a Windows Server-based AMI, Amazon covers licensing costs and license compliance for you.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/logging/index.html b/windows/docs/logging/index.html new file mode 100644 index 000000000..e239b54d3 --- /dev/null +++ b/windows/docs/logging/index.html @@ -0,0 +1,2148 @@ + + + + + + + + + + + + + + + + + + + + + + + Logging - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Logging

+

Containerized applications typically direct application logs to STDOUT. The container runtime traps these logs and does something with them - typically writes to a file. Where these files are stored depends on the container runtime and configuration.

+

One fundamental difference with Windows pods is they do not generate STDOUT. You can run LogMonitor to retrieve the ETW (Event Tracing for Windows), Windows Event Logs and other application specific logs from running Windows containers and pipes formatted log output to STDOUT. These logs can then be streamed using fluent-bit or fluentd to your desired destination such as Amazon CloudWatch.

+

The Log collection mechanism retrieves STDOUT/STDERR logs from Kubernetes pods. A DaemonSet is a common way to collect logs from containers. It gives you the ability to manage log routing/filtering/enrichment independently of the application. A fluentd DaemonSet can be used to stream these logs and any other application generated logs to a desired log aggregator.

+

More detailed information about log streaming from Windows workloads to CloudWatch is explained here

+

Logging Recomendations

+

The general logging best practices are no different when operating Windows workloads in Kubernetes.

+
    +
  • Always log structured log entries (JSON/SYSLOG) which makes handling log entries easier as there are many pre-written parsers for such structured formats.
  • +
  • Centralize logs - dedicated logging containers can be used specifically to gather and forward log messages from all containers to a destination
  • +
  • Keep log verbosity down except when debugging. Verbosity places a lot of stress on the logging infrastructure and significant events can be lost in the noise.
  • +
  • +

    Always log the application information along with transaction/request id for traceability. Kubernetes objects do-not carry the application name, so for example a pod name windows-twryrqyw may not carry any meaning when debugging logs. This helps with traceability and troubleshooting applications with your aggregated logs.

    +

    How you generate these transaction/correlation id's depends on the programming construct. But a very common pattern is to use a logging Aspect/Interceptor, which can use MDC (Mapped diagnostic context) to inject a unique transaction/correlation id to every incoming request, like so:

    +
  • +
+
import org.slf4j.MDC;
+import java.util.UUID;
+Class LoggingAspect { //interceptor
+
+    @Before(value = "execution(* *.*(..))")
+    func before(...) {
+        transactionId = generateTransactionId();
+        MDC.put(CORRELATION_ID, transactionId);
+    }
+
+    func generateTransactionId() {
+        return UUID.randomUUID().toString();
+    }
+}
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/monitoring/index.html b/windows/docs/monitoring/index.html new file mode 100644 index 000000000..99600d6c8 --- /dev/null +++ b/windows/docs/monitoring/index.html @@ -0,0 +1,2162 @@ + + + + + + + + + + + + + + + + + + + + + + + Monitoring Windows Containers - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Monitoring

+

Prometheus, a graduated CNCF project is by far the most popular monitoring system with native integration into Kubernetes. Prometheus collects metrics around containers, pods, nodes, and clusters. Additionally, Prometheus leverages AlertsManager which lets you program alerts to warn you if something in your cluster is going wrong. Prometheus stores the metric data as a time series data identified by metric name and key/value pairs. Prometheus includes away to query using a language called PromQL, which is short for Prometheus Query Language.

+

The high level architecture of Prometheus metrics collection is shown below:

+

Prometheus Metrics collection

+

Prometheus uses a pull mechanism and scrapes metrics from targets using exporters and from the Kubernetes API using the kube state metrics. This means applications and services must expose a HTTP(S) endpoint containing Prometheus formatted metrics. Prometheus will then, as per its configuration, periodically pull metrics from these HTTP(S) endpoints.

+

An exporter lets you consume third party metrics as Prometheus formatted metrics. A Prometheus exporter is typically deployed on each node. For a complete list of exporters please refer to the Prometheus exporters. While node exporter is suited for exporting host hardware and OS metrics for linux nodes, it wont work for Windows nodes.

+

In a mixed node EKS cluster with Windows nodes when you use the stable Prometheus helm chart, you will see failed pods on the Windows nodes, as this exporter is not intended for Windows. You will need to treat the Windows worker pool separate and instead install the Windows exporter on the Windows worker node group.

+

In order to setup Prometheus monitoring for Windows nodes, you need to download and install the WMI exporter on the Windows server itself and then setup the targets inside the scrape configuration of the Prometheus configuration file. +The releases page provides all available .msi installers, with respective feature sets and bug fixes. The installer will setup the windows_exporter as a Windows service, as well as create an exception in the Windows firewall. If the installer is run without any parameters, the exporter will run with default settings for enabled collectors, ports, etc.

+

You can check out the scheduling best practices section of this guide which suggests the use of taints/tolerations or RuntimeClass to selectively deploy node exporter only to linux nodes, while the Windows exporter is installed on Windows nodes as you bootstrap the node or using a configuration management tool of your choice (example chef, Ansible, SSM etc).

+

Note that, unlike the linux nodes where the node exporter is installed as a daemonset , on Windows nodes the WMI exporter is installed on the host itself. The exporter will export metrics such as the CPU usage, the memory and the disk I/O usage and can also be used to monitor IIS sites and applications, the network interfaces and services.

+

The windows_exporter will expose all metrics from enabled collectors by default. This is the recommended way to collect metrics to avoid errors. However, for advanced use the windows_exporter can be passed an optional list of collectors to filter metrics. The collect[] parameter, in the Prometheus configuration lets you do that.

+

The default install steps for Windows include downloading and starting the exporter as a service during the bootstrapping process with arguments, such as the collectors you want to filter.

+
> Powershell Invoke-WebRequest https://github.com/prometheus-community/windows_exporter/releases/download/v0.13.0/windows_exporter-0.13.0-amd64.msi -OutFile <DOWNLOADPATH> 
+
+> msiexec /i <DOWNLOADPATH> ENABLED_COLLECTORS="cpu,cs,logical_disk,net,os,system,container,memory"
+
+

By default, the metrics can be scraped at the /metrics endpoint on port 9182. +At this point, Prometheus can consume the metrics by adding the following scrape_config to the Prometheus configuration

+
scrape_configs:
+    - job_name: "prometheus"
+      static_configs: 
+        - targets: ['localhost:9090']
+    ...
+    - job_name: "wmi_exporter"
+      scrape_interval: 10s
+      static_configs: 
+        - targets: ['<windows-node1-ip>:9182', '<windows-node2-ip>:9182', ...]
+
+

Prometheus configuration is reloaded using

+
> ps aux | grep prometheus
+> kill HUP <PID> 
+
+

A better and recommended way to add targets is to use a Custom Resource Definition called ServiceMonitor, which comes as part of the Prometheus operator] that provides the definition for a ServiceMonitor Object and a controller that will activate the ServiceMonitors we define and automatically build the required Prometheus configuration.

+

The ServiceMonitor, which declaratively specifies how groups of Kubernetes services should be monitored, is used to define an application you wish to scrape metrics from within Kubernetes. Within the ServiceMonitor we specify the Kubernetes labels that the operator can use to identify the Kubernetes Service which in turn identifies the Pods, that we wish to monitor.

+

In order to leverage the ServiceMonitor, create an Endpoint object pointing to specific Windows targets, a headless service and a ServiceMontor for the Windows nodes.

+
apiVersion: v1
+kind: Endpoints
+metadata:
+  labels:
+    k8s-app: wmiexporter
+  name: wmiexporter
+  namespace: kube-system
+subsets:
+- addresses:
+  - ip: NODE-ONE-IP
+    targetRef:
+      kind: Node
+      name: NODE-ONE-NAME
+  - ip: NODE-TWO-IP
+    targetRef:
+      kind: Node
+      name: NODE-TWO-NAME
+  - ip: NODE-THREE-IP
+    targetRef:
+      kind: Node
+      name: NODE-THREE-NAME
+  ports:
+  - name: http-metrics
+    port: 9182
+    protocol: TCP
+
+---
+apiVersion: v1
+kind: Service ##Headless Service
+metadata:
+  labels:
+    k8s-app: wmiexporter
+  name: wmiexporter
+  namespace: kube-system
+spec:
+  clusterIP: None
+  ports:
+  - name: http-metrics
+    port: 9182
+    protocol: TCP
+    targetPort: 9182
+  sessionAffinity: None
+  type: ClusterIP
+
+---
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor ##Custom ServiceMonitor Object
+metadata:
+  labels:
+    k8s-app: wmiexporter
+  name: wmiexporter
+  namespace: monitoring
+spec:
+  endpoints:
+  - interval: 30s
+    port: http-metrics
+  jobLabel: k8s-app
+  namespaceSelector:
+    matchNames:
+    - kube-system
+  selector:
+    matchLabels:
+      k8s-app: wmiexporter
+
+

For more details on the operator and the usage of ServiceMonitor, checkout the official operator documentation. Note that Prometheus does support dynamic target discovery using many service discovery options.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/networking/index.html b/windows/docs/networking/index.html new file mode 100644 index 000000000..abcd99f9a --- /dev/null +++ b/windows/docs/networking/index.html @@ -0,0 +1,2231 @@ + + + + + + + + + + + + + + + + + + + + + + + Windows Networking - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Windows Networking

+

Windows Container Networking Overview

+

Windows containers are fundamentally different than Linux containers. Linux containers use Linux constructs like namespaces, the union file system, and cgroups. On Windows, those constructs are abstracted from containerd by the Host Compute Service (HCS). HCS acts as an API layer that sits above the container implementation on Windows. Windows containers also leverage the Host Network Service (HNS) that defines the network topology on a node.

+

+

From a networking perspective, HCS and HNS make Windows containers function like virtual machines. For example, each container has a virtual network adapter (vNIC) that is connected to a Hyper-V virtual switch (vSwitch) as shown in the diagram above.

+

IP Address Management

+

A node in Amazon EKS uses it's Elastic Network Interface (ENI) to connect to an AWS VPC network. Presently, only a single ENI per Windows worker node is supported. The IP address management for Windows nodes is performed by VPC Resource Controller which runs in control plane. More details about the workflow for IP address management of Windows nodes can be found here.

+

The number of pods that a Windows worker node can support is dictated by the size of the node and the number of available IPv4 addresses. You can calculate the IPv4 address available on the node as below: +- By default, only secondary IPv4 addresses are assigned to the ENI. In such a case: +

Total IPv4 addresses available for Pods = Number of supported IPv4 addresses in the primary interface - 1
+
+ We subtract one from the total count since one IPv4 addresses will be used as the primary address of the ENI and hence cannot be allocated to the Pods.

+
    +
  • If the cluster has been configured for high pod density by enabling prefix delegation feature then- +
    Total IPv4 addresses available for Pods = (Number of supported IPv4 addresses in the primary interface - 1) * 16
    +
    + Here, instead of allocating secondary IPv4 addresses, VPC Resource Controller will allocate /28 prefixes and therefore, the overall number of available IPv4 addresses will be boosted 16 times.
  • +
+

Using the formula above, we can calculate max pods for an Windows worker noded based on a m5.large instance as below: +- By default, when running in secondary IP mode- +

10 secondary IPv4 addresses per ENI - 1 = 9 available IPv4 addresses
+
+- When using prefix delegation- +
(10 secondary IPv4 addresses per ENI - 1) * 16 = 144 available IPv4 addresses
+

+

For more information on how many IP addresses an instance type can support, see IP addresses per network interface per instance type.

+
+

Another key consideration is the flow of network traffic. With Windows there is a risk of port exhaustion on nodes with more than 100 services. When this condition arises, the nodes will start throwing errors with the following message:

+

"Policy creation failed: hcnCreateLoadBalancer failed in Win32: The specified port already exists."

+

To address this issue, we leverage Direct Server Return (DSR). DSR is an implementation of asymmetric network load distribution. In other words, the request and response traffic use different network paths. This feature speeds up communication between pods and reduces the risk of port exhaustion. We therefore recommend enabling DSR on Windows nodes.

+

DSR is enabled by default in Windows Server SAC EKS Optimized AMIs. For Windows Server 2019 LTSC EKS Optimized AMIs, you will need to enable it during instance provisioning using the script below and by using Windows Server 2019 Full or Core as the amiFamily in the eksctl nodeGroup. See eksctl custom AMI for additional information.

+

nodeGroups:
+- name: windows-ng
+  instanceType: c5.xlarge
+  minSize: 1
+  volumeSize: 50
+  amiFamily: WindowsServer2019CoreContainer
+  ssh:
+    allow: false
+
+In order to utilize DSR in Windows Server 2019 and above, you will need to specify the following kube-proxy flags during instance startup. You can do this by adjusting the userdata script associated with the self-managed node groups Launch Template.

+
<powershell>
+[string]$EKSBinDir = "$env:ProgramFiles\Amazon\EKS"
+[string]$EKSBootstrapScriptName = 'Start-EKSBootstrap.ps1'
+[string]$EKSBootstrapScriptFile = "$EKSBinDir\$EKSBootstrapScriptName"
+(Get-Content $EKSBootstrapScriptFile).replace('"--proxy-mode=kernelspace",', '"--proxy-mode=kernelspace", "--feature-gates WinDSR=true", "--enable-dsr",') | Set-Content $EKSBootstrapScriptFile 
+& $EKSBootstrapScriptFile -EKSClusterName "eks-windows" -APIServerEndpoint "https://<REPLACE-EKS-CLUSTER-CONFIG-API-SERVER>" -Base64ClusterCA "<REPLACE-EKSCLUSTER-CONFIG-DETAILS-CA>" -DNSClusterIP "172.20.0.10" -KubeletExtraArgs "--node-labels=alpha.eksctl.io/cluster-name=eks-windows,alpha.eksctl.io/nodegroup-name=windows-ng-ltsc2019 --register-with-taints=" 3>&1 4>&1 5>&1 6>&1
+</powershell>
+
+

DSR enablement can be verified following the instructions in the Microsoft Networking blog and the Windows Containers on AWS Lab.

+

+

If preserving your available IPv4 addresses and minimizing wastage is crucial for your subnet, it is generally recommended to avoid using prefix delegation mode as mentioned in Prefix Mode for Windows - When to avoid. If using prefix delegation is still desired, you can take steps to optimize IPv4 address utilization in your subnet. See Configuring Parameters for Prefix Delegation for detailed instructions on how to fine-tune the IPv4 address request and allocation process. Adjusting these configurations can help you strike a balance between conserving IPv4 addresses and pod density benefits of prefix delegation.

+

When using the default setting of assigning secondary IPv4 addresses, there are currently no supported configurations to manipulate how the VPC Resource Controller requests and allocates IPv4 addresses. More specifically, minimum-ip-target and warm-ip-target are only supported for prefix delegation mode. Also take note that in secondary IP mode, depending on the available IP addresses on the interface, the VPC Resource Controller will typically allocate 3 unused IPv4 addresses on the node on your behalf to maintain warm IPs for faster pod startup times. If you would like to minimize IP wastage of unused warm IP addresses, you could aim to schedule more pods on a given Windows node such that you use as much IP address capacity of the ENI as possible. More explicitly, you could avoid having warm unused IPs if all IP addresses on the ENI are already in use by the node and running pods. Another workaround to help you resolve constraints with IP address availability in your subnet(s) could be to explore increasing your subnet size or separating your Windows nodes into their own dedicated subnets.

+

Additionally, it's important to note that IPv6 is not supported on Windows nodes at the moment.

+

Container Network Interface (CNI) options

+

The AWSVPC CNI is the de facto CNI plugin for Windows and Linux worker nodes. While the AWSVPC CNI satisfies the needs of many customers, still there may be times when you need to consider alternatives like an overlay network to avoid IP exhaustion. In these cases, the Calico CNI can be used in place of the AWSVPC CNI. Project Calico is open source software that was developed by Tigera. That software includes a CNI that works with EKS. Instructions for installing Calico CNI in EKS can be found on the Project Calico EKS installation page.

+

Network Polices

+

It is considered a best practice to change from the default mode of open communication between pods on your Kubernetes cluster to limiting access based on network polices. The open source Project Calico has strong support for network polices that work with both Linux and Windows nodes. This feature is separate and not dependent on using the Calico CNI. We therefore recommend installing Calico and using it for network policy management.

+

Instructions for installing Calico in EKS can be found on the Installing Calico on Amazon EKS page.

+

In addition, the advice provided in the Amazon EKS Best Practices Guide for Security - Network Section applies equally to EKS clusters with Windows worker nodes, however, some features like "Security Groups for Pods" are not supported by Windows at this time.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/oom/index.html b/windows/docs/oom/index.html new file mode 100644 index 000000000..eeed879ef --- /dev/null +++ b/windows/docs/oom/index.html @@ -0,0 +1,2180 @@ + + + + + + + + + + + + + + + + + + + + + + + Memory and Systems Management - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Avoiding OOM errors

+

Windows does not have an out-of-memory process killer as Linux does. Windows always treats all user-mode memory allocations as virtual, and pagefiles are mandatory. The net effect is that Windows won't reach out of memory conditions the same way Linux does. Processes will page to disk instead of being subject to out of memory (OOM) termination. If memory is over-provisioned and all physical memory is exhausted, then paging can slow down performance.

+

Reserving system and kubelet memory

+

Different from Linux where --kubelet-reserve capture resource reservation for kubernetes system daemons like kubelet, container runtime, etc; and --system-reserve capture resource reservation for OS system daemons like sshd, udev and etc. On Windows these flags do not capture and set memory limits on kubelet or processes running on the node.

+

However, you can combine these flags to manage NodeAllocatable to reduce Capacity on the node with Pod manifest memory resource limit to control memory allocation per pod. Using this strategy you have a better control of memory allocation as well as a mechanism to minimize out-of-memory (OOM) on Windows nodes.

+

On Windows nodes, a best practice is to reserve at least 2GB of memory for the OS and process. Use --kubelet-reserve and/or --system-reserve to reduce NodeAllocatable.

+

Following the Amazon EKS Self-managed Windows nodes documentation, use the CloudFormation template to launch a new Windows node group with customizations to kubelet configuration. The CloudFormation has an element called BootstrapArguments which is the same as KubeletExtraArgs. Use with the following flags and values:

+
--kube-reserved memory=0.5Gi,ephemeral-storage=1Gi --system-reserved memory=1.5Gi,ephemeral-storage=1Gi --eviction-hard memory.available<200Mi,nodefs.available<10%"
+
+

If eksctl is the deployment tool, check the following documentation to customize the kubelet configuration https://eksctl.io/usage/customizing-the-kubelet/

+

Windows container memory requirements

+

As per Microsoft documentation, a Windows Server base image for NANO requires at least 30MB, whereas Server Core requires 45MB. These numbers grow as you add Windows components such as the .NET Framework, Web Services as IIS and applications.

+

It is essential for you to know the minimum amount of memory required by your Windows container image, i.e. the base image plus its application layers, and set it as the container's resources/requests in the pod specification. You should also set a limit to avoid pods to consume all the available node memory in case of an application issue.

+

In the example below, when the Kubernetes scheduler tries to place a pod on a node, the pod's requests are used to determine which node has sufficient resources available for scheduling.

+
 spec:
+  - name: iis
+    image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
+    resources:
+      limits:
+        cpu: 1
+        memory: 800Mi
+      requests:
+        cpu: .1
+        memory: 128Mi
+
+

Conclusion

+

Using this approach minimizes the risks of memory exhaustion but does not prevent it happen. Using Amazon CloudWatch Metrics, you can set up alerts and remediations in case of memory exhaustion occurs.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/patching/index.html b/windows/docs/patching/index.html new file mode 100644 index 000000000..2c102deb0 --- /dev/null +++ b/windows/docs/patching/index.html @@ -0,0 +1,2170 @@ + + + + + + + + + + + + + + + + + + + + + + + Infrastructure Management - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Patching Windows Servers and Containers

+

Patching Windows Server is a standard management task for Windows Administrators. This can be accomplished using different tools like Amazon System Manager - Patch Manager, WSUS, System Center Configuration Manager, and many others. However, Windows nodes in an Amazon EKS cluster should not be treated as an ordinary Windows servers. They should be treated as an immutable server. Simply put, avoid updating an existing node, just launch a new one based on an new updated AMI.

+

Using EC2 Image Builder you can automate AMIs build, by creating recipes and adding components.

+

The following example shows components, which can be pre-existing ones built by AWS (Amazon-managed) as well as the components you create (Owned by me). Pay close attention to the Amazon-managed component called update-windows, this updates Windows Server before generating the AMI through the EC2 Image Builder pipeline.

+

+

EC2 Image Builder allows you to build AMI's based off Amazon Managed Public AMIs and customize them to meet your business requirements. You can then associate those AMIs with Launch Templates which allows you to link a new AMI to the Auto Scaling Group created by the EKS Nodegroup. After that is complete, you can begin terminating the existing Windows Nodes and new ones will be launched based on the new updated AMI.

+

Pushing and pulling Windows images

+

Amazon publishes EKS optimized AMIs that include two cached Windows container images.

+
mcr.microsoft.com/windows/servercore
+mcr.microsoft.com/windows/nanoserver
+
+ +

+

Cached images are updated following the updates on the main OS. When Microsoft releases a new Windows update that directly affects the Windows container base image, the update will be launched as an ordinary Windows Update on the main OS. Keeping the environment up-to-date offers a more secure environment at the Node and Container level.

+

The size of a Windows container image influences push/pull operations which can lead to slow container startup times. Caching Windows container images allows the expensive I/O operations (file extraction) to occur on the AMI build creation instead of the container launch. As a result, all the necessary image layers will be extracted on the AMI and will be ready to be used, speeding up the time a Windows container launches and can start accepting traffic. During a push operation, only the layers that compose your image are uploaded to the repository.

+

The following example shows that on the Amazon ECR the fluentd-windows-sac2004 images have only 390.18MB. This is the amount of upload that happened during the push operation.

+

The following example shows a fluentd Windows ltsc image pushed to an Amazon ECR repository. The size of the layer stored in ECR is 533.05MB.

+

+

The output below from docker image ls , the size of the fluentd v1.14-windows-ltsc2019-1 is 6.96GB on disk, but that doesn't mean it downloaded and extracted that amount of data.

+

In practice, during the pull operation only the compressed 533.05MB will be downloaded and extracted.

+
REPOSITORY                                                              TAG                        IMAGE ID       CREATED         SIZE
+111122223333.dkr.ecr.us-east-1.amazonaws.com/fluentd-windows-coreltsc   latest                     721afca2c725   7 weeks ago     6.96GB
+fluent/fluentd                                                          v1.14-windows-ltsc2019-1   721afca2c725   7 weeks ago     6.96GB
+amazonaws.com/eks/pause-windows                                         latest                     6392f69ae6e7   10 months ago   255MB
+
+

The size column shows the overall size of image, 6.96GB. Breaking it down:

+
    +
  • Windows Server Core 2019 LTSC Base image = 5.74GB
  • +
  • Fluentd Uncompressed Base Image = 6.96GB
  • +
  • Difference on disk = 1.2GB
  • +
  • Fluentd compressed final image ECR = 533.05MB
  • +
+

The base image already exists on the local disk, resulting in the total amount on disk being 1.2GB additional. The next time you see the amount of GBs in the size column, don't worry too much, likely more than 70% is already on disk as a cached container image.

+

Reference

+

Speeding up Windows container launch times with EC2 Image builder and image cache strategy

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/scheduling/index.html b/windows/docs/scheduling/index.html new file mode 100644 index 000000000..2c8613c52 --- /dev/null +++ b/windows/docs/scheduling/index.html @@ -0,0 +1,2203 @@ + + + + + + + + + + + + + + + + + + + + + + + Scheduling - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Running Heterogeneous workloads¶

+

Kubernetes has support for heterogeneous clusters where you can have a mixture of Linux and Windows nodes in the same cluster. Within that cluster, you can have a mixture of Pods that run on Linux and Pods that run on Windows. You can even run multiple versions of Windows in the same cluster. However, there are several factors (as mentioned below) that will need to be accounted for when making this decision.

+

Assigning PODs to Nodes Best practices

+

In order to keep Linux and Windows workloads on their respective OS-specific nodes, you need to use some combination of node selectors and taints/tolerations. The main goal of scheduling workloads in a heterogeneous environment is to avoid breaking compatibility for existing Linux workloads.

+

Ensuring OS-specific workloads land on the appropriate container host

+

Users can ensure Windows containers can be scheduled on the appropriate host using nodeSelectors. All Kubernetes nodes today have the following default labels:

+
kubernetes.io/os = [windows|linux]
+kubernetes.io/arch = [amd64|arm64|...]
+
+ +

If a Pod specification does not include a nodeSelector like "kubernetes.io/os": windows, the Pod may be scheduled on any host, Windows or Linux. This can be problematic since a Windows container can only run on Windows and a Linux container can only run on Linux.

+

In Enterprise environments, it's not uncommon to have a large number of pre-existing deployments for Linux containers, as well as an ecosystem of off-the-shelf configurations, like Helm charts. In these situations, you may be hesitant to make changes to a deployment's nodeSelectors. The alternative is to use Taints.

+

For example: --register-with-taints='os=windows:NoSchedule'

+

If you are using EKS, eksctl offers ways to apply taints through clusterConfig:

+
NodeGroups:
+  - name: windows-ng
+    amiFamily: WindowsServer2022FullContainer
+    ...
+    labels:
+      nodeclass: windows2022
+    taints:
+      os: "windows:NoSchedule"
+
+

Adding a taint to all Windows nodes, the scheduler will not schedule pods on those nodes unless they tolerate the taint. Pod manifest example:

+
nodeSelector:
+    kubernetes.io/os: windows
+tolerations:
+    - key: "os"
+      operator: "Equal"
+      value: "windows"
+      effect: "NoSchedule"
+
+

Handling multiple Windows build in the same cluster

+

The Windows container base image used by each pod must match the same kernel build version as the node. If you want to use multiple Windows Server builds in the same cluster, then you should set additional node labels, nodeSelectors or leverage a label called windows-build.

+

Kubernetes 1.17 automatically adds a new label node.kubernetes.io/windows-build to simplify the management of multiple Windows build in the same cluster. If you're running an older version, then it's recommended to add this label manually to Windows nodes.

+

This label reflects the Windows major, minor, and build number that need to match for compatibility. Below are values used today for each Windows Server version.

+

It's important to note that Windows Server is moving to the Long-Term Servicing Channel (LTSC) as the primary release channel. The Windows Server Semi-Annual Channel (SAC) was retired on August 9, 2022. There will be no future SAC releases of Windows Server.

+ + + + + + + + + + + + + + + + + +
Product NameBuild Number(s)
Server full 2022 LTSC10.0.20348
Server core 2019 LTSC10.0.17763
+

It is possible to check the OS build version through the following command:

+
kubectl get nodes -o wide
+
+

The KERNEL-VERSION output matches the Windows OS build version.

+
NAME                          STATUS   ROLES    AGE   VERSION                INTERNAL-IP   EXTERNAL-IP     OS-IMAGE                         KERNEL-VERSION                  CONTAINER-RUNTIME
+ip-10-10-2-235.ec2.internal   Ready    <none>   23m   v1.24.7-eks-fb459a0    10.10.2.235   3.236.30.157    Windows Server 2022 Datacenter   10.0.20348.1607                 containerd://1.6.6
+ip-10-10-31-27.ec2.internal   Ready    <none>   23m   v1.24.7-eks-fb459a0    10.10.31.27   44.204.218.24   Windows Server 2019 Datacenter   10.0.17763.4131                 containerd://1.6.6
+ip-10-10-7-54.ec2.internal    Ready    <none>   31m   v1.24.11-eks-a59e1f0   10.10.7.54    3.227.8.172     Amazon Linux 2                   5.10.173-154.642.amzn2.x86_64   containerd://1.6.19
+
+

The example below applies an additional nodeSelector to the pod manifest in order to match the correct Windows-build version when running different Windows node groups OS versions.

+
nodeSelector:
+    kubernetes.io/os: windows
+    node.kubernetes.io/windows-build: '10.0.20348'
+tolerations:
+    - key: "os"
+    operator: "Equal"
+    value: "windows"
+    effect: "NoSchedule"
+
+

Simplifying NodeSelector and Toleration in Pod manifests using RuntimeClass

+

You can also make use of RuntimeClass to simplify the process of using taints and tolerations. This can be accomplished by creating a RuntimeClass object which is used to encapsulate these taints and tolerations.

+

Create a RuntimeClass by running the following manifest:

+
apiVersion: node.k8s.io/v1beta1
+kind: RuntimeClass
+metadata:
+  name: windows-2022
+handler: 'docker'
+scheduling:
+  nodeSelector:
+    kubernetes.io/os: 'windows'
+    kubernetes.io/arch: 'amd64'
+    node.kubernetes.io/windows-build: '10.0.20348'
+  tolerations:
+  - effect: NoSchedule
+    key: os
+    operator: Equal
+    value: "windows"
+
+

Once the Runtimeclass is created, assign it using as a Spec on the Pod manifest:

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: iis-2022
+  labels:
+    app: iis-2022
+spec:
+  replicas: 1
+  template:
+    metadata:
+      name: iis-2022
+      labels:
+        app: iis-2022
+    spec:
+      runtimeClassName: windows-2022
+      containers:
+      - name: iis
+
+

Managed Node Group Support

+

To help customers run their Windows applications in a more streamlined manner, AWS launched the support for Amazon EKS Managed Node Group (MNG) support for Windows containers on December 15, 2022. To help align operations teams, Windows MNGs are enabled using the same workflows and tools as Linux MNGs. Full and core AMI (Amazon Machine Image) family versions of Windows Server 2019 and 2022 are supported.

+

Following AMI families are supported for Managed Node Groups(MNG)s.

+ + + + + + + + + + + + + + + + + + + + +
AMI Family
WINDOWS_CORE_2019_x86_64
WINDOWS_FULL_2019_x86_64
WINDOWS_CORE_2022_x86_64
WINDOWS_FULL_2022_x86_64
+

Additional documentations

+

AWS Official Documentation: +https://docs.aws.amazon.com/eks/latest/userguide/windows-support.html

+

To better understand how Pod Networking (CNI) works, check the following link: https://docs.aws.amazon.com/eks/latest/userguide/pod-networking.html

+

AWS Blog on Deploying Managed Node Group for Windows on EKS: +https://aws.amazon.com/blogs/containers/deploying-amazon-eks-windows-managed-node-groups/

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/security/index.html b/windows/docs/security/index.html new file mode 100644 index 000000000..3a213c39b --- /dev/null +++ b/windows/docs/security/index.html @@ -0,0 +1,2119 @@ + + + + + + + + + + + + + + + + + + + + + + + Pod Security for Windows Containers - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Pod Security Contexts

+

Pod Security Policies (PSP) and Pod Security Standards (PSS) are two main ways of enforcing security in Kubernetes. Note that PodSecurityPolicy is deprecated as of Kubernetes v1.21, and will be removed in v1.25 and Pod Security Standard (PSS) is the Kubernetes recommended approach for enforcing security going forward.

+

A Pod Security Policy (PSP) is a native solution in Kubernetes to implement security policies. PSP is a cluster-level resource that controls security-sensitive aspects of the Pod specification. Using Pod Security Policy you can define a set of conditions that Pods must meet to be accepted by the cluster. +The PSP feature has been available from the early days of Kubernetes and is designed to block misconfigured pods from being created on a given cluster.

+

For more information on Pod Security Policies please reference the Kubernetes documentation. According to the Kubernetes deprecation policy, older versions will stop getting support nine months after the deprecation of the feature.

+

On the other hand, Pod Security Standards (PSS) which is the recommended security approach and typically implemented using Security Contexts are defined as part of the Pod and container specifications in the Pod manifest. PSS is the official standard that the Kubernetes project team has defined to address the security-related best practices for Pods. It defines policies such as baseline (minimally restrictive, default), privileged (unrestrictive) and restricted (most restrictive).

+

We recommend starting with the baseline profile. PSS baseline profile provides a solid balance between security and potential friction, requiring a minimal list of exceptions, it serves as a good starting point for workload security. If you are currently using PSPs we recommend switching to PSS. More details on the PSS policies can be found in the Kubernetes documentation. These policies can be enforced with several tools including those from OPA and Kyverno. For example, Kyverno provides the full collection of PSS policies here.

+

Security context settings allow one to give privileges to select processes, use program profiles to restrict capabilities to individual programs, allow privilege escalation, filter system calls, among other things.

+

Windows pods in Kubernetes have some limitations and differentiators from standard Linux-based workloads when it comes to security contexts.

+

Windows uses a Job object per container with a system namespace filter to contain all processes in a container and provide logical isolation from the host. There is no way to run a Windows container without the namespace filtering in place. This means that system privileges cannot be asserted in the context of the host, and thus privileged containers are not available on Windows.

+

The following windowsOptions are the only documented Windows Security Context options while the rest are general Security Context options

+

For a list of security context attributes that are supported in Windows vs linux, please refer to the official documentation here.

+

The Pod specific settings are applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.

+

For example, runAsUserName setting for Pods and containers which is a Windows option is a rough equivalent of the Linux-specific runAsUser setting and in the following manifest, the pod specific security context is applied to all containers

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: run-as-username-pod-demo
+spec:
+  securityContext:
+    windowsOptions:
+      runAsUserName: "ContainerUser"
+  containers:
+  - name: run-as-username-demo
+    ...
+  nodeSelector:
+    kubernetes.io/os: windows
+
+

Whereas in the following, the container level security context overrides the pod level security context.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: run-as-username-container-demo
+spec:
+  securityContext:
+    windowsOptions:
+      runAsUserName: "ContainerUser"
+  containers:
+  - name: run-as-username-demo
+    ..
+    securityContext:
+        windowsOptions:
+            runAsUserName: "ContainerAdministrator"
+  nodeSelector:
+    kubernetes.io/os: windows
+
+

Examples of acceptable values for the runAsUserName field: ContainerAdministrator, ContainerUser, NT AUTHORITY\NETWORK SERVICE, NT AUTHORITY\LOCAL SERVICE

+

It is generally a good idea to run your containers with ContainerUser for Windows pods. The users are not shared between the container and host but the ContainerAdministrator does have additional privileges with in the container. Note that, there are username limitations to be aware of.

+

A good example of when to use ContainerAdministrator is to set PATH. You can use the USER directive to do that, like so:

+
USER ContainerAdministrator
+RUN setx /M PATH "%PATH%;C:/your/path"
+USER ContainerUser
+
+

Also note that, secrets are written in clear text on the node's volume (as compared to tmpfs/in-memory on linux). This means you have to do two things

+
    +
  • Use file ACLs to secure the secrets file location
  • +
  • Use volume-level encryption using BitLocker
  • +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/windows/docs/storage/index.html b/windows/docs/storage/index.html new file mode 100644 index 000000000..c7b6b2491 --- /dev/null +++ b/windows/docs/storage/index.html @@ -0,0 +1,2329 @@ + + + + + + + + + + + + + + + + + + + + + Storage Options - EKS Best Practices Guides + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Persistent storage options

+

What is an in-tree vs. out-of-tree volume plugin?

+

Before the introduction of the Container Storage Interface (CSI), all volume plugins were in-tree meaning they were built, linked, compiled, and shipped with the core Kubernetes binaries and extend the core Kubernetes API. This meant that adding a new storage system to Kubernetes (a volume plugin) required checking code into the core Kubernetes code repository.

+

Out-of-tree volume plugins are developed independently of the Kubernetes code base, and are deployed (installed) on Kubernetes clusters as extensions. This gives vendors the ability to update drivers out-of-band, i.e. separately from the Kubernetes release cycle. This is largely possible because Kubernetes has created a storage interface or CSI that provides vendors a standard way of interfacing with k8s.

+

You can check more about Amazon Elastic Kubernetes Services (EKS) storage classes and CSI Drivers on https://docs.aws.amazon.com/eks/latest/userguide/storage.html

+

In-tree Volume Plugin for Windows

+

Kubernetes volumes enable applications, with data persistence requirements, to be deployed on Kubernetes. The management of persistent volumes consists of provisioning/de-provisioning/resizing of volumes, attaching/detaching a volume to/from a Kubernetes node, and mounting/dismounting a volume to/from individual containers in a pod. The code for implementing these volume management actions for a specific storage back-end or protocol is shipped in the form of a Kubernetes volume plugin (In-tree Volume Plugins). On Amazon Elastic Kubernetes Services (EKS) the following class of Kubernetes volume plugins are supported on Windows:

+

In-tree Volume Plugin: awsElasticBlockStore

+

In order to use In-tree volume plugin on Windows nodes, it is necessary to create an additional StorageClass to use NTFS as the fsType. On EKS, the default StorageClass uses ext4 as the default fsType.

+

A StorageClass provides a way for administrators to describe the "classes" of storage they offer. Different classes might map to quality-of-service levels, backup policies, or arbitrary policies determined by the cluster administrators. Kubernetes is unopinionated about what classes represent. This concept is sometimes called "profiles" in other storage systems.

+

You can check it by running the following command:

+
kubectl describe storageclass gp2
+
+

Output:

+
Name:            gp2
+IsDefaultClass:  Yes
+Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClas
+","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"name":"gp2"},"parameters":{"fsType"
+"ext4","type":"gp2"},"provisioner":"kubernetes.io/aws-ebs","volumeBindingMode":"WaitForFirstConsumer"}
+,storageclass.kubernetes.io/is-default-class=true
+Provisioner:           kubernetes.io/aws-ebs
+Parameters:            fsType=ext4,type=gp2
+AllowVolumeExpansion:  <unset>
+MountOptions:          <none>
+ReclaimPolicy:         Delete
+VolumeBindingMode:     WaitForFirstConsumer
+Events:                <none>
+
+

To create the new StorageClass to support NTFS, use the following manifest:

+
kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+  name: gp2-windows
+provisioner: kubernetes.io/aws-ebs
+parameters:
+  type: gp2
+  fsType: ntfs
+volumeBindingMode: WaitForFirstConsumer
+
+

Create the StorageClass by running the following command:

+
kubectl apply -f NTFSStorageClass.yaml
+
+

The next step is to create a Persistent Volume Claim (PVC).

+

A PersistentVolume (PV) is a piece of storage in the cluster that has been provisioned by an administrator or dynamically provisioned using PVC. It is a resource in the cluster just like a node is a cluster resource. This API object captures the details of the implementation of the storage, be that NFS, iSCSI, or a cloud-provider-specific storage system.

+

A PersistentVolumeClaim (PVC) is a request for storage by a user. Claims can request specific size and access modes (e.g., they can be mounted ReadWriteOnce, ReadOnlyMany or ReadWriteMany).

+

Users need PersistentVolumes with different attributes, such as performance, for different use cases. Cluster administrators need to be able to offer a variety of PersistentVolumes that differ in more ways than just size and access modes, without exposing users to the details of how those volumes are implemented. For these needs, there is the StorageClass resource.

+

In the example below, the PVC has been created within the namespace windows.

+
apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: ebs-windows-pv-claim
+  namespace: windows
+spec: 
+  accessModes:
+    - ReadWriteOnce
+  storageClassName: gp2-windows
+  resources: 
+    requests:
+      storage: 1Gi
+
+

Create the PVC by running the following command:

+
kubectl apply -f persistent-volume-claim.yaml
+
+

The following manifest creates a Windows Pod, setup the VolumeMount as C:\Data and uses the PVC as the attached storage on C:\Data.

+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: windows-server-ltsc2019
+  namespace: windows
+spec:
+  selector:
+    matchLabels:
+      app: windows-server-ltsc2019
+      tier: backend
+      track: stable
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: windows-server-ltsc2019
+        tier: backend
+        track: stable
+    spec:
+      containers:
+      - name: windows-server-ltsc2019
+        image: mcr.microsoft.com/windows/servercore:ltsc2019
+        ports:
+        - name: http
+          containerPort: 80
+        imagePullPolicy: IfNotPresent
+        volumeMounts:
+        - mountPath: "C:\\data"
+          name: test-volume
+      volumes:
+        - name: test-volume
+          persistentVolumeClaim:
+            claimName: ebs-windows-pv-claim
+      nodeSelector:
+        kubernetes.io/os: windows
+        node.kubernetes.io/windows-build: '10.0.17763'
+
+

Test the results by accessing the Windows pod via PowerShell:

+
kubectl exec -it podname powershell -n windows
+
+

Inside the Windows Pod, run: ls

+

Output:

+
PS C:\> ls
+
+
+    Directory: C:\
+
+
+Mode                 LastWriteTime         Length Name
+----                 -------------         ------ ----
+d-----          3/8/2021   1:54 PM                data
+d-----          3/8/2021   3:37 PM                inetpub
+d-r---          1/9/2021   7:26 AM                Program Files
+d-----          1/9/2021   7:18 AM                Program Files (x86)
+d-r---          1/9/2021   7:28 AM                Users
+d-----          3/8/2021   3:36 PM                var
+d-----          3/8/2021   3:36 PM                Windows
+-a----         12/7/2019   4:20 AM           5510 License.txt
+
+

The data directory is provided by the EBS volume.

+

Out-of-tree for Windows

+

Code associated with CSI plugins ship as out-of-tree scripts and binaries that are typically distributed as container images and deployed using standard Kubernetes constructs like DaemonSets and StatefulSets. CSI plugins handle a wide range of volume management actions in Kubernetes. CSI plugins typically consist of node plugins (that run on each node as a DaemonSet) and controller plugins.

+

CSI node plugins (especially those associated with persistent volumes exposed as either block devices or over a shared file-system) need to perform various privileged operations like scanning of disk devices, mounting of file systems, etc. These operations differ for each host operating system. For Linux worker nodes, containerized CSI node plugins are typically deployed as privileged containers. For Windows worker nodes, privileged operations for containerized CSI node plugins is supported using csi-proxy, a community-managed, stand-alone binary that needs to be pre-installed on each Windows node.

+

The Amazon EKS Optimized Windows AMI includes CSI-proxy starting from April 2022. Customers can use the SMB CSI Driver on Windows nodes to access Amazon FSx for Windows File Server, Amazon FSx for NetApp ONTAP SMB Shares, and/or AWS Storage Gateway – File Gateway.

+

The following blog has implementation details on how to setup SMB CSI Driver to use Amazon FSx for Windows File Server as a persistent storage for Windows Pods.

+

Amazon FSx for Windows File Server

+

An option is to use Amazon FSx for Windows File Server through an SMB feature called SMB Global Mapping which makes it possible to mount a SMB share on the host, then pass directories on that share into a container. The container doesn't need to be configured with a specific server, share, username or password - that's all handled on the host instead. The container will work the same as if it had local storage.

+
+

The SMB Global Mapping is transparent to the orchestrator, and it is mounted through HostPath which can imply in secure concerns.

+
+

In the example below, the path G:\Directory\app-state is an SMB share on the Windows Node.

+
apiVersion: v1
+kind: Pod
+metadata:
+  name: test-fsx
+spec:
+  containers:
+  - name: test-fsx
+    image: mcr.microsoft.com/windows/servercore:ltsc2019
+    command:
+      - powershell.exe
+      - -command
+      - "Add-WindowsFeature Web-Server; Invoke-WebRequest -UseBasicParsing -Uri 'https://dotnetbinaries.blob.core.windows.net/servicemonitor/2.0.1.6/ServiceMonitor.exe' -OutFile 'C:\\ServiceMonitor.exe'; echo '<html><body><br/><br/><marquee><H1>Hello EKS!!!<H1><marquee></body><html>' > C:\\inetpub\\wwwroot\\default.html; C:\\ServiceMonitor.exe 'w3svc'; "
+    volumeMounts:
+      - mountPath: C:\dotnetapp\app-state
+        name: test-mount
+  volumes:
+    - name: test-mount
+      hostPath: 
+        path: G:\Directory\app-state
+        type: Directory
+  nodeSelector:
+      beta.kubernetes.io/os: windows
+      beta.kubernetes.io/arch: amd64
+
+

The following blog has implementation details on how to setup Amazon FSx for Windows File Server as a persistent storage for Windows Pods.

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file

Overview