Update to main

This commit is contained in:
Jerome Petazzoni
2021-04-12 19:10:59 +02:00
46 changed files with 20357 additions and 160 deletions
+1 -1
View File
@@ -329,4 +329,4 @@ This is ideal to debug regressions, do side-by-side comparisons, etc.
:EN:- Connecting services together with a *Compose file*
:FR:- Utiliser Compose pour décrire son environnement
:FR:- Écrire un *Compose file* pour connecter les services entre eux
:FR:- Écrire un *Compose file* pour connecter les services entre eux
@@ -742,3 +742,15 @@ class: extra-details
* This may be used to access an internal package repository.
(But try to use a multi-stage build instead, if possible!)
???
:EN:Container networking essentials
:EN:- The Container Network Model
:EN:- Container isolation
:EN:- Service discovery
:FR:Mettre ses conteneurs en réseau
:FR:- Le "Container Network Model"
:FR:- Isolation des conteneurs
:FR:- *Service discovery*
@@ -229,10 +229,5 @@ containers together without exposing their ports.
???
:EN:Connecting containers
:EN:- Container networking basics
:EN:- Exposing a container
:FR:Connecter les conteneurs
:FR:- Description du modèle réseau des conteneurs
:FR:- Exposer un conteneur
:EN:- Exposing single containers
:FR:- Exposer un conteneur isolé
@@ -101,5 +101,5 @@ Success!
???
:EN:- The build cache
:EN:- Leveraging the build cache for faster builds
:FR:- Tirer parti du cache afin d'optimiser la vitesse de *build*
+8 -1
View File
@@ -434,5 +434,12 @@ services:
???
:EN:Optimizing images
:EN:- Dockerfile tips, tricks, and best practices
:FR:- Bonnes pratiques pour la construction des images
:EN:- Reducing build time
:EN:- Reducing image size
:FR:Optimiser ses images
:FR:- Bonnes pratiques, trucs et astuces
:FR:- Réduire le temps de build
:FR:- Réduire la taille des images
+9
View File
@@ -82,3 +82,12 @@ Use cases:
* Those containers can communicate over their `lo` interface.
<br/>(i.e. one can bind to 127.0.0.1 and the others can connect to it.)
???
:EN:Advanced container networking
:EN:- Transparent network access with the "host" driver
:EN:- Sharing is caring with the "container" driver
:FR:Paramétrage réseau avancé
:FR:- Accès transparent au réseau avec le mode "host"
:FR:- Partage de la pile réseau avece le mode "container"
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 231 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 208 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 71 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 167 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 90 KiB

@@ -0,0 +1,914 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1600"
height="900"
viewBox="0 0 1600 900"
version="1.1"
id="svg696"
sodipodi:docname="single-node-dev.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
enable-background="new">
<metadata
id="metadata700">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>how-does-k8s-work</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1080"
id="namedview698"
showgrid="false"
inkscape:zoom="0.64"
inkscape:cx="133.80574"
inkscape:cy="440.39529"
inkscape:window-x="0"
inkscape:window-y="1080"
inkscape:window-maximized="0"
inkscape:current-layer="how-does-k8s-work"
units="px"
inkscape:snap-object-midpoints="true"
inkscape:document-rotation="0" />
<title
id="title304">how-does-k8s-work</title>
<style
type="text/css"
id="style5"><![CDATA[
@font-face {
font-family: "Droid Serif";
src: url(https://fonts.gstatic.com/s/droidserif/v9/tDbI2oqRg1oM3QBjjcaDkOr9rAU.woff2) format("woff2");
}
]]></style>
<defs
id="defs483">
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker4502"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4500"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker4492"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4490"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3758"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path3756" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3586"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path3584" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2794"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2792" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2634"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2632" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2202"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2200" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2054"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2052" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2781"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2779" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2657"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2655" />
</marker>
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker2327"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path2325"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker2181"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path2179"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker2026"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path2024"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker1880"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path1878"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker1725"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1723"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker1613"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1611"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<linearGradient
id="linearGradient15544"
osb:paint="solid">
<stop
style="stop-color:#f7fe9a;stop-opacity:1;"
offset="0"
id="stop15542" />
</linearGradient>
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker15078"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path15076"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker14924"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path14922"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6635"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path6633" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6541"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path6539" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker6297"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path6295"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker4353"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS"
inkscape:collect="always">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path4351" />
</marker>
<filter
x="-0.039000001"
y="-0.096999995"
width="1.077"
height="1.181"
filterUnits="objectBoundingBox"
id="filter-1">
<feOffset
dx="0"
dy="2"
in="SourceAlpha"
result="shadowOffsetOuter1"
id="feOffset308" />
<feGaussianBlur
stdDeviation="2"
in="shadowOffsetOuter1"
result="shadowBlurOuter1"
id="feGaussianBlur310" />
<feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"
type="matrix"
in="shadowBlurOuter1"
result="shadowMatrixOuter1"
id="feColorMatrix312" />
<feMerge
id="feMerge318">
<feMergeNode
in="shadowMatrixOuter1"
id="feMergeNode314" />
<feMergeNode
in="SourceGraphic"
id="feMergeNode316" />
</feMerge>
</filter>
<filter
x="-0.039000001"
y="-0.096999995"
width="1.077"
height="1.181"
filterUnits="objectBoundingBox"
id="filter-1-3">
<feOffset
dx="0"
dy="2"
in="SourceAlpha"
result="shadowOffsetOuter1"
id="feOffset308-6" />
<feGaussianBlur
stdDeviation="2"
in="shadowOffsetOuter1"
result="shadowBlurOuter1"
id="feGaussianBlur310-7" />
<feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"
type="matrix"
in="shadowBlurOuter1"
result="shadowMatrixOuter1"
id="feColorMatrix312-5" />
<feMerge
id="feMerge318-3">
<feMergeNode
in="shadowMatrixOuter1"
id="feMergeNode314-5" />
<feMergeNode
in="SourceGraphic"
id="feMergeNode316-6" />
</feMerge>
</filter>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1101"
x="-0.023413722"
width="1.0468274"
y="-0.023627247"
height="1.0472545">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="6.3996521"
id="feGaussianBlur1103" />
</filter>
</defs>
<g
id="how-does-k8s-work"
style="display:inline;fill:none;fill-rule:evenodd;stroke:none;stroke-width:1"
transform="translate(240,90)">
<path
inkscape:connector-curvature="0"
id="path3461"
d="m 550.17888,-14.918735 c -5.79916,0.29836 -11.4811,1.76683 -16.7125,4.31926 L 305.41221,100.68854 c -11.95688,5.8319 -20.64156,16.86146 -23.59583,29.96674 l -56.2625,249.981 c -2.62478,11.6363 -0.48906,23.8532 5.92083,33.869 0.7693,1.2119 1.59668,2.3849 2.47917,3.515 l 157.85,200.44354 c 8.27676,10.5066 20.82591,16.6243 34.09583,16.6217 l 253.13751,-0.06 c 13.26496,0.01 25.81322,-6.0964 34.09583,-16.5919 L 870.92472,417.96068 c 8.28119,-10.5119 11.38389,-24.2726 8.42917,-37.384 l -56.35,-249.98098 c -2.95427,-13.10528 -11.63895,-24.13483 -23.59583,-29.96674 L 571.32472,-10.599475 c -6.58031,-3.21076 -13.85136,-4.69595 -21.14584,-4.31926 z"
style="display:inline;opacity:1;mix-blend-mode:overlay;vector-effect:none;fill:#4285f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.78722;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter1101)"
transform="matrix(1.1552713,0,0,1.1552713,-85.780113,43.857391)"
inkscape:label="control plane" />
<text
id="text3581"
y="763.69812"
x="502.07855"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:32.6588px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter-1)"
xml:space="preserve"
transform="matrix(1.4029438,0,0,1.4029438,-157.63347,-1100.6682)"
inkscape:label="control plane label"><tspan
y="763.69812"
x="502.07855"
id="tspan3579"
sodipodi:role="line">SINGLE-NODE CLUSTER (FOR DEVELOPMENT)</tspan></text>
<g
id="apiserver"
transform="translate(-160.72924,-102.29405)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,99.00177,261.15864)"
ry="5.617908"
y="135.0636"
x="427.27243"
height="125.52966"
width="231.99153"
id="rect3668"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
inkscape:label="API server" />
<text
id="text4504"
y="438.31876"
x="552.05261"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
y="438.31876"
x="552.05261"
id="tspan4502"
sodipodi:role="line">API server</tspan></text>
</g>
<g
id="controller-manager"
transform="translate(-200,22)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,205.00177,315.15864)"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
id="rect4506"
width="231.99153"
height="125.52966"
x="426.37198"
y="298.2099"
ry="5.617908"
inkscape:label="controller manager" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
x="656.36871"
y="606.2431"
id="text4510"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
sodipodi:role="line"
id="tspan4508"
x="656.36871"
y="606.2431">controller</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
id="tspan4524"
sodipodi:role="line"
x="656.36871"
y="647.06647">manager</tspan></text>
</g>
<g
id="scheduler"
transform="translate(-100,-118)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,-4.99823,167.15864)"
ry="5.617908"
y="475.73566"
x="427.94846"
height="125.52966"
width="231.99153"
id="rect4512"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
inkscape:label="scheduler" />
<text
id="text4516"
y="628.66296"
x="447.62476"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
y="628.66296"
x="447.62476"
id="tspan4514"
sodipodi:role="line">scheduler</tspan></text>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
x="560.76428"
y="764.29028"
id="text1649"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
sodipodi:role="line"
id="tspan1647"
x="560.76428"
y="764.29028">VM or container</tspan></text>
<text
id="text3666"
y="281.34979"
x="-192.40442"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:71.7295px;line-height:1.25;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79323"
xml:space="preserve"
inkscape:label="emojis"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:71.7295px;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:1.79323"
y="281.34979"
x="-192.40442"
id="tspan3664"
sodipodi:role="line">👩🏼‍💻👨🏾‍💻🤖</tspan></text>
<rect
transform="matrix(0.74849003,0,0,0.42877044,-44.82304,220.38115)"
y="149.33455"
x="-217.52838"
height="357.51495"
width="435.94931"
id="rect3662"
style="display:inline;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1-3)"
inkscape:label="terminal" />
<text
inkscape:label="commands"
id="text3656"
y="331.70175"
x="-189.80005"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35.8105px;line-height:1.25;font-family:Consolas;-inkscape-font-specification:'Consolas, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#e6e6e6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.895262"
xml:space="preserve"><tspan
style="stroke-width:0.895262"
y="331.70175"
x="-189.80005"
sodipodi:role="line"
id="tspan1145">$ kubectl ...</tspan></text>
<text
inkscape:label="thumbsup"
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:71.7295px;line-height:1.25;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79323"
x="207.5956"
y="359.34979"
id="text5150"><tspan
sodipodi:role="line"
id="tspan5148"
x="207.5956"
y="359.34979"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:71.7295px;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:1.79323" /></text>
<path
inkscape:label="arrow kubectl"
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path1135"
d="m 198.47677,432.58136 237.89885,-0.39724"
style="display:none;vector-effect:none;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#marker1613);marker-end:url(#marker1725);paint-order:normal" />
<path
inkscape:label="arrow scheduler"
style="display:inline;fill:#cccccc;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker4353);paint-order:normal"
d="m 414.32586,398.25642 -3.32843,50.64656"
id="path4349"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:label="arrow controller manager"
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path6293"
d="m 469.44118,396.89114 20.93237,189.75864"
style="display:inline;fill:#cccccc;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker6297);paint-order:normal" />
<path
inkscape:label="node top"
style="vector-effect:none;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker4502);paint-order:normal"
d="M 117.18356,331.36155 274.9691,326.32941"
id="path4488"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<g
id="etcd"
transform="translate(1.971505,-80.740088)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,99.00177,181.15864)"
style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
id="rect4518"
width="231.99153"
height="125.52966"
x="427.27246"
y="-4.9364014"
ry="5.617908"
inkscape:label="etcd" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.834657"
x="552.46265"
y="161.46683"
id="text4522"
transform="translate(0,80)"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
sodipodi:role="line"
id="tspan4520"
x="552.46265"
y="161.46683">etcd</tspan></text>
<g
id="g3228"
inkscape:label="storage"
style="display:inline"
transform="matrix(0.24039167,0,0,0.24784672,397.27503,204.48707)">
<ellipse
cx="374.0946"
cy="234.48322"
rx="92.65731"
ry="25.358843"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="ellipse9871" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="rect9873"
d="M 281.43729,235.006 V -24.92734 H 466.75191 V 235.006"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<ellipse
ry="25.358843"
rx="92.65731"
cy="-24.750473"
cx="374.0946"
id="path9869"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
</g>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:1.25;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
x="-314.87122"
y="76.790283"
id="text5696"><tspan
sodipodi:role="line"
id="tspan5694"
x="-314.87122"
y="76.790283" /></text>
<g
id="g6027"
transform="translate(173.26362,-123.65545)"
inkscape:label="kubelet">
<rect
transform="matrix(0.83465672,0,0,0.83465672,99.00177,261.15864)"
ry="5.617908"
y="135.0636"
x="427.27243"
height="125.52966"
width="231.99153"
id="rect6021"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
inkscape:label="rect" />
<text
id="text6025"
y="438.31876"
x="552.05261"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
y="438.31876"
x="552.05261"
id="tspan6023"
sodipodi:role="line">kubelet</tspan></text>
</g>
<g
id="g6159"
inkscape:label="pod"
style="display:inline"
transform="translate(-465.32975,388.44365)">
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,188.949 38.7689,-11.2425 38.7688,11.2425 -38.7688,11.24254 z"
id="path6139" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,193.25418 v 41.2523 l 36.1218,20.00898 0.1788,-50.46488 z"
id="path6141" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1254.6157,193.25418 v 41.2523 l -36.1217,20.00898 -0.1788,-50.46488 z"
id="path6143" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.0298px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.550744"
x="1195.4893"
y="274.76129"
id="text6147"><tspan
sodipodi:role="line"
id="tspan6145"
x="1195.4893"
y="274.76129"
style="stroke-width:0.550744">pod</tspan></text>
</g>
<g
id="g7100"
inkscape:label="container engine"
transform="translate(780.76442,-206.55137)">
<g
id="g7089"
transform="translate(-657.05924,68.771622)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,205.00177,315.15864)"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
id="rect7081"
width="231.99153"
height="125.52966"
x="426.37198"
y="298.2099"
ry="5.617908"
inkscape:label="controller manager" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
x="656.36871"
y="606.2431"
id="text7087"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
id="tspan7085"
sodipodi:role="line"
x="656.36871"
y="606.2431">container</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
sodipodi:role="line"
x="656.36871"
y="647.06647"
id="tspan7093">engine</tspan></text>
</g>
</g>
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path2048"
d="m 718.47061,417.59763 2.84701,-46.58932"
style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker2054);marker-end:url(#marker2202);paint-order:normal"
inkscape:label="node top left" />
<path
inkscape:label="arrow etcd"
style="display:inline;fill:#cccccc;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6635);paint-order:normal"
d="m 465.71622,277.64 30.73977,-64.3282"
id="path6537"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:label="node top"
style="display:inline;vector-effect:none;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#marker1880);marker-end:url(#marker2026);paint-order:normal"
d="m 505.55141,327.11713 103.53017,-3.03009"
id="path8569"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<g
id="g7154"
inkscape:label="pod"
style="display:inline"
transform="translate(-578.21351,370.84416)">
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,188.949 38.7689,-11.2425 38.7688,11.2425 -38.7688,11.24254 z"
id="path7144" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,193.25418 v 41.2523 l 36.1218,20.00898 0.1788,-50.46488 z"
id="path7146" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1254.6157,193.25418 v 41.2523 l -36.1217,20.00898 -0.1788,-50.46488 z"
id="path7148" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.0298px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.550744"
x="1195.4893"
y="274.76129"
id="text7152"><tspan
sodipodi:role="line"
id="tspan7150"
x="1195.4893"
y="274.76129"
style="stroke-width:0.550744">pod</tspan></text>
</g>
<g
id="g7166"
inkscape:label="pod"
style="display:inline"
transform="translate(-606.70424,238.95491)">
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,188.949 38.7689,-11.2425 38.7688,11.2425 -38.7688,11.24254 z"
id="path7156" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,193.25418 v 41.2523 l 36.1218,20.00898 0.1788,-50.46488 z"
id="path7158" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1254.6157,193.25418 v 41.2523 l -36.1217,20.00898 -0.1788,-50.46488 z"
id="path7160" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.0298px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.550744"
x="1195.4893"
y="274.76129"
id="text7164"><tspan
sodipodi:role="line"
id="tspan7162"
x="1195.4893"
y="274.76129"
style="stroke-width:0.550744">pod</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 234 KiB

+104
View File
@@ -0,0 +1,104 @@
## Accessing our EKS cluster
- We also have a shared EKS cluster
- With individual IAM users
- Let's connect to this cluster!
---
## What we need
- `kubectl` (obviously!)
- `aws` CLI (recent-ish version)
(or `aws` CLI + `aws-iam-authenticator` plugin)
- AWS API access key and secret access key
- AWS region
- EKS cluster name
---
## Setting up AWS credentials
- There are many ways to do this
- We're going to use environment variables
- You're welcome to use whatever you like (e.g. AWS profiles)
.exercise[
- Set the AWS region, API access key, and secret key:
```bash
export AWS_DEFAULT_REGION=`us-east-2`
export AWS_ACCESS_KEY_ID=`AKI...`
export AWS_SECRET_ACCESS_KEY=`xyz123...`
```
- Check that the AWS API recognizes us:
```bash
aws sts get-caller-identity
```
]
---
## Updating our kubeconfig file
- Now we can use the AWS CLI to:
- obtain the Kubernetes API address
- register it in our kubeconfig file
.exercise[
- Update our kubeconfig file:
```bash
aws eks update-kubeconfig --name `fancy-clustername-1234`
```
- Run some harmless command:
```bash
kubectl version
```
]
---
## Our resources
- We have the following permissions:
- `view` in the `default` namespace
- `edit` in the `container-training` namespace
- `admin` in our personal namespace
- Our personal namespace is our IAM user name
(but with dots replaced with dashes)
- For instance, user `ada.lovelace` has namespace `ada-lovelace`
---
## Deploying things
- Let's deploy DockerCoins in our personal namespace!
- Expose the Web UI with a `LoadBalancer` service
???
:EN:- Working with an EKS cluster
:FR:- Travailler avec un cluster EKS
+14
View File
@@ -134,3 +134,17 @@ installed and set up `kubectl` to communicate with your cluster.
:EN:- Securely accessing internal services
:FR:- Accès sécurisé aux services internes
:T: Accessing internal services from our local machine
:Q: What's the advantage of "kubectl port-forward" compared to a NodePort?
:A: It can forward arbitrary protocols
:A: It doesn't require Kubernetes API credentials
:A: It offers deterministic load balancing (instead of random)
:A: ✔️It doesn't expose the service to the public
:Q: What's the security concept behind "kubectl port-forward"?
:A: ✔️We authenticate with the Kubernetes API, and it forwards connections on our behalf
:A: It detects our source IP address, and only allows connections coming from it
:A: It uses end-to-end mTLS (mutual TLS) to authenticate our connections
:A: There is no security (as long as it's running, anyone can connect from anywhere)
+8 -6
View File
@@ -733,17 +733,19 @@ class: extra-details
## Figuring out who can do what
- For auditing purposes, sometimes we want to know who can perform an action
- For auditing purposes, sometimes we want to know who can perform which actions
- There are a few tools to help us with that
- There are a few tools to help us with that, available as `kubectl` plugins:
- [kubectl-who-can](https://github.com/aquasecurity/kubectl-who-can) by Aqua Security
- `kubectl who-can` / [kubectl-who-can](https://github.com/aquasecurity/kubectl-who-can) by Aqua Security
- [Review Access (aka Rakkess)](https://github.com/corneliusweig/rakkess)
- `kubectl access-matrix` / [Rakkess (Review Access)](https://github.com/corneliusweig/rakkess) by Cornelius Weig
- Both are available as standalone programs, or as plugins for `kubectl`
- `kubectl rbac-lookup` / [RBAC Lookup](https://github.com/FairwindsOps/rbac-lookup) by FairwindsOps
(`kubectl` plugins can be installed and managed with `krew`)
- `kubectl` plugins can be installed and managed with `krew`
- They can also be installed and executed as standalone programs
???
+20
View File
@@ -223,6 +223,24 @@ spec:
class: extra-details
## Automatic TLS Ingress with annotations
- It is also possible to annotate Ingress resources for cert-manager
- If we annotate an Ingress resource with `cert-manager.io/cluster-issuer=xxx`:
- cert-manager will detect that annotation
- it will obtain a certificate using the specified ClusterIssuer (`xxx`)
- it will store the key and certificate in the specified Secret
- Note: the Ingress still needs the `tls` section with `secretName` and `hosts`
---
class: extra-details
## Let's Encrypt and nip.io
- Let's Encrypt has [rate limits](https://letsencrypt.org/docs/rate-limits/) per domain
@@ -242,3 +260,5 @@ class: extra-details
:EN:- Obtaining certificates with cert-manager
:FR:- Obtenir des certificats avec cert-manager
:T: Obtaining TLS certificates with cert-manager
+35
View File
@@ -220,6 +220,41 @@ class: extra-details
---
class: pic
![](images/control-planes/single-node-dev.svg)
---
class: pic
![](images/control-planes/managed-kubernetes.svg)
---
class: pic
![](images/control-planes/single-control-and-workers.svg)
---
class: pic
![](images/control-planes/stacked-control-plane.svg)
---
class: pic
![](images/control-planes/non-dedicated-stacked-nodes.svg)
---
class: pic
![](images/control-planes/advanced-control-plane.svg)
---
class: pic
![](images/control-planes/advanced-control-plane-split-events.svg)
---
class: extra-details
## How many nodes should a cluster have?
+27 -13
View File
@@ -40,7 +40,22 @@
- a `Chart.yaml` file, containing metadata (name, version, description ...)
- Let's look at a simple chart, `stable/tomcat`
- Let's look at a simple chart for a basic demo app
---
## Adding the repo
- If you haven't done it before, you need to add the repo for that chart
.exercise[
- Add the repo that holds the chart for the OWASP Juice Shop:
```bash
helm repo add juice https://charts.securecodebox.io
```
]
---
@@ -50,17 +65,17 @@
.exercise[
- Download the tarball for `stable/tomcat`:
- Download the tarball for `juice/juice-shop`:
```bash
helm pull stable/tomcat
helm pull juice/juice-shop
```
(This will create a file named `tomcat-X.Y.Z.tgz`.)
(This will create a file named `juice-shop-X.Y.Z.tgz`.)
- Or, download + untar `stable/tomcat`:
- Or, download + untar `juice/juice-shop`:
```bash
helm pull stable/tomcat --untar
helm pull juice/juice-shop --untar
```
(This will create a directory named `tomcat`.)
(This will create a directory named `juice-shop`.)
]
@@ -68,13 +83,13 @@
## Looking at the chart's content
- Let's look at the files and directories in the `tomcat` chart
- Let's look at the files and directories in the `juice-shop` chart
.exercise[
- Display the tree structure of the chart we just downloaded:
```bash
tree tomcat
tree juice-shop
```
]
@@ -93,12 +108,11 @@ We see the components mentioned above: `Chart.yaml`, `templates/`, `values.yaml`
(using the standard Go template library)
.exercise[
- Look at the template file for the tomcat Service resource:
- Look at the template file for the Service resource:
```bash
cat tomcat/templates/appsrv-svc.yaml
cat juice-shop/templates/service.yaml
```
]
@@ -190,7 +204,7 @@ We see the components mentioned above: `Chart.yaml`, `templates/`, `values.yaml`
- At the top-level of the chart, it's a good idea to have a README
- It will be viewable with e.g. `helm show readme stable/tomcat`
- It will be viewable with e.g. `helm show readme juice/juice-shop`
- In the `templates/` directory, we can also have a `NOTES.txt` file
+338
View File
@@ -0,0 +1,338 @@
# Charts using other charts
- Helm charts can have *dependencies* on other charts
- These dependencies will help us to share or reuse components
(so that we write and maintain less manifests, less templates, less code!)
- As an example, we will use a community chart for Redis
- This will help people who write charts, and people who use them
- ... And potentially remove a lot of code! ✌️
---
## Redis in DockerCoins
- In the DockerCoins demo app, we have 5 components:
- 2 internal webservices
- 1 worker
- 1 public web UI
- 1 Redis data store
- Every component is running some custom code, except Redis
- Every component is using a custom image, except Redis
(which is using the official `redis` image)
- Could we use a standard chart for Redis?
- Yes! Dependencies to the rescue!
---
## Adding our dependency
- First, we will add the dependency to the `Chart.yaml` file
- Then, we will ask Helm to download that dependency
- We will also *lock* the dependency
(lock it to a specific version, to ensure reproducibility)
---
## Declaring the dependency
- First, let's edit `Chart.yaml`
.exercise[
- In `Chart.yaml`, fill the `dependencies` section:
```yaml
dependencies:
- name: redis
version: 11.0.5
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
```
]
Where do that `repository` and `version` come from?
We're assuming here that we did our reserach,
or that our resident Helm expert advised us to
use Bitnami's Redis chart.
---
## Conditions
- The `condition` field gives us a way to enable/disable the dependency:
```yaml
conditions: redis.enabled
```
- Here, we can disable Redis with the Helm flag `--set redis.enabled=false`
(or set that value in a `values.yaml` file)
- Of course, this is mostly useful for *optional* dependencies
(otherwise, the app ends up being broken since it'll miss a component)
---
## Lock & Load!
- After adding the dependency, we ask Helm to pin an download it
.exercise[
- Ask Helm:
```bash
helm dependency update
```
(Or `helm dep up`)
]
- This wil create `Chart.lock` and fetch the dependency
---
## What's `Chart.lock`?
- This is a common pattern with dependencies
(see also: `Gemfile.lock`, `package.json.lock`, and many others)
- This lets us define loose dependencies in `Chart.yaml`
(e.g. "version 11.whatever, but below 12")
- But have the exact version used in `Chart.lock`
- This ensures reproducible deployments
- `Chart.lock` can (should!) be added to our source tree
- `Chart.lock` can (should!) regularly be updated
---
## Loose dependencies
- Here is an example of loose version requirement:
```yaml
dependencies:
- name: redis
version: ">=11 <12"
repository: https://charts.bitnami.com/bitnami
```
- This makes sure that we have the most recent version in the 11.x train
- ... But without upgrading to version 12.x
(because it might be incompatible)
---
## `build` vs `update`
- Helm actually offers two commands to manage dependencies:
`helm dependency build` = fetch dependencies listed in `Chart.lock`
`helm dependency update` = update `Chart.lock` (and run `build`)
- When the dependency gets updated, we can/should:
- `helm dep up` (update `Chart.lock` and fetch new chart)
- test!
- if everything is fine, `git add Chart.lock` and commit
---
## Where are my dependencies?
- Dependencies are downloaded to the `charts/` subdirectory
- When they're downloaded, they stay in compressed format (`.tgz`)
- Should we commit them to our code repository?
- Pros:
- more resilient to internet/mirror failures/decomissioning
- Cons:
- can add a lot of weight to the repo if charts are big or change often
- this can be solved by extra tools like git-lfs
---
## Dependency tuning
- DockerCoins expects the `redis` Service to be named `redis`
- Our Redis chart uses a different Service name by default
- Service name is `{{ template "redis.fullname" . }}-master`
- `redis.fullname` looks like this:
```
{{- define "redis.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
[...]
{{- end }}
{{- end }}
```
- How do we fix this?
---
## Setting dependency variables
- If we set `fullnameOverride` to `redis`:
- the `{{ template ... }}` block will output `redis`
- the Service name will be `redis-master`
- A parent chart can set values for its dependencies
- For example, in the parent's `values.yaml`:
```yaml
redis: # Name of the dependency
fullnameOverride: redis # Value passed to redis
cluster: # Other values passed to redis
enabled: false
```
- User can also set variables with `--set=` or with `--values=`
---
class: extra-details
## Passing templates
- We can even pass template `{{ include "template.name" }}`, but warning:
- need to be evaluated with the `tpl` function, on the child side
- evaluated in the context of the child, with no access to parent variables
<!-- FIXME this probably deserves an example, but I can't imagine one right now 😅 -->
---
## Getting rid of the `-master`
- Even if we set that `fullnameOverride`, the Service name will be `redis-master`
- To remove the `-master` suffix, we need to edit the chart itself
- To edit the Redis chart, we need to *embed* it in our own chart
- We need to:
- decompress the chart
- adjust `Chart.yaml` accordingly
---
## Embedding a dependency
.exercise[
- Decompress the chart:
```yaml
cd charts
tar zxf redis-*.tgz
cd ..
```
- Edit `Chart.yaml` and update the `dependencies` section:
```yaml
dependencies:
- name: redis
version: '*' # No need to constraint version, from local files
```
- Run `helm dep update`
]
---
## Updating the dependency
- Now we can edit the Service name
(it should be in `charts/redis/templates/redis-master-svc.yaml`)
- Then try to deploy the whole chart!
---
## Embedding a dependency multiple times
- What if we need multiple copies of the same subchart?
(for instance, if we need two completely different Redis servers)
- We can declare a dependency multiple times, and specify an `alias`:
```yaml
dependencies:
- name: redis
version: '*'
alias: querycache
- name: redis
version: '*'
alias: celeryqueue
```
- `.Chart.Name` will be set to the `alias`
---
class: extra-details
## Compatibility with Helm 2
- Chart `apiVersion: v1` is the only version supported by Helm 2
- Chart v1 is also supported by Helm 3
- Use v1 if you want to be compatible with Helm 2
- Instead of `Chart.yaml`, dependencies are defined in `requirements.yaml`
(and we should commit `requirements.lock` instead of `Chart.lock`)
???
:EN:- Depending on other charts
:EN:- Charts within charts
:FR:- Dépendances entre charts
:FR:- Un chart peut en cacher un autre
+107 -72
View File
@@ -229,71 +229,95 @@ fine for personal and development clusters.)
---
## Managing repositories
- Let's check what repositories we have, and add the `stable` repo
(the `stable` repo contains a set of official-ish charts)
.exercise[
- List our repos:
```bash
helm repo list
```
- Add the `stable` repo:
```bash
helm repo add stable https://charts.helm.sh/stable
```
]
Adding a repo can take a few seconds (it downloads the list of charts from the repo).
It's OK to add a repo that already exists (it will merely update it).
---
class: extra-details
## Deprecation warning
## How to find charts, the old way
- That "stable" is being deprecated, in favor of a more decentralized approach
- Helm 2 came with one pre-configured repo, the "stable" repo
(each community / company / group / project hosting their own repository)
(located at https://charts.helm.sh/stable)
- We're going to use it here for educational purposes
- Helm 3 doesn't have any pre-configured repo
- But if you're looking for production-grade charts, look elsewhere!
- The "stable" repo mentioned above is now being deprecated
(namely, on the Helm Hub)
- The new approach is to have fully decentralized repos
- Repos can be indexed in the Artifact Hub
(which supersedes the Helm Hub)
---
## Search available charts
## How to find charts, the new way
- We can search available charts with `helm search`
- Go to the [Artifact Hub](https://artifacthub.io/packages/search?kind=0) (https://artifacthub.io)
- We need to specify where to search (only our repos, or Helm Hub)
- Or use `helm search hub ...` from the CLI
- Let's search for all charts mentioning tomcat!
- Let's try to find a Helm chart for something called "OWASP Juice Shop"!
(it is a famous demo app used in security challenges)
---
## Finding charts from the CLI
- We can use `helm search hub <keyword>`
.exercise[
- Search for tomcat in the repo that we added earlier:
- Look for the OWASP Juice Shop app:
```bash
helm search repo tomcat
helm search hub owasp juice
```
- Search for tomcat on the Helm Hub:
- Since the URLs are truncated, try with the YAML output:
```bash
helm search hub tomcat
helm search hub owasp juice -o yaml
```
]
[Helm Hub](https://hub.helm.sh/) indexes many repos, using the [Monocular](https://github.com/helm/monocular) server.
Then go to → https://artifacthub.io/packages/helm/seccurecodebox/juice-shop
---
## Finding charts on the web
- We can also use the Artifact Hub search feature
.exercise[
- Go to https://artifacthub.io/
- In the search box on top, enter "owasp juice"
- Click on the "juice-shop" result (not "multi-juicer" or "juicy-ctf")
]
---
## Installing the chart
- Click on the "Install" button, it will show instructions
.exercise[
- First, add the repository for that chart:
```bash
helm repo add juice https://charts.securecodebox.io
```
- Then, install the chart:
```bash
helm install my-juice-shop juice/juice-shop
```
]
Note: it is also possible to install directly a chart, with `--repo https://...`
---
@@ -301,22 +325,22 @@ class: extra-details
- "Installing a chart" means creating a *release*
- We need to name that release
- In the previous exemple, the release was named "my-juice-shop"
(or use the `--generate-name` to get Helm to generate one for us)
- We can also use `--generate-name` to ask Helm to generate a name for us
.exercise[
- Install the tomcat chart that we found earlier:
```bash
helm install java4ever stable/tomcat
```
- List the releases:
```bash
helm list
```
- Check that we have a `my-juice-shop-...` Pod up and running:
```bash
kubectl get pods
```
]
---
@@ -329,13 +353,13 @@ class: extra-details
- The `helm search` command only takes a search string argument
(e.g. `helm search tomcat`)
(e.g. `helm search juice-shop`)
- With Helm 2, the name is optional:
`helm install stable/tomcat` will automatically generate a name
`helm install juice/juice-shop` will automatically generate a name
`helm install --name java4ever stable/tomcat` will specify a name
`helm install --name my-juice-shop juice/juice-shop` will specify a name
---
@@ -349,12 +373,12 @@ class: extra-details
- List all the resources created by this release:
```bash
kubectl get all --selector=release=java4ever
kubectl get all --selector=app.kubernetes.io/instance=my-juice-shop
```
]
Note: this `release` label wasn't added automatically by Helm.
Note: this label wasn't added automatically by Helm.
<br/>
It is defined in that chart. In other words, not all charts will provide this label.
@@ -362,11 +386,11 @@ It is defined in that chart. In other words, not all charts will provide this la
## Configuring a release
- By default, `stable/tomcat` creates a service of type `LoadBalancer`
- By default, `juice/juice-shop` creates a service of type `ClusterIP`
- We would like to change that to a `NodePort`
- We could use `kubectl edit service java4ever-tomcat`, but ...
- We could use `kubectl edit service my-juice-shop`, but ...
... our changes would get overwritten next time we update that chart!
@@ -386,14 +410,14 @@ It is defined in that chart. In other words, not all charts will provide this la
.exercise[
- Look at the README for tomcat:
- Look at the README for the app:
```bash
helm show readme stable/tomcat
helm show readme juice/juice-shop
```
- Look at the values and their defaults:
```bash
helm show values stable/tomcat
helm show values juice/juice-shop
```
]
@@ -410,18 +434,19 @@ The `readme` may or may not have (accurate) explanations for the values.
- Values can be set when installing a chart, or when upgrading it
- We are going to update `java4ever` to change the type of the service
- We are going to update `my-juice-shop` to change the type of the service
.exercise[
- Update `java4ever`:
- Update `my-juice-shop`:
```bash
helm upgrade java4ever stable/tomcat --set service.type=NodePort
helm upgrade my-juice-shop juice/my-juice-shop \
--set service.type=NodePort
```
]
Note that we have to specify the chart that we use (`stable/tomcat`),
Note that we have to specify the chart that we use (`juice/my-juice-shop`),
even if we just want to update some values.
We can set multiple values. If we want to set many values, we can use `-f`/`--values` and pass a YAML file with all the values.
@@ -430,25 +455,21 @@ All unspecified values will take the default values defined in the chart.
---
## Connecting to tomcat
## Connecting to the Juice Shop
- Let's check the tomcat server that we just installed
- Note: its readiness probe has a 60s delay
(so it will take 60s after the initial deployment before the service works)
- Let's check the app that we just installed
.exercise[
- Check the node port allocated to the service:
```bash
kubectl get service java4ever-tomcat
PORT=$(kubectl get service java4ever-tomcat -o jsonpath={..nodePort})
kubectl get service my-juice-shop
PORT=$(kubectl get service my-juice-shop -o jsonpath={..nodePort})
```
- Connect to it, checking the demo app on `/sample/`:
- Connect to it:
```bash
curl localhost:$PORT/sample/
curl localhost:$PORT/
```
]
@@ -462,3 +483,17 @@ All unspecified values will take the default values defined in the chart.
:FR:- Fonctionnement général de Helm
:FR:- Installer des composants via Helm
:FR:- Helm 2, Helm 3, et le *Helm Hub*
:T: Getting started with Helm and its concepts
:Q: Which comparison is the most adequate?
:A: Helm is a firewall, charts are access lists
:A: ✔️Helm is a package manager, charts are packages
:A: Helm is an artefact repository, charts are artefacts
:A: Helm is a CI/CD platform, charts are CI/CD pipelines
:Q: What's required to distribute a Helm chart?
:A: A Helm commercial license
:A: A Docker registry
:A: An account on the Helm Hub
:A: ✔️An HTTP server
+29 -14
View File
@@ -12,22 +12,37 @@
---
## Adding the repo
- If you haven't done it before, you need to add the repo for that chart
.exercise[
- Add the repo that holds the chart for the OWASP Juice Shop:
```bash
helm repo add juice https://charts.securecodebox.io
```
]
---
## We need a release
- We need to install something with Helm
- Let's use the `stable/tomcat` chart as an example
- Let's use the `juice/juice-shop` chart as an example
.exercise[
- Install a release called `tomcat` with the chart `stable/tomcat`:
- Install a release called `orange` with the chart `juice/juice-shop`:
```bash
helm upgrade tomcat stable/tomcat --install
helm upgrade orange juice/juice-shop --install
```
- Let's upgrade that release, and change a value:
```bash
helm upgrade tomcat stable/tomcat --set ingress.enabled=true
helm upgrade orange juice/juice-shop --set ingress.enabled=true
```
]
@@ -42,7 +57,7 @@
- View the history for that release:
```bash
helm history tomcat
helm history orange
```
]
@@ -82,11 +97,11 @@ We should see a number of secrets with TYPE `helm.sh/release.v1`.
.exercise[
- Examine the secret corresponding to the second release of `tomcat`:
- Examine the secret corresponding to the second release of `orange`:
```bash
kubectl describe secret sh.helm.release.v1.tomcat.v2
kubectl describe secret sh.helm.release.v1.orange.v2
```
(`v1` is the secret format; `v2` means revision 2 of the `tomcat` release)
(`v1` is the secret format; `v2` means revision 2 of the `orange` release)
]
@@ -102,7 +117,7 @@ There is a key named `release`.
- Dump the secret:
```bash
kubectl get secret sh.helm.release.v1.tomcat.v2 \
kubectl get secret sh.helm.release.v1.orange.v2 \
-o go-template='{{ .data.release }}'
```
@@ -120,7 +135,7 @@ Secrets are encoded in base64. We need to decode that!
- Decode the secret:
```bash
kubectl get secret sh.helm.release.v1.tomcat.v2 \
kubectl get secret sh.helm.release.v1.orange.v2 \
-o go-template='{{ .data.release | base64decode }}'
```
@@ -144,7 +159,7 @@ Let's try one more round of decoding!
- Decode it twice:
```bash
kubectl get secret sh.helm.release.v1.tomcat.v2 \
kubectl get secret sh.helm.release.v1.orange.v2 \
-o go-template='{{ .data.release | base64decode | base64decode }}'
```
@@ -164,7 +179,7 @@ Let's try one more round of decoding!
- Pipe the decoded release through `file -`:
```bash
kubectl get secret sh.helm.release.v1.tomcat.v2 \
kubectl get secret sh.helm.release.v1.orange.v2 \
-o go-template='{{ .data.release | base64decode | base64decode }}' \
| file -
```
@@ -185,7 +200,7 @@ Gzipped data! It can be decoded with `gunzip -c`.
- Rerun the previous command, but with `| gunzip -c > release-info` :
```bash
kubectl get secret sh.helm.release.v1.tomcat.v2 \
kubectl get secret sh.helm.release.v1.orange.v2 \
-o go-template='{{ .data.release | base64decode | base64decode }}' \
| gunzip -c > release-info
```
@@ -211,7 +226,7 @@ If we inspect that JSON (e.g. with `jq keys release-info`), we see:
- `config` (contains the values that we've set)
- `info` (date of deployment, status messages)
- `manifest` (YAML generated from the templates)
- `name` (name of the release, so `tomcat`)
- `name` (name of the release, so `orange`)
- `namespace` (namespace where we deployed the release)
- `version` (revision number within that release; starts at 1)
+191
View File
@@ -0,0 +1,191 @@
# Helm and invalid values
- A lot of Helm charts let us specify an image tag like this:
```bash
helm install ... --set image.tag=v1.0
```
- What happens if we make a small mistake, like this:
```bash
helm install ... --set imagetag=v1.0
```
- Or even, like this:
```bash
helm install ... --set image=v1.0
```
🤔
---
## Making mistakes
- In the first case:
- we set `imagetag=v1.0` instead of `image.tag=v1.0`
- Helm will ignore that value (if it's not used anywhere in templates)
- the chart is deployed with the default value instead
- In the second case:
- we set `image=v1.0` instead of `image.tag=v1.0`
- `image` will be a string instead of an object
- Helm will *probably* fail when trying to evaluate `image.tag`
---
## Preventing mistakes
- To prevent the first mistake, we need to tell Helm:
*"let me know if any additional (unknonw) value was set!"*
- To prevent the second mistake, we need to tell Helm:
*"`image` should be an object, and `image.tag` should be a string!"*
- We can do this with *values schema validation*
---
## Helm values schema validation
- We can write a spec representing the possible values accepted by the chart
- Helm will check the validity of the values before trying to install/upgrade
- If it finds problems, it will stop immediately
- The spec uses [JSON Schema](https://json-schema.org/):
*JSON Schema is a vocabulary that allows you to annotate and validate JSON documents.*
- JSON Schema is designed for JSON, but can easily work with YAML too
(or any language with `map|dict|associativearray` and `list|array|sequence|tuple`)
---
## In practice
- We need to put the JSON Schema spec in a file called `values.schema.json`
(at the root of our chart; right next to `values.yaml` etc.)
- The file is optional
- We don't need to register or declare it in `Chart.yaml` or anywhere
- Let's write a schema that will verify that ...
- `image.repository` is an official image (string without slashes or dots)
- `image.pullPolicy` can only be `Always`, `Never`, `IfNotPresent`
---
## `values.schema.json`
```json
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"pattern": "^[a-z0-9-_]+$"
},
"pullPolicy": {
"type": "string",
"pattern": "^(Always|Never|IfNotPresent)$"
}
}
}
}
}
```
---
## Testing our schema
- Let's try to install a couple releases with that schema!
.exercise[
- Try an invalid `pullPolicy`:
```bash
helm install broken --set image.pullPolicy=ShallNotPass
```
- Try an invalid value:
```bash
helm install should-break --set ImAgeTAg=toto
```
]
- The first one fails, but the second one still passes ...
- Why?
---
## Bailing out on unkown properties
- We told Helm what properties (values) were valid
- We didn't say what to do about additional (unknown) properties!
- We can fix that with `"additionalProperties": false`
.exercise[
- Edit `values.schema.json` to add `"additionalProperties": false`
```json
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
...
```
]
---
## Testing with unknown properties
.exercise[
- Try to pass an extra property:
```bash
helm install should-break --set ImAgeTAg=toto
```
- Try to pass an extra nested property:
```bash
helm install does-it-work --set image.hello=world
```
]
The first command should break.
The second will not.
`"additionalProperties": false` needs to be specified at each level.
???
:EN:- Helm schema validation
:FR:- Validation de schema Helm
+1 -1
View File
@@ -52,7 +52,7 @@
- There are literally dozens of implementations out there
(15 are listed in the Kubernetes documentation)
(https://github.com/containernetworking/cni/ lists more than 25 plugins)
- Pods have level 3 (IP) connectivity, but *services* are level 4 (TCP or UDP)
+521
View File
@@ -0,0 +1,521 @@
# OpenEBS
- [OpenEBS] is a popular open-source storage solution for Kubernetes
- Uses the concept of "Container Attached Storage"
(1 volume = 1 dedicated controller pod + a set of replica pods)
- Supports a wide range of storage engines:
- LocalPV: local volumes (hostpath or device), no replication
- Jiva: for lighter workloads with basic cloning/snapshotting
- cStor: more powerful engine that also supports resizing, RAID, disk pools ...
- [Mayastor]: newer, even more powerful engine with NVMe and vhost-user support
[OpenEBS]: https://openebs.io/
[Mayastor]: https://github.com/openebs/MayaStor#mayastor
---
class: extra-details
## What are all these storage engines?
- LocalPV is great if we want good performance, no replication, easy setup
(it is similar to the Rancher local path provisioner)
- Jiva is great if we want replication and easy setup
(data is stored in containers' filesystems)
- cStor is more powerful and flexible, but requires more extensive setup
- Mayastor is designed to achieve extreme performance levels
(with the right hardware and disks)
- The OpenEBS documentation has a [good comparison of engines] to help us pick
[good comparison of engines]: https://docs.openebs.io/docs/next/casengines.html#cstor-vs-jiva-vs-localpv-features-comparison
---
## Installing OpenEBS with Helm
- The OpenEBS control plane can be installed with Helm
- It will run as a set of containers on Kubernetes worker nodes
.exercise[
- Install OpenEBS:
```bash
helm upgrade --install openebs openebs \
--repo https://openebs.github.io/charts \
--namespace openebs --create-namespace
```
]
---
## Checking what was installed
- Wait a little bit ...
.exercise[
- Look at the pods in the `openebs` namespace:
```bash
kubectl get pods --namespace openebs
```
- And the StorageClasses that were created:
```bash
kubectl get sc
```
]
---
## The default StorageClasses
- OpenEBS typically creates three default StorageClasses
- `openebs-jiva-default` provisions 3 replicated Jiva pods per volume
- data is stored in `/openebs` in the replica pods
- `/openebs` is a localpath volume mapped to `/var/openebs/pvc-...` on the node
- `openebs-hostpath` uses LocalPV with local directories
- volumes are hostpath volumes created in `/var/openebs/local` on each node
- `openebs-device` uses LocalPV with local block devices
- requires available disks and/or a bit of extra configuration
- the default configuration filters out loop, LVM, MD devices
---
## When do we need custom StorageClasses?
- To store LocalPV hostpath volumes on a different path on the host
- To change the number of replicated Jiva pods
- To use a different Jiva pool
(i.e. a different path on the host to store the Jiva volumes)
- To create a cStor pool
- ...
---
class: extra-details
## Defining a custom StorageClass
Example for a LocalPV hostpath class using an extra mount on `/mnt/vol001`:
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: localpv-hostpath-mntvol001
annotations:
openebs.io/cas-type: local
cas.openebs.io/config: |
- name: BasePath
value: "/mnt/vol001"
- name: StorageType
value: "hostpath"
provisioner: openebs.io/local
```
- `provisioner` needs to be set accordingly
- Storage engine is chosen by specifying the annotation `openebs.io/cas-type`
- Storage engine configuration is set with the annotation `cas.openebs.io/config`
---
## Checking the default hostpath StorageClass
- Let's inspect the StorageClass that OpenEBS created for us
.exercise[
- Let's look at the OpenEBS LocalPV hostpath StorageClass:
```bash
kubectl get storageclass openebs-hostpath -o yaml
```
]
---
## Create a host path PVC
- Let's create a Persistent Volume Claim using an explicit StorageClass
.exercise[
```bash
kubectl apply -f - <<EOF
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: local-hostpath-pvc
spec:
storageClassName: openebs-hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1G
EOF
```
]
---
## Making sure that a PV was created for our PVC
- Normally, the `openebs-hostpath` StorageClass created a PV for our PVC
.exercise[
- Look at the PV and PVC:
```bash
kubectl get pv,pvc
```
]
---
## Create a Pod to consume the PV
.exercise[
- Create a Pod using that PVC:
```bash
kubectl apply -f ~/container.training/k8s/openebs-pod.yaml
```
- Here are the sections that declare and use the volume:
```yaml
volumes:
- name: my-storage
persistentVolumeClaim:
claimName: local-hostpath-pvc
containers:
...
volumeMounts:
- mountPath: /mnt/storage
name: my-storage
```
]
---
## Verify that data is written on the node
- Let's find the file written by the Pod on the node where the Pod is running
.exercise[
- Get the worker node where the pod is located
```bash
kubectl get pod openebs-local-hostpath-pod -ojsonpath={.spec.nodeName}
```
- SSH into the node
- Check the volume content
```bash
sudo tail /var/openebs/local/pvc-*/greet.txt
```
]
---
## Heads up!
- The following labs and exercises will use the Jiva storage class
- This storage class creates 3 replicas by default
- It uses *anti-affinity* placement constraits to put these replicas on different nodes
- **This requires a cluster with multiple nodes!**
- It also requires the iSCSI client (aka *initiator*) to be installed on the nodes
- On many platforms, the iSCSI client is preinstalled and will start automatically
- If it doesn't, you might want to check [this documentation page] for details
[this documentation page]: https://docs.openebs.io/docs/next/prerequisites.html
---
## The default StorageClass
- The PVC that we defined earlier specified an explicit StorageClass
- We can also set a default StorageClass
- It will then be used for all PVC that *don't* specify and explicit StorageClass
- This is done with the annotation `storageclass.kubernetes.io/is-default-class`
.exercise[
- Check if we have a default StorageClass:
```bash
kubectl get storageclasses
```
]
- The default StorageClass (if there is one) is shown with `(default)`
---
## Setting a default StorageClass
- Let's set the default StorageClass to use `openebs-jiva-default`
.exercise[
- Remove the annotation (just in case we already have a default class):
```bash
kubectl annotate storageclass storageclass.kubernetes.io/is-default-class- --all
```
- Annotate the Jiva StorageClass:
```bash
kubectl annotate storageclasses \
openebs-jiva-default storageclass.kubernetes.io/is-default-class=true
```
- Check the result:
```bash
kuectl get storageclasses
```
]
---
## Creating a Pod using the Jiva class
- We will create a Pod running PostgreSQL, using the default class
.exercise[
- Create the Pod:
```bash
kubectl apply -f ~/container.training/k8s/postgres.yaml
```
- Wait for the PV, PVC, and Pod to be up:
```bash
watch kubectl get pv,pvc,pod
```
- We can also check what's going on in the `openebs` namespace:
```bash
watch kubectl get pods --namespace openebs
```
]
---
## Node failover
⚠️ This will partially break your cluster!
- We are going to disconnect the node running PostgreSQL from the cluster
- We will see what happens, and how to recover
- We will not reconnect the node to the cluster
- This whole lab will take at least 10-15 minutes (due to various timeouts)
⚠️ Only do this lab at the very end, when you don't want to run anything else after!
---
## Disconnecting the node from the cluster
.exercise[
- Find out where the Pod is running, and SSH into that node:
```bash
kubectl get pod postgres-0 -o jsonpath={.spec.nodeName}
ssh nodeX
```
- Check the name of the network interface:
```bash
sudo ip route ls default
```
- The output should look like this:
```
default via 10.10.0.1 `dev ensX` proto dhcp src 10.10.0.13 metric 100
```
- Shutdown the network interface:
```bash
sudo ip link set ensX down
```
]
---
## Watch what's going on
- Let's look at the status of Nodes, Pods, and Events
.exercise[
- In a first pane/tab/window, check Nodes and Pods:
```bash
watch kubectl get nodes,pods -o wide
```
- In another pane/tab/window, check Events:
```bash
kubectl get events --watch
```
]
---
## Node Ready → NotReady
- After \~30 seconds, the control plane stops receiving heartbeats from the Node
- The Node is marked NotReady
- It is not *schedulable* anymore
(the scheduler won't place new pods there, except some special cases)
- All Pods on that Node are also *not ready*
(they get removed from service Endpoints)
- ... But nothing else happens for now
(the control plane is waiting: maybe the Node will come back shortly?)
---
## Pod eviction
- After \~5 minutes, the control plane will evict most Pods from the Node
- These Pods are now `Terminating`
- The Pods controlled by e.g. ReplicaSets are automatically moved
(or rather: new Pods are created to replace them)
- But nothing happens to the Pods controlled by StatefulSets at this point
(they remain `Terminating` forever)
- Why? 🤔
--
- This is to avoid *split brain scenarios*
---
class: extra-details
## Split brain 🧠⚡️🧠
- Imagine that we create a replacement pod `postgres-0` on another Node
- And 15 minutes later, the Node is reconnected and the original `postgres-0` comes back
- Which one is the "right" one?
- What if they have conflicting data?
😱
- We *cannot* let that happen!
- Kubernetes won't do it
- ... Unless we tell it to
---
## The Node is gone
- One thing we can do, is tell Kubernetes "the Node won't come back"
(there are other methods; but this one is the simplest one here)
- This is done with a simple `kubectl delete node`
.exercise[
- `kubectl delete` the Node that we disconnected
]
---
## Pod rescheduling
- Kubernetes removes the Node
- After a brief period of time (\~1 minute) the "Terminating" Pods are removed
- A replacement Pod is created on another Node
- ... But it doens't start yet!
- Why? 🤔
---
## Multiple attachment
- By default, a disk can only be attached to one Node at a time
(sometimes it's a hardware or API limitation; sometimes enforced in software)
- In our Events, we should see `FailedAttachVolume` and `FailedMount` messages
- After \~5 more minutes, the disk will be force-detached from the old Node
- ... Which will allow attaching it to the new Node!
🎉
- The Pod will then be able to start
- Failover is complete!
+3 -9
View File
@@ -331,11 +331,8 @@ consul agent -data-dir=/consul/data -client=0.0.0.0 -server -ui \
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- consul
matchLabels:
app: consul
topologyKey: kubernetes.io/hostname
```
@@ -353,10 +350,7 @@ consul agent -data-dir=/consul/data -client=0.0.0.0 -server -ui \
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- consul leave
command: [ "sh", "-c", "consul leave" ]
```
---
+2
View File
@@ -42,6 +42,8 @@ content:
#- k8s/helm-chart-format.md
#- k8s/helm-create-basic-chart.md
#- k8s/helm-create-better-chart.md
#- k8s/helm-dependencies.md
#- k8s/helm-values-schema-validation.md
#- k8s/helm-secrets.md
#- k8s/exercise-helm.md
- shared/thankyou.md
+1
View File
@@ -213,6 +213,7 @@ def processcontent(content, filename):
return (content, titles)
if os.path.isfile(content):
return processcontent(open(content).read(), content)
logging.warning("Content spans only one line (it's probably a file name) but no file found: {}".format(content))
if isinstance(content, list):
subparts = [processcontent(c, filename) for c in content]
markdown = "\n---\n".join(c[0] for c in subparts)
+12
View File
@@ -0,0 +1,12 @@
## Chat room
- A Slack room has been set up for the duration of the training
- We'll use it to ask questions, get help, share feedback ...
(let's keep an eye on it during the training!)
- Reminder, the room is @@CHAT@@
- Say hi in the chat room!
+9
View File
@@ -109,8 +109,17 @@ div.pic p {
div.pic img {
display: block;
margin: auto;
/*
"pic" class slides should have a single, full screen picture.
We used to have these attributes below but they prevented
pictures from taking up the whole slide. Replacing them with
100%/100% seems to put the pictures full screen, but I've left
these old attributes here just in case.
max-width: 1210px;
max-height: 550px;
*/
max-width: 100%;
max-height: 100%;
}
div.pic h1, div.pic h2, div.title h1, div.title h2 {
text-align: center;