<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 8P Inc.</title>
    <description>The latest articles on DEV Community by 8P Inc. (@peepeepopapapeepeepo).</description>
    <link>https://dev.to/peepeepopapapeepeepo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F190246%2F03144a17-851f-4cd9-ae81-fd5f194e8207.jpeg</url>
      <title>DEV Community: 8P Inc.</title>
      <link>https://dev.to/peepeepopapapeepeepo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peepeepopapapeepeepo"/>
    <language>en</language>
    <item>
      <title>Kiro Cannot Get Response from The Second Command in The Same Shell</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Sat, 20 Sep 2025 09:15:48 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/kiro-cannot-get-response-from-the-second-command-in-the-same-shell-1blk</link>
      <guid>https://dev.to/peepeepopapapeepeepo/kiro-cannot-get-response-from-the-second-command-in-the-same-shell-1blk</guid>
      <description>&lt;p&gt;As you may know, when Kiro wants to run shell commands, it opens a new &lt;code&gt;zsh&lt;/code&gt; (named "Kiro"), runs the command on that shell, and gets the response to analyze the next step.&lt;/p&gt;

&lt;p&gt;Unfortunately, if you're using &lt;strong&gt;Kiro&lt;/strong&gt;, &lt;strong&gt;zsh&lt;/strong&gt;, and &lt;strong&gt;powerlevel10k&lt;/strong&gt; theme in OH-My-Zsh together, you may face this problem like I did.&lt;/p&gt;

&lt;p&gt;For the first command sent to the shell, it gets a response successfully. However, for the second command in the same shell, Kiro cannot get the response from the shell command and ends up waiting for a response indefinitely.&lt;/p&gt;

&lt;p&gt;This problem is so annoying and made me fed up with Kiro.&lt;/p&gt;

&lt;p&gt;Recently, I discovered it's due to the incompatibility between &lt;strong&gt;powerlevel10k&lt;/strong&gt; theme and Kiro. So, I found a way to mitigate this problem, which allows me to give Kiro another shot.&lt;/p&gt;

&lt;p&gt;We know that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If zsh is opened by Kiro, the environment variable &lt;code&gt;TERM_PROGRAM&lt;/code&gt; equals &lt;strong&gt;"kiro"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;There are 2 personas in Kiro that can open new shells:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Us"&lt;/strong&gt; as users &lt;strong&gt;can use&lt;/strong&gt; powerlevel10k theme&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Kiro"&lt;/strong&gt; as an IDE &lt;strong&gt;can't use&lt;/strong&gt; powerlevel10k theme&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Mitigation:
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key Concept&lt;/strong&gt;: We will not use &lt;strong&gt;powerlevel10k&lt;/strong&gt; theme if the zsh is opened by Kiro&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In Kiro, add environment variable &lt;code&gt;ENABLE_ZSH_THEME=1&lt;/code&gt; in zsh configuration&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edit Kiro settings
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtr2m5hw889dybnm0p4o.png" alt="kiro-settings-1" width="800" height="467"&gt;
&lt;/li&gt;
&lt;li&gt;Change this value (terminal.integrated.profiles.osx)
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9m2d0s19a1609m9kojey.png" alt="kiro-settings-2" width="800" height="400"&gt;
&lt;/li&gt;
&lt;li&gt;Add environment variable
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckuhfuwbfrvdf5pduzee.png" alt="kiro-settings-3" width="800" height="732"&gt;
At this stage, zsh opened by you will have environment variable &lt;code&gt;ENABLE_ZSH_THEME=1&lt;/code&gt;, while zsh opened by Kiro will not have &lt;code&gt;ENABLE_ZSH_THEME=1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure &lt;code&gt;~/.zshrc&lt;/code&gt; to match the configuration above&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a condition to load &lt;strong&gt;powerlevel10k&lt;/strong&gt; theme
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9l3if3fyike797g3xkr.png" alt="zshrc" width="800" height="228"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test by opening zsh yourself and letting Kiro open it to see the result&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Expected Result&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;zsh opened by Kiro → no powerlevel10k theme&lt;/li&gt;
&lt;li&gt;zsh opened by You → has powerlevel10k theme&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Kiro commands work reliably&lt;/strong&gt; without timeouts&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Preserves your beautiful terminal theme&lt;/strong&gt; for regular use**&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Simple, maintainable solution&lt;/strong&gt; with &lt;strong&gt;minimal configuration&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;No need to completely disable&lt;/strong&gt; powerlevel10k&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>kiro</category>
      <category>zsh</category>
      <category>powerlevel10k</category>
    </item>
    <item>
      <title>เริ่มต้นใช้งาน Amazon Q Developer Custom Agents</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Sun, 03 Aug 2025 04:07:15 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/amazon-q-developer-custom-agents-pbl</link>
      <guid>https://dev.to/peepeepopapapeepeepo/amazon-q-developer-custom-agents-pbl</guid>
      <description>&lt;p&gt;หลายคนที่ได้ลองใช้ Amazon Q Developer น่าจะหงุดหงิดของ &lt;code&gt;/profile&lt;/code&gt; เพราะมันดันไม่สามารถ custom พวก MCP configuration ได้ และไม่สามารถ set trust tools ตามแต่ละ profile ได้&lt;br&gt;
ตอนนี้ AWS เลยทำการ rebrand &lt;code&gt;/profile&lt;/code&gt; ไปเป็น feature ใหม่ที่เรียกว่า &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-custom-agents.html" rel="noopener noreferrer"&gt;custom agents&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;คราวนี้เรามาดูกันครับว่า custom agents มีอะไรให้เราเล่นบ้าง&lt;/p&gt;
&lt;h3&gt;
  
  
  Migrate
&lt;/h3&gt;

&lt;p&gt;เริ่มจากการ migrate ก่อน ปกติ Q cli จะถามว่าเราจะ upgrade ไหม แต่ถ้าไม่ถาม หรือว่าเรา say no ไปก็ run command นี้ เพื่อ migrate ได้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q agent migrate --force
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;ถ้าใครอยากลงลึกเรียนเชิญ -&amp;gt; &lt;a href="https://github.com/aws/amazon-q-developer-cli/blob/main/docs/legacy-profile-to-agent-migration.md" rel="noopener noreferrer"&gt;Migrate Profiles to Agents&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Customize Agents
&lt;/h3&gt;

&lt;p&gt;หลังจากที่ migrate แล้ว profile ที่เรา set ไว้มันจะกลายเป็น agent ให้เราโดยอัตโนมัติ โดยกลายเป็น json configuration file ใน path &lt;code&gt;~/.aws/amazonq/cli-agents/&amp;lt;NAME&amp;gt;.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;AWS มี plan ที่จะเพิ่ม sub command &lt;code&gt;/agent edit &amp;lt;NAME&amp;gt;&lt;/code&gt; ให้ แต่ ณ วันที่เขียน blog นี้ยังไม่มี แต่เราสามารถ edit file นี้ได้ตรงๆ ผ่าน ide ได้ตามปกติ&lt;/p&gt;

&lt;p&gt;ที่นี้มาดูหน้่าตาของ configuration file ( สามารถพิมพ์ &lt;code&gt;/agent schema&lt;/code&gt; เพื่อดู schema เต็มๆ ของ config ได้ )&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;NAME&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"toolAliases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowedTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"fs_read"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"file://AmazonQ.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"file://README.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"file://.amazonq/rules/**/*.md"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"toolsSettings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"useLegacyMcpJson"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; คือ ชื่อของ custom agent ควรตรงกับ ชื่อ file เพื่อความไม่สับสน โดย Q cli จะอ้างอิงชื่อ custom agent จากในส่วนนี้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; คือ คำอธิายคร่าวๆ ของ custom agent นี้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prompt&lt;/code&gt; คือที่ใส่ system prompt ของ custom agent นี้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcpServers&lt;/code&gt; คือ ที่ configure mcp server ที่ agent นี้จะ start ขึ้นมาพร้อมกับ custom agent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tools&lt;/code&gt; ใช้ระบุ tools ต่างๆ ที่ custom agent นี้สามารถใช้ได้ สามารถระบุได้ทั้ง builtin tools และ tools จาก mcp server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toolAliases&lt;/code&gt; ใช้ตั้งชื่อ tools ที่มีอยู่แล้วใหม่ ให้เป็นแบบที่เข้าใจง่ายขึ้น หรือ จำง่ายขึ้น&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allowedTools&lt;/code&gt; ใช้ระบุว่า เมื่อ Q cli ถูก start ขึ้นมาให้ trust tools ไหนได้เลย 

&lt;ul&gt;
&lt;li&gt;โดยสามารถ allow ได้ทั้งแบบ

&lt;ul&gt;
&lt;li&gt;ระบุ built-in tools -&amp;gt; &lt;code&gt;fs_read&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ทั้ง mcp server -&amp;gt; &lt;code&gt;@server_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;เฉพาะบาง tools ใน mcp server -&amp;gt; &lt;code&gt;@server_name/tool_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ทั้งหมด -&amp;gt; &lt;code&gt;*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;builtin ทั้งหมด -&amp;gt; &lt;code&gt;@builtin&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"allowedTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"fs_read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"@git/git_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="s2"&gt;"@fetch"&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;resources&lt;/code&gt; ใช้ระบุถึง context file ของ custom agent นั้นๆ เป็น concept เดียวกับ &lt;code&gt;/context&lt;/code&gt; ที่ใช้ร่วมกับ &lt;code&gt;/profile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hooks&lt;/code&gt; เป็นที่ที่เราสามารถ define ได้ว่า ใน event เหล่านี้ จะให้ Q cli ทำอะไร โดย ตอนนี้มี 2 events คือ

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agentSpawn&lt;/code&gt;: ตอนที่ Q cli ถูก start&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;userPromptSubmit&lt;/code&gt;: ตอนที่เรา submit prompt ให้ Q cli
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"agentSpawn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"userPromptSubmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ls -la"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;toolsSettings&lt;/code&gt; ใช้ define configuration ต่างๆ ของ builtin tools เช่น
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"toolsSettings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"fs_write"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"allowedPaths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tests/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"execute_bash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"allowedCommands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"git status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm test"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"allowReadOnly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"use_aws"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"allowedServices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lambda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useLegacyMcpJson&lt;/code&gt; ระบุว่าจะให้ include legacy global MCP configuration file (&lt;code&gt;~/.aws/amazonq/mcp.json&lt;/code&gt;) เข้ามาด้วยไหมตอน Q cli ถุก start&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;สามารถดูเพิ่มเติมได้จาก &lt;a href="https://github.com/aws/amazon-q-developer-cli/blob/main/docs/agent-format.md#uselegacymcpjson-field" rel="noopener noreferrer"&gt;Agent Configuration Format&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Validate Agent Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q agent validate --path ./my-agent.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้า syntax ผิดจะเป็นประมาณนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowdz5ztij4i35ran22xz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowdz5ztij4i35ran22xz.png" alt="syntax-error" width="800" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;แต่ถ้าถูกจะไม่มีอะไร print ออกมา และ exit 0&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh77f5b8oqh7l9ylgvju2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh77f5b8oqh7l9ylgvju2.png" alt="good-to-go" width="800" height="58"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Use Custom Agent
&lt;/h3&gt;

&lt;p&gt;เราสามารถ lists agent ทั้งหมดที่เรามีได้โดย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q agent list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[default]&amp;gt; /agent list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จากนั้นเรียกใช้ custom agent ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q chat --agent &amp;lt;NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;ในอนาคต เราจะสามารถ switch custom agent ได้ด้วย &lt;code&gt;/agent use &amp;lt;NAME&amp;gt;&lt;/code&gt; ซึ่งเป็น feature ที่กำลัง develop อยู่&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Set Default Agent
&lt;/h3&gt;

&lt;p&gt;เมื่อเราสร้าง custom agent แล้วสามารถ set ให้มันเป็น ค่า default ได้โดย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q agent set-default --name &amp;lt;NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/agent set-default --name &amp;lt;NAME&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จากนั้นเราสามารถเรียกใช้ custom agent ได้ โดยการเรียก &lt;code&gt;q&lt;/code&gt; หรือ &lt;code&gt;q chat&lt;/code&gt; ได้โดยไม่ต้องระบุชื่อ custom agent แล้ว&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ถ้าเราไม่ได้ set default agent ไว้ Q cli จะใช้งาน &lt;strong&gt;builtin default agent&lt;/strong&gt; ในการทำงาน เช่นเดียวกันถ้า configuration ของ custom agent เราผิด เวลาเรา start Q cli มันก็จะ fall back มาใช้งาน &lt;strong&gt;builtin default agent&lt;/strong&gt; เหมือนกัน&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Type of Custom Agent Configuration
&lt;/h3&gt;

&lt;p&gt;มีอยู่ 2 แบบ ดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local custom agent (Workspace-specific)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Path: &lt;code&gt;{current_working_directory}/.amazonq/cli-agents/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Scope: จะใช้ได้ก็ต่อเมื่อ start Q cli ใน {current_working_directory}&lt;/li&gt;
&lt;li&gt;Use Case: Project-specific agents หรือ team-shared configurations&lt;/li&gt;
&lt;li&gt;Example:
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
├── .amazonq/
│   └── cli-agents/
│       ├── dev-agent.json
│       ├── aws-specialist.json
│       └── code-reviewer.json
├── src/
│   └── main.py
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global custom agent&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Path: &lt;code&gt;~/.aws/amazonq/cli-agents/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Scope: start Q cli ได้ทุกที่&lt;/li&gt;
&lt;li&gt;Use Case: agents ส่วนตัว หรือ ใช้เป็น general-purpose assistants&lt;/li&gt;
&lt;li&gt;Example
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.aws/amazonq/cli-agents/
├── general-assistant.json
├── documentation-writer.json
├── security-analyst.json
└── devops-automation.json

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้าเราอยู่ที่ {current_working_directory} แล้ว list agents จะเห็นแบบนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; q agent list
general-assistant        /Users/demo/.aws/amazonq/cli-agents
documentation-writer     /Users/demo/.aws/amazonq/cli-agents
security-analyst         /Users/demo/.aws/amazonq/cli-agents
devops-automation        /Users/demo/.aws/amazonq/cli-agents
dev-agent                /Users/demo/my-project/.amazonq/cli-agents
aws-specialist           /Users/demo/my-project/.amazonq/cli-agents
code-reviewer            /Users/demo/my-project/.amazonq/cli-agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[general-assistant] &amp;gt; /agent list

* general-assistant   
  documentation-writer
  security-analyst    
  devops-automation               
  dev-agent           
  aws-specialist      
  code-reviewer       
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้าออกไป directory อื่น ก็จะเห็น custom agent เท่าที่มีใน Global&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; q agent list
general-assistant        /Users/demo/.aws/amazonq/cli-agents
documentation-writer     /Users/demo/.aws/amazonq/cli-agents
security-analyst         /Users/demo/.aws/amazonq/cli-agents
devops-automation        /Users/demo/.aws/amazonq/cli-agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[general-assistant] &amp;gt; /agent list

* general-assistant   
  documentation-writer
  security-analyst    
  devops-automation                    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>amazonq</category>
      <category>qdev</category>
    </item>
    <item>
      <title>How to Create AWS Builder ID</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Sun, 06 Jul 2025 06:53:24 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/how-to-create-aws-builder-id-2kjm</link>
      <guid>https://dev.to/peepeepopapapeepeepo/how-to-create-aws-builder-id-2kjm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Table of Content&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create AWS Builder ID&lt;/li&gt;
&lt;li&gt;Login with your AWS Builder ID&lt;/li&gt;
&lt;li&gt;Register MFA Devices&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Create AWS Builder ID
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;a href="https://profile.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS Builder ID profile&lt;/a&gt;. You will redirect to "Create AWS Builder ID" page.

&lt;ul&gt;
&lt;li&gt;If you &lt;strong&gt;have already had&lt;/strong&gt; "AWS Builder ID", click "Already have AWS Builder ID? Sign in" and proceed login.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69nd7xykzic7w1yymdu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69nd7xykzic7w1yymdu8.png" alt="Already have AWS Builder ID" width="800" height="711"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you &lt;strong&gt;don't have&lt;/strong&gt; "AWS Builder ID" yet, fill in your email ( recommend to use personal email since. You can use this ID to register for &lt;a href="https://www.aws.training/certification/" rel="noopener noreferrer"&gt;AWS Certificate Exam&lt;/a&gt; and &lt;a href="https://skillbuilder.aws/" rel="noopener noreferrer"&gt;AWS Skill Builder&lt;/a&gt; in the future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea7emhlpa38e32epufl0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea7emhlpa38e32epufl0.png" alt="Don't have AWS Builder ID" width="800" height="723"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To continue creating "AWS Builder ID", fill in your name and click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3hn6pnkd8ph9bqcjnmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3hn6pnkd8ph9bqcjnmy.png" alt="Fill in name" width="800" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The verification code will be sent to you email address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmv4pyrzc7p66i95uaa4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmv4pyrzc7p66i95uaa4.png" alt="email-verification" width="800" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fill in the code and click &lt;strong&gt;Verify&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnkxsfw00zgsgldd4zgk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnkxsfw00zgsgldd4zgk5.png" alt="verify-email" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After your email is successfully verified, it will direct you to set password for your account. Fill in your password, finish captcha challenge and click &lt;strong&gt;Create AWS Builder ID&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3yg4qrs2rohsh994nrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3yg4qrs2rohsh994nrv.png" alt="set-password" width="800" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After your AWS Builder ID is successfully created, it will redirect you to main login page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhv70bly2gtg5hfns2sz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhv70bly2gtg5hfns2sz.png" alt="successful-creation" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Login with your AWS Builder ID
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Click at "Already have AWS Builder ID? Sign in" and fill in your email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11hzv5xpsfymud78ecmm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11hzv5xpsfymud78ecmm.png" alt="login" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fill in your password and enter captcha&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayl90kirl7yh3oxmvtc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayl90kirl7yh3oxmvtc5.png" alt="enter-password" width="800" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The verification code will be sent to you email address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbve2sajrcofqkpsdp2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbve2sajrcofqkpsdp2v.png" alt="code-verification" width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fill in the code and click &lt;strong&gt;Verify&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ijjyqb2a9b29zuk9dbh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ijjyqb2a9b29zuk9dbh.png" alt="verify-code" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It will direct you back to login page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhv70bly2gtg5hfns2sz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhv70bly2gtg5hfns2sz.png" alt="successful-creation" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Register MFA Devices
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait for about 10 minutes after creating AWS Builder ID to continue add MFA devices&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;a href="https://profile.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS Builder ID profile&lt;/a&gt; and login with your credential. After login success, you will be direct to this page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnqb0lfvxr901w3lhwlg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnqb0lfvxr901w3lhwlg.png" alt="profile-page" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click at &lt;strong&gt;Security&lt;/strong&gt; and &lt;strong&gt;Register devices&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxh77w0i9a0mngmdof445.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxh77w0i9a0mngmdof445.png" alt="register-devices" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select your preference MFA devices type and click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vaezmivasghn1rdl1d7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vaezmivasghn1rdl1d7.png" alt="mfa-devices" width="800" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Follow its instruction to add each type of MFA device. After successfully add a MFA devices, your device will be appeared here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryx8s7b8bckd8ryyyy70.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryx8s7b8bckd8ryyyy70.png" alt="registed-mfa-devices" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Incognito or InPrivate mode, and try login to &lt;a href="https://profile.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS Builder ID profile&lt;/a&gt;. You will be ask for both password and MFA device. After successfully login, sign out and close the Incognito or InPrivate mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;After finish all the steps, your AWS Builder ID is ready and safe to use.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/signin/latest/userguide/create-aws_builder_id.html" rel="noopener noreferrer"&gt;Create your AWS Builder ID&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>awsbuilder</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>GitHub Self-Hosted Runners on Azure Container Apps with Autocsaling</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Tue, 07 Mar 2023 04:41:00 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/github-selfhosted-runners-on-azure-container-apps-with-autocsaling-5h02</link>
      <guid>https://dev.to/peepeepopapapeepeepo/github-selfhosted-runners-on-azure-container-apps-with-autocsaling-5h02</guid>
      <description>&lt;p&gt;เรื่องมันมีอยู่ว่า อยากได้ GitHub Self-Hosted Runners แบบที่ autoscale ได้ตาม workload ครับ และตอนที่ไม่ใช้เลยก็สามารถ scale in instance เหลือ 0 ได้ ผมค้นไปเจอ &lt;a href="https://dev.to/pwd9000/run-docker-based-github-runner-containers-on-azure-container-apps-aca-1n13"&gt;Autoscaling self hosted GitHub runner containers with Azure Container Apps (ACA)&lt;/a&gt; มา คิดว่าค่อนข้างตรงกับที่ต้องการ เลยเอามาลอง POC ดูครับ&lt;/p&gt;

&lt;p&gt;จากใน link ข้างต้น จะเป็นการ run container image ของ GitHub Self-Hosted Runners ด้วย Azure Container Apps และอาศัยความสามารถของ &lt;a href="https://keda.sh/" rel="noopener noreferrer"&gt;KEDA autoscaler&lt;/a&gt; ในการ scale replica ตามจำนวน queue ใน Azure Storage Queue&lt;/p&gt;

&lt;p&gt;การทำงานจะเป็นแบบนี้ครับ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;เมื่อ GitHub workflow ถูก trigger ให้ run มันจะไป push message ลงไปที่ Azure Storage Queue&lt;/li&gt;
&lt;li&gt;KEDA scaler ใน Azure Container Apps ตรวจพบว่ามี queue เพิ่มขึ้น จึงทำการ scale out instance ของ GitHub selfhosted runners ตามจำนวน queue&lt;/li&gt;
&lt;li&gt;เมื่อ scale เรียบร้อย ตัว GitHub selfhosted runners จะมารับงานจาก GitHub workflow ไป run จนเรียบร้อย&lt;/li&gt;
&lt;li&gt;GitHub workflow ทำการ delete message ออกจาก Azure Storage Queue&lt;/li&gt;
&lt;li&gt;KEDA scaler ตรวจพบว่ามี queue ลดลงจึง scale in  instance ของ GitHub selfhosted runners ลง ถ้า queue เป็น 0 จำนวน instance ของ GitHub selfhosted runners ก็เป็น 0&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ด้วยความคัน ผมจะเปลี่ยนนิดหน่อย ดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ใช้เป็น Azure Service Bus Queue แทน Azure Storage Queue เพราะในกรณีที่มีการ send / receive message เยอะ Azure Service Bus Queue จะถูกกว่านิดหน่อย (อ่านเพิ่ม &lt;a href="https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-azure-and-service-bus-queues-compared-contrasted" rel="noopener noreferrer"&gt;Storage queues and Service Bus queues - compared and contrasted&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;ปรับ workflow ให้ใช้ &lt;a href="https://dev.to/peepeepopapapeepeepo/let-github-action-access-azure-resources-without-password-f0i"&gt;Workload Identity Federation&lt;/a&gt; เพื่อให้เป็น passwordless&lt;/li&gt;
&lt;li&gt;สร้าง Azure Container Apps แบบมี Virtual Network Integration เพื่อให้ interact กับ resources ที่เป็น private ได้&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Diagram จะเป็นแบบนี้นะครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6w2u2a30zbuk58uosjl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6w2u2a30zbuk58uosjl.png" alt="Diagram" width="683" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;และเช่นกัน ณ เวลาที่ผมเขียน blog นี้นั้น Azure Container Apps ยังมีข้อจำกัดดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support แค่ Linux-based x86-64 (linux/amd64) container image.&lt;/li&gt;
&lt;li&gt;ยังไม่มี KEDA scalers for GitHub runners&lt;/li&gt;
&lt;li&gt;ยังไม่มีที่ Southeast Asia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ทีนี้ก่อนเริ่มทำเรามาดูกันก่อนว่าเราต้องมีอะไรกันบ้าง&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Azure Subscription&lt;/strong&gt; ( ใช้เพื่อ deploy resources ต่างๆ เช่น vnet, log analytics workspace, container apps และ service bus เป็นต้น )&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Github Organization&lt;/strong&gt; ( เราจะให้ runners เป็น oganization level runners )&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Github Repository&lt;/strong&gt; ( ใช้เก็บและ run workflow )&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ลงมือทำกันเลย
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dynamics-nav/how-to--sign-up-for-a-microsoft-azure-subscription" rel="noopener noreferrer"&gt;Create Azure subscription&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/get-started/signing-up-for-github/signing-up-for-a-new-github-account" rel="noopener noreferrer"&gt;Sign up Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository" rel="noopener noreferrer"&gt;Create repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/organizations/collaborating-with-groups-in-organizations/creating-a-new-organization-from-scratch" rel="noopener noreferrer"&gt;Create organization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token" rel="noopener noreferrer"&gt;Create Github Access Token&lt;/a&gt; โดยเลือก scope เป็น &lt;code&gt;admin:org&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create GitHub Self-hosted Runners Container Image ดูได้จาก &lt;a href="https://github.com/myoung34/docker-github-actions-runner" rel="noopener noreferrer"&gt;docker-github-actions-runner&lt;/a&gt; หรือ &lt;a href="https://dev.to/pwd9000/create-a-docker-based-self-hosted-github-runner-linux-container-48dh"&gt;Create a Docker based Self Hosted GitHub runner Linux container&lt;/a&gt; แล้ว push ขึ้น container registry ของเรา&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preflight
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;ทำการ install extensions ของ Azure CLI และ providers ที่ subscription
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az extension add &lt;span class="nt"&gt;--name&lt;/span&gt; containerapp &lt;span class="nt"&gt;--upgrade&lt;/span&gt;
az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.App
az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.OperationalInsights
az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.ContainerService
az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.ServiceBus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Define variables
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;POC_PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pocghaca"&lt;/span&gt;
&lt;span class="nv"&gt;LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"westus3"&lt;/span&gt;
&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"usw3"&lt;/span&gt;
&lt;span class="nv"&gt;RESOURCE_GROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rg-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POC_PREFIX&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-az-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-001"&lt;/span&gt;
&lt;span class="nv"&gt;VNET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"vnet-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POC_PREFIX&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-az-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-001"&lt;/span&gt;
&lt;span class="nv"&gt;INFRASTRUCTURE_SUBNET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"snet-infra-az-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-001"&lt;/span&gt;
&lt;span class="nv"&gt;LOG_ANALYTIC_WORKSPACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"log-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POC_PREFIX&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-az-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-001"&lt;/span&gt;
&lt;span class="nv"&gt;SERVICEBUS_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sbn-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POC_PREFIX&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-az-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-001"&lt;/span&gt;
&lt;span class="nv"&gt;SERVICEBUS_QUEUE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gh-runner-scaler"&lt;/span&gt;
&lt;span class="nv"&gt;CONTAINERAPPS_ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"cte-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POC_PREFIX&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-az-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-001"&lt;/span&gt;
&lt;span class="nv"&gt;CONTAINERAPP_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"cta-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POC_PREFIX&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-az-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-001"&lt;/span&gt;
&lt;span class="nv"&gt;CONTAINER_REGISTRY_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..&amp;lt;YOUR-CONTAINER-REGISTRY&amp;gt;.."&lt;/span&gt;
&lt;span class="nv"&gt;CONTAINER_REGISTRY_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..&amp;lt;YOUR-REGISTRY-USERNAME&amp;gt;.."&lt;/span&gt;
&lt;span class="nv"&gt;CONTAINER_REGISTRY_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..&amp;lt;YOUR-REGISTRY-PASSWORD&amp;gt;.."&lt;/span&gt;
&lt;span class="nv"&gt;CONTAINER_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..&amp;lt;YOU-CONTAINER-IMAGE&amp;gt;.."&lt;/span&gt;
&lt;span class="nv"&gt;PAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..&amp;lt;YOUR-GITHUB-ACCESS-TOKEN&amp;gt;.."&lt;/span&gt;
&lt;span class="nv"&gt;GH_ORG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..&amp;lt;YOUR-ORGANIZATION-NAME&amp;gt;.."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prerequisite Azure Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create Resource group
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOCATION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Log analytics workspaces
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az monitor log-analytics workspace create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$LOG_ANALYTIC_WORKSPACE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--quota&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--retention-time&lt;/span&gt; 30 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--sku&lt;/span&gt; PerGB2018
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Virtual network and subnet
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az network vnet create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOCATION&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--address-prefix&lt;/span&gt; 10.0.0.0/16
az network vnet subnet create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--vnet-name&lt;/span&gt; &lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$INFRASTRUCTURE_SUBNET&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--address-prefixes&lt;/span&gt; 10.0.0.0/21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure Service Bus and Queue
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create Azure service bus and queue
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az servicebus namespace create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$SERVICEBUS_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOCATION&lt;/span&gt;
az servicebus queue create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--namespace-name&lt;/span&gt; &lt;span class="nv"&gt;$SERVICEBUS_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$SERVICEBUS_QUEUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create connection string of Azure Service Bus Queue
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az servicebus queue authorization-rule create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--namespace-name&lt;/span&gt; &lt;span class="nv"&gt;$SERVICEBUS_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--queue-name&lt;/span&gt; &lt;span class="nv"&gt;$SERVICEBUS_QUEUE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPP_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--rights&lt;/span&gt; Manage Send Listen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add yourself as &lt;code&gt;Azure Service Bus Data Owner&lt;/code&gt; on Azure Service Bus Namespace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94xeqexiob7lie0mga07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94xeqexiob7lie0mga07.png" alt="service-bus-queue-role-assignment" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test send a message and receive/delete a message from the queue&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ ถ้าทำใน bash ของ Azure Cloud Shell ใน run &lt;code&gt;az login&lt;/code&gt; อีกครั้ง&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Send message&lt;/span&gt;
az rest &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource&lt;/span&gt; &lt;span class="s2"&gt;"https://servicebus.azure.net"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--method&lt;/span&gt; post &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--headers&lt;/span&gt; &lt;span class="nv"&gt;BrokerProperties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"TimeToLive":10}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICEBUS_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.servicebus.windows.net/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICEBUS_QUEUE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/messages"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Hello from Az REST with TTL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Receive / Delete message&lt;/span&gt;
az rest &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource&lt;/span&gt; &lt;span class="s2"&gt;"https://servicebus.azure.net"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--method&lt;/span&gt; delete &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICEBUS_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.servicebus.windows.net/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICEBUS_QUEUE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/messages/head"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Azure Container App
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;รวบรวมข้อมูลที่ใช้ในการสร้าง Azure Container App และ Azure Container Environment
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;INFRASTRUCTURE_SUBNET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az network vnet subnet show &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="nt"&gt;--vnet-name&lt;/span&gt; &lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$INFRASTRUCTURE_SUBNET&lt;/span&gt; &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; tsv | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[:space:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;LOG_WORKSPACE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az monitor log-analytics workspace show &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$LOG_ANALYTIC_WORKSPACE&lt;/span&gt; &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"customerId"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; tsv | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[:space:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;LOG_WORKSPACE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az monitor log-analytics workspace get-shared-keys &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$LOG_ANALYTIC_WORKSPACE&lt;/span&gt; &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"primarySharedKey"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; tsv | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[:space:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CONNECTION_QUEUE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az servicebus queue authorization-rule keys list &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="nt"&gt;--namespace-name&lt;/span&gt; &lt;span class="nv"&gt;$SERVICEBUS_NAME&lt;/span&gt; &lt;span class="nt"&gt;--queue-name&lt;/span&gt; &lt;span class="nv"&gt;$SERVICEBUS_QUEUE&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPP_NAME&lt;/span&gt; &lt;span class="nt"&gt;--query&lt;/span&gt; primaryConnectionString &lt;span class="nt"&gt;--output&lt;/span&gt; tsv | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[:space:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Azure Container App Environment
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az containerapp &lt;span class="nb"&gt;env &lt;/span&gt;create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPPS_ENVIRONMENT&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOCATION&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--infrastructure-subnet-resource-id&lt;/span&gt; &lt;span class="nv"&gt;$INFRASTRUCTURE_SUBNET&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker-bridge-cidr&lt;/span&gt; &lt;span class="s2"&gt;"172.17.0.1/16"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform-reserved-cidr&lt;/span&gt; &lt;span class="s2"&gt;"100.64.0.0/16"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform-reserved-dns-ip&lt;/span&gt; &lt;span class="s2"&gt;"100.64.0.10"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--logs-destination&lt;/span&gt; &lt;span class="s2"&gt;"log-analytics"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--logs-workspace-id&lt;/span&gt; &lt;span class="nv"&gt;$LOG_WORKSPACE_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--logs-workspace-key&lt;/span&gt; &lt;span class="nv"&gt;$LOG_WORKSPACE_KEY&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--internal-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Azure Container App with autoscaling
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az containerapp create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPP_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--image&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINER_IMAGE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--environment&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPPS_ENVIRONMENT&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--registry-server&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINER_REGISTRY_SERVER&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--registry-username&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINER_REGISTRY_USER&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--registry-password&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINER_REGISTRY_PASS&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--secrets&lt;/span&gt; gh-token&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PAT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; servicebus-connection-string&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONNECTION_QUEUE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--env-vars&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secretref:gh-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;RUNNER_SCOPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"org"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;ORG_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GH_ORG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;LABELS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"poc-gh-aca"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;RUNNER_WORKDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/tmp/github-runner"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--cpu&lt;/span&gt; &lt;span class="s2"&gt;"1.75"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--memory&lt;/span&gt; &lt;span class="s2"&gt;"3.5Gi"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--min-replicas&lt;/span&gt; 0 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--max-replicas&lt;/span&gt; 3 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--scale-rule-auth&lt;/span&gt; &lt;span class="s2"&gt;"connection=servicebus-connection-string"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--scale-rule-name&lt;/span&gt; queue-scaling &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--scale-rule-type&lt;/span&gt; azure-servicebus &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--scale-rule-metadata&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"queueName=&lt;/span&gt;&lt;span class="nv"&gt;$SERVICEBUS_QUEUE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"namespace=&lt;/span&gt;&lt;span class="nv"&gt;$SERVICEBUS_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"messageCount=1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;ตรวจสอบ system log
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az containerapp logs show &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPP_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--type&lt;/span&gt; console &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;ตรวจสอบ console log
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az containerapp logs show &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPP_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--type&lt;/span&gt; system &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;ตรวจสอบว่า runner เกิดขึ้นใน organization ของเรา&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lmz57zm30vr42h39pyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lmz57zm30vr42h39pyl.png" alt="github-self-hosted-runner" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ลอง send และ delete message แล้วตรวจสอบ Queue ใน Azure Service Bus และ check จำนวน runner ที่เกิดขึ้นใน organization
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Send message&lt;/span&gt;
az rest &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource&lt;/span&gt; &lt;span class="s2"&gt;"https://servicebus.azure.net"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--method&lt;/span&gt; post &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--headers&lt;/span&gt; &lt;span class="nv"&gt;BrokerProperties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"TimeToLive":3600}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICEBUS_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.servicebus.windows.net/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICEBUS_QUEUE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/messages"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Hello from Az REST with TTL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Receive / Delete message&lt;/span&gt;
az rest &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource&lt;/span&gt; &lt;span class="s2"&gt;"https://servicebus.azure.net"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--method&lt;/span&gt; delete &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICEBUS_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.servicebus.windows.net/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICEBUS_QUEUE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/messages/head"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See console log&lt;/span&gt;
az containerapp logs show &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPP_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--type&lt;/span&gt; console &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See system log&lt;/span&gt;
az containerapp logs show &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINERAPP_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--type&lt;/span&gt; system &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create GitHub workflow to verify our system
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;ทำการ create Service Principal แล้ว federate credential ไปให้ Github Organization และ Repository ที่เราสร้างเตรียมไว้โดยเลือกเป็น branch main&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejw53kzfisu7i43cfl8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejw53kzfisu7i43cfl8r.png" alt="Credential Federation" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;รายละเอียดดูได้จาก &lt;a href="https://dev.to/peepeepopapapeepeepo/let-github-action-access-azure-resources-without-password-f0i"&gt;Let Github Action Access Azure Resources without password&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Create role assignment ให้ Service Principal ของเรามีสิทธิ์ &lt;code&gt;Azure Service Bus Data Receiver&lt;/code&gt; และ &lt;code&gt;Azure Service Bus Data Sender&lt;/code&gt; ที่ Queue ที่เราสร้างไว้&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ovxk3ri7ucyws5h7kno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ovxk3ri7ucyws5h7kno.png" alt="queue-role-assignment" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create role assignment ให้ Service Principal ของเรามีสิทธิ์ &lt;code&gt;Contributor&lt;/code&gt; บน subscription ของเรา&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix8bd5tn0gb06ydzxk82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix8bd5tn0gb06ydzxk82.png" alt="subscription-role-assignment" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;สำหรับสาย free ถ้า GitHub repository ของคุณเป็น public ต้องมา allow ที่ Runner Groups Default ก่อน&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06z4p33zw46jmpp2upxb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06z4p33zw46jmpp2upxb.png" alt="Runner-Groups-Default" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ต่อไปเข้าไปที่ Repository ของตัวเองแล้ว define secret และ variable เหล่านี้

&lt;ul&gt;
&lt;li&gt;Secrets

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AZURE_CLIENT_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AZURE_SUBSCRIPTION_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AZURE_TENANT_ID&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Variables

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AZ_SERVICE_BUS_NAMESPACE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AZ_SERVICE_BUS_QUEUE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s1f9hbjof0riz2whd52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s1f9hbjof0riz2whd52.png" alt="action-menu" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7y6isydhatlaw3770i6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7y6isydhatlaw3770i6.png" alt="action-secret" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fod4xo8h2xaku1lvhugn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fod4xo8h2xaku1lvhugn5.png" alt="action-variable" width="786" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;จากนั้นทำการสร้าง file workflow &lt;code&gt;.github/workflows/demo.yaml&lt;/code&gt; ใน GitHub Repository
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scale up selfhosted runner and run&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AZ_SERVICE_BUS_NAMESPACE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.AZ_SERVICE_BUS_NAMESPACE }}&lt;/span&gt;
  &lt;span class="na"&gt;AZ_SERVICE_BUS_QUEUE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;${{ vars.AZ_SERVICE_BUS_QUEUE }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scale-out-runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Az&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CLI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;login'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scale out self hosted&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
         &lt;span class="s"&gt;az rest \&lt;/span&gt;
         &lt;span class="s"&gt;--resource "https://servicebus.azure.net" \&lt;/span&gt;
         &lt;span class="s"&gt;--method post \&lt;/span&gt;
         &lt;span class="s"&gt;--headers BrokerProperties='{"TimeToLive":3600}' \&lt;/span&gt;
         &lt;span class="s"&gt;--url "https://${{ env.AZ_SERVICE_BUS_NAMESPACE }}.servicebus.windows.net/${{ env.AZ_SERVICE_BUS_QUEUE }}/messages" \&lt;/span&gt;
         &lt;span class="s"&gt;--body "${{ github.run_id }}"&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;self-hosted&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;poc-gh-aca&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;scale-out-runner&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Az&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CLI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;login'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;az&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commands'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;az account show --output table&lt;/span&gt;

  &lt;span class="na"&gt;scale-in-runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;scale-out-runner&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ always() &amp;amp;&amp;amp; needs.scale-out-runner.result == 'success' }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Az&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CLI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;login'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scale in self hosted&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;az rest \&lt;/span&gt;
          &lt;span class="s"&gt;--resource "https://servicebus.azure.net" \&lt;/span&gt;
          &lt;span class="s"&gt;--method delete \&lt;/span&gt;
          &lt;span class="s"&gt;--url "https://${{ env.AZ_SERVICE_BUS_NAMESPACE }}.servicebus.windows.net/${{ env.AZ_SERVICE_BUS_QUEUE }}/messages/head"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99uv7qqnmwpfvf7as9so.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99uv7qqnmwpfvf7as9so.png" alt="github-workflow" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ใน workflow จะมี 3 jobs&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;scale-out-runner&lt;/code&gt;: จะเป็นการ send message เข้าไปที่ Service Bus เพื่อ ให้ container apps scale out GitHub SelfHosted Runners ขึ้นมารับงานไปทำ&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy&lt;/code&gt;: จะเป็นส่วนงานหลักที่ pipeline ต้องทำ โดย job นี้จะทำก็ต่อเมื่อ &lt;code&gt;scale-out-runner&lt;/code&gt; success&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scale-in-runner&lt;/code&gt;: หลังจากทำงานเสร็จไม่ว่าจะ success หรือไม่ job นี้จะทำการ delete message ออกจาก Service Bus เพื่อให้ container apps ทำการ scale in GitHub SelfHosted Runners ลงไป&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;ให้ลองทำการ run workflow หลายๆ ครั้ง พร้อมๆ กัน&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ryjfwty1jyooz0r2d4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ryjfwty1jyooz0r2d4h.png" alt="run-workflow" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ตรวจสอบดูว่าการ autoscale ทำงานได้ตามที่คาดการณไว้หรือไม่&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Service Bus Queue
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F551n6f397wa7fegi1bvk.png" alt="Azure-Service-Bus-Queue" width="800" height="368"&gt;
&lt;/li&gt;
&lt;li&gt;Container App Replicas
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnio0su9ayt7yjfg9xh6h.png" alt="Container-App-Replicas" width="800" height="506"&gt;
&lt;/li&gt;
&lt;li&gt;Github Organization-Level Self-hosted Runners
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frgr3j7sin9hrtezcfxin.png" alt="Github-Organization-Level-Self-hosted-Runners" width="800" height="515"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;หลังจาก workflow run เรียบร้อยหมดแล้ว ลองตรวจสอบ อีกครั้ง ทุกอย่างต้องเป็น 0&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Service Bus Queue
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmu66kv9a66inhhg0llu.png" alt="Azure-Service-Bus-Queue" width="800" height="286"&gt;
&lt;/li&gt;
&lt;li&gt;Container App Replicas
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjpd6mf2yoey8zalax8b.png" alt="Container-App-Replicas" width="800" height="509"&gt;
&lt;/li&gt;
&lt;li&gt;Github Organization-Level Self-hosted Runners
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gq1yk4jihv5p86oyx5d.png" alt="Github-Organization-Level-Self-hosted-Runners" width="800" height="474"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://keda.sh/docs/2.9/scalers/azure-service-bus/" rel="noopener noreferrer"&gt;KEDA | Azure Service Bus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/scale-app?pivots=azure-cli#example-2" rel="noopener noreferrer"&gt;Scaling in Azure Container Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pwd9000/run-docker-based-github-runner-containers-on-azure-container-apps-aca-1n13"&gt;Autoscaling self hosted GitHub runner containers with Azure Container Apps (ACA)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/vnet-custom-internal?tabs=bash&amp;amp;pivots=azure-cli" rel="noopener noreferrer"&gt;Provide a virtual network to an internal Azure Container Apps environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/rest/api/servicebus/send-message-to-queue" rel="noopener noreferrer"&gt;Send Message to Queue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/rest/api/servicebus/receive-and-delete-message-destructive-read" rel="noopener noreferrer"&gt;Receive and Delete Message (Destructive Read)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mauridb.medium.com/calling-azure-rest-api-via-curl-eb10a06127" rel="noopener noreferrer"&gt;Calling Azure REST API via curl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>welcome</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Let Github Action Access Azure Resources without password</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Tue, 03 Jan 2023 11:11:13 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/let-github-action-access-azure-resources-without-password-f0i</link>
      <guid>https://dev.to/peepeepopapapeepeepo/let-github-action-access-azure-resources-without-password-f0i</guid>
      <description>&lt;p&gt;พอกันที่กับการ rotate secret ของ Service Principal ที่ใช้กับ Github Action ทุก 3 เดือน 6 เดือน &lt;br&gt;
พอกันทีกับโอกาสที่ secret ของ Service Principal จะ leak&lt;/p&gt;

&lt;p&gt;วันนี้มีสิ่งที่เรียกว่า &lt;strong&gt;"Workload Identity Federation"&lt;/strong&gt; นำเสนอครับ&lt;/p&gt;

&lt;h2&gt;
  
  
  Workload Identity Federation
&lt;/h2&gt;

&lt;p&gt;คือการที่เราทำการสร้าง trusted relationship กันระหว่าง Microsoft Identity Platform กับ External Identity Provider (IdP) เช่น GitHub พอ trust กันแล้ว เราจะสามารถใช้ token ของ External IdP ไปแลกเป็น access token ของ Azure เพื่อ access resources ใน Azure ที่ถูก protect ด้วย Azure AD ได้&lt;/p&gt;

&lt;h3&gt;
  
  
  External Identity Provider (IdP) ที่ support ตอนนี้ เช่น
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;Google Cloud&lt;/li&gt;
&lt;li&gt;Workload ใน Kubernetes&lt;/li&gt;
&lt;li&gt;External Provider อื่นๆ ที่ support OpenID Connect (OIDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  มาดู flow ดีกว่าว่ามันทำงานยังไง
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2ds8v59cf3092avq6jy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2ds8v59cf3092avq6jy.png" alt="Workload Identity Federation" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;External Workload เช่น github action ทำการขอ token จาก IdP ของตัวเอง&lt;/li&gt;
&lt;li&gt;External IdP ทำการ generate แล้วให้ token กับ External Workload&lt;/li&gt;
&lt;li&gt;External Workload ใช้ token ที่ได้มาเพื่อขอ access token จาก Microsoft identity platform&lt;/li&gt;
&lt;li&gt;Microsoft identity platform ทำการตรวจสอบว่ามี trust relationship สร้างไว้อยู่หรือไม่ และ เอา token ที่ได้ไป validate กับ External IdP&lt;/li&gt;
&lt;li&gt;ถ้าทุกอย่างเรียบร้อย Microsoft identity platform จะสร้าง access token แล้วส่งให้กับ External Workload&lt;/li&gt;
&lt;li&gt;จากนั้น External Workload จะใช้ access token ที่ได้มาเช้าถึง resources ใน Azure โดย access token จะมี permission เท่ากับ service principal ที่ สร้าง trust relationship ไว้&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  มาลองลงมือทำดีกว่า
&lt;/h2&gt;

&lt;p&gt;สิ่งที่เราจะทำคือสร้าง &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 Github Repos ที่มี 2 Actions และ 2 environments ( develop / production )&lt;/li&gt;
&lt;li&gt;2 services principals ( develop / production )&lt;/li&gt;
&lt;li&gt;trust relationship ระหว่าง Service Principals และ Environment ของ Github&lt;/li&gt;
&lt;li&gt;2 resource groups และให้ services principals เป็น Contributors ใน resource groups ( develop / production ) &lt;/li&gt;
&lt;li&gt;สร้าง Github Action Workflow เพื่อทดสอบ storage account ใน resource group ตาม environemnt&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  สร้าง Github Repos และ environment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;สร้าง Github Action Repo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xlvr3nb4xraxkedec9t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xlvr3nb4xraxkedec9t.png" alt="Create Github Action Repo" width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnfjg2489lrfd00okz1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnfjg2489lrfd00okz1i.png" alt="Create Github Action Repo" width="747" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Environment ( ชื่อ environemnt จะถูกใช้ในการ สร้าง trust relationship ใน Azure Portal )

&lt;ul&gt;
&lt;li&gt;develop&lt;/li&gt;
&lt;li&gt;production&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbc1vasemx7g52lj49zi0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbc1vasemx7g52lj49zi0.png" alt="Create Environment" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw07ustvmfmbummfkdw29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw07ustvmfmbummfkdw29.png" alt="Environment" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  สร้าง service principal
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;ใน &lt;code&gt;Azure Portal&lt;/code&gt; &amp;gt; &lt;code&gt;Azure Active Directory&lt;/code&gt; &amp;gt; &lt;code&gt;App registrations&lt;/code&gt; &amp;gt; &lt;code&gt;+ New Registration&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tlsmc6fq7hvkre9j281.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tlsmc6fq7hvkre9j281.png" alt="App registrations" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ใส่ชื่อของ service principal เลือก single tenant แล้วกด &lt;code&gt;Register&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sp-demogithub-az-asse-dev-001&lt;/code&gt; สำหรับ develop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sp-demogithub-az-asse-prd-001&lt;/code&gt; สำหรับ production&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fml2y5e0kdo5nfweb302x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fml2y5e0kdo5nfweb302x.png" alt="New Registration Dev" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  สร้าง Trust Relationship ( Federated credentials )
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Click ที่ Service Principal ที่เราสร้างขึ้น &amp;gt; &lt;code&gt;Certificates &amp;amp; secrets&lt;/code&gt; &amp;gt; &lt;code&gt;Federated credentials&lt;/code&gt; &amp;gt; &lt;code&gt;+ Add credential&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkvgapz72z0987ninc1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkvgapz72z0987ninc1o.png" alt="Federated credentials" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ใส่ข้อมูลของ Github Repo ที่ต้องการสร้าง Trust Relationship ด้วย

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Organization&lt;/code&gt;: ใส่ชื่อ organization ของ ตัวเอง&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Repository&lt;/code&gt;: ชื่อ repo ของ github ที่เราสร้างไว้ก่อนหน้า&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Entity type&lt;/code&gt;: เลือก &lt;code&gt;Environment&lt;/code&gt; ค่าอื่นดูต่อที่ &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#entity-type-examples" rel="noopener noreferrer"&gt;Entity type examples&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Name&lt;/code&gt;: ตั้งชื่อขอ Federated credentials นี้ ( FQDN friendly )&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufo5wt1fvevvq52fp53d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufo5wt1fvevvq52fp53d.png" alt="Trust Relationship" width="800" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;กดที่ overview ของ Service Pricipal แล้วเก็บค่าเหล่านี้ไว้ใช้ add ใน github environment

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AZURE_CLIENT_ID&lt;/code&gt;: Application (client) ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AZURE_TENANT_ID&lt;/code&gt;: Directory (tenant) ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AZURE_SUBSCRIPTION_ID&lt;/code&gt;: subscription ID ที่จำให้ Github Action Provision storage account&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9j776xisq0y84amjbma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9j776xisq0y84amjbma.png" alt="Develop Environment" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ทำแบบเดียวกันกับ Service Principal ของ Production Environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd45vlevx3h33m7fauyto.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd45vlevx3h33m7fauyto.png" alt="Production Environment" width="800" height="757"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  สร้าง resource group แล้ว assign Contributor role ให้กับ Service Principal
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;สร้าง resource groups

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rg-demogithubfed-az-asse-dev-001&lt;/code&gt; สำหรับ develop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rg-demogithubfed-az-asse-prd-001&lt;/code&gt; สำหรับ production&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovlelm71acyh66eun6qq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovlelm71acyh66eun6qq.png" alt="resource groups" width="800" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Role Assignment ให้ 

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sp-demogithub-az-asse-dev-001&lt;/code&gt; เป็น Contributor ใน &lt;code&gt;rg-demogithubfed-az-asse-dev-001&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnt2qn1iwyjr2zizhcjbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnt2qn1iwyjr2zizhcjbz.png" alt="sp-demogithub-az-asse-dev-001" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sp-demogithub-az-asse-prd-001&lt;/code&gt; เป็น Contributor ใน &lt;code&gt;rg-demogithubfed-az-asse-prd-001&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5db1ugdzyopvu50qhfh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5db1ugdzyopvu50qhfh.png" alt="sp-demogithub-az-asse-prd-001" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  สร้าง Github workflow เพื่อทดสอบสร้าง storage account
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;สร้าง Enviroment secret ใน Github

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AZURE_CLIENT_ID&lt;/code&gt;: Application (client) ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AZURE_TENANT_ID&lt;/code&gt;: Directory (tenant) ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AZURE_SUBSCRIPTION_ID&lt;/code&gt;: subscription ID ที่จำให้ Github Action Provision storage account&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81v0lofj52h6eqp6gi6b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81v0lofj52h6eqp6gi6b.png" alt="Enviroment secret 1" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxl6a2ln1emvrgpys8vx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxl6a2ln1emvrgpys8vx.png" alt="Enviroment secret 2" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2d9uufc9249gm36q3a9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2d9uufc9249gm36q3a9.png" alt="Enviroment secret 3" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ทำแบบเดียวกัน กับ production enviroment&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;สร้าง Github Workflow สำหรับ create github action&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Action ที่ใช้ login คือ &lt;a href="https://github.com/marketplace/actions/azure-login" rel="noopener noreferrer"&gt;Azure Login&lt;/a&gt; โดยเราจะทำตามตัวอย่างนี้ &lt;a href="https://github.com/marketplace/actions/azure-login#sample-workflow-that-uses-azure-login-action-using-oidc-to-run-az-cli-linux" rel="noopener noreferrer"&gt;Sample workflow that uses Azure login action using OIDC to run az cli (Linux)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o23ep3yb1618va93g02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o23ep3yb1618va93g02.png" alt="github create file" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;สร้าง file สำหรับ develop =&amp;gt; &lt;code&gt;.github/workflows/dev-demo.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;name: DEV - Demo Azure Login with OIDC
on:
   workflow_dispatch:

permissions:
  id-token: write
  contents: &lt;span class="nb"&gt;read

jobs&lt;/span&gt;:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: develop
    steps:
      - name: &lt;span class="s1"&gt;'Az CLI login'&lt;/span&gt;
        uses: azure/login@v1
        with:
          client-id: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AZURE_CLIENT_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          tenant-id: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AZURE_TENANT_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          subscription-id: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AZURE_SUBSCRIPTION_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

      - name: &lt;span class="s1"&gt;'Run az commands'&lt;/span&gt;
        run: |
          az account show &lt;span class="nt"&gt;--output&lt;/span&gt; table
          az storage account create &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--subscription&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AZURE_SUBSCRIPTION_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--location&lt;/span&gt; southeastasia &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--resource-group&lt;/span&gt; rg-githubfed-az-asse-dev-001 &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--name&lt;/span&gt; stdemofeddev001 &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--sku&lt;/span&gt; Standard_LRS &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--default-action&lt;/span&gt; Deny &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--min-tls-version&lt;/span&gt; TLS1_2 &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;สร้าง file สำหรับ production =&amp;gt; &lt;code&gt;.github/workflows/prd-demo.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;name: PRD - Demo Azure Login with OIDC
on:
   workflow_dispatch:

permissions:
  id-token: write
  contents: &lt;span class="nb"&gt;read

jobs&lt;/span&gt;:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: &lt;span class="s1"&gt;'Az CLI login'&lt;/span&gt;
        uses: azure/login@v1
        with:
          client-id: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AZURE_CLIENT_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          tenant-id: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AZURE_TENANT_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
          subscription-id: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AZURE_SUBSCRIPTION_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

      - name: &lt;span class="s1"&gt;'Run az commands'&lt;/span&gt;
        run: |
          az account show &lt;span class="nt"&gt;--output&lt;/span&gt; table
          az storage account create &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--subscription&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AZURE_SUBSCRIPTION_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--location&lt;/span&gt; southeastasia &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--resource-group&lt;/span&gt; rg-githubfed-az-asse-prd-001 &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--name&lt;/span&gt; stdemofedprd001 &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--sku&lt;/span&gt; Standard_LRS &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--default-action&lt;/span&gt; Deny &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--min-tls-version&lt;/span&gt; TLS1_2 &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;ทดสอบ run Github Action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79ppzoi4iqf4nss07y9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79ppzoi4iqf4nss07y9k.png" alt="Github action run" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tfc3uwb2xpfhjzgitqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tfc3uwb2xpfhjzgitqi.png" alt="Github action result" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ตรวจสอบ storage account ที่ Azure Portal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1s1oon7nzyuqjfcxyzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1s1oon7nzyuqjfcxyzt.png" alt="storage account" width="751" height="116"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation" rel="noopener noreferrer"&gt;Workload identity federation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Cwindows#use-the-azure-login-action-with-openid-connect" rel="noopener noreferrer"&gt;Use the Azure login action with OpenID Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp" rel="noopener noreferrer"&gt;Configure an app to trust an external identity provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/marketplace/actions/azure-login" rel="noopener noreferrer"&gt;Github Action | Azure Login&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gratitude</category>
      <category>community</category>
    </item>
    <item>
      <title>Hybrid DNS Solution with Azure</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Fri, 04 Nov 2022 01:07:56 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/hybrid-dns-solution-with-azure-phi</link>
      <guid>https://dev.to/peepeepopapapeepeepo/hybrid-dns-solution-with-azure-phi</guid>
      <description>&lt;p&gt;หลังจากที่ &lt;a href="https://azure.microsoft.com/en-us/updates/general-availability-azure-dns-private-resolver-hybrid-name-resolution-and-conditional-forwarding/" rel="noopener noreferrer"&gt;Azure ประกาศ GA Azure DNS Private Resolver&lt;/a&gt; ก็ได้ฤกษ์ ลองเล่นซะที หลังจากเก็บไว้ในกองดองอยู่นาน มาเข้าเรื่องกันเลยครับ&lt;/p&gt;

&lt;h3&gt;
  
  
  Azure DNS Private Resolver คืออะไร
&lt;/h3&gt;

&lt;p&gt;สั้นๆ มันคือ resource ที่ทำหน้าที่เป็น DNS server นั้นเอง ในโลก internet DNS เป็นส่วนที่สำคัญมากๆ มันทำให้เราพิมพ์ google.co.th แล้วสามารถ connect ไปที่ google ได้เลยแทนที่จะต้องพิมพ์ &lt;code&gt;142.251.12.94&lt;/code&gt; ซึ่งเป็น IP address ของ goole ที่ผม resolve ได้ในตอนที่เขียน blog นี้&lt;/p&gt;

&lt;p&gt;โดย resource ตัวนี้จะทำงานใน private network ของเราบน Azure  คอย resolve private domain name ของเราให้เป็น private IP address&lt;/p&gt;

&lt;h3&gt;
  
  
  แล้วก่อนหน้านี้ resolve domain name กันอย่างไรล่ะ
&lt;/h3&gt;

&lt;p&gt;ใน Azure จริงๆ แล้ว มัมมี built-in DNS อยู่แล้ว  เรียกว่า Azure-provided DNS มันจะเป็น configuration เนียนๆ อยู่ใน virtual network ของเรา ตรงนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmkm2uewlxzwmjki45inj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmkm2uewlxzwmjki45inj.png" alt=" " width="522" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;โดย มันจะปรากฏในรูปแบบของ IP &lt;code&gt;168.63.129.16&lt;/code&gt; ใน ใน VM ของเรา ที่ deploy ใน virtual network ที่ configure DNS server ไว้ตามข้างต้น โดย &lt;code&gt;168.63.129.16&lt;/code&gt; สามารถ connect จาก Azure resources เท่านั้น&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ systemd-resolve --status | grep 'DNS Servers'
DNS Servers: 168.63.129.16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;รายละเอียดเพิ่มเติม ของ &lt;code&gt;168.63.129.16&lt;/code&gt; สามารถอ่านเพิ่มได้จาก &lt;a href="https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16" rel="noopener noreferrer"&gt;What is IP address 168.63.129.16?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ทีนี้หากเราต้องการ เพิ่ม private domain name ก็แค่สร้าง private dns zone แล้ว link มันกับ virtual network ของเรา เท่านี้ Azure DNS ก็จะสามารถ resolve private domain name ของเราได้แล้ว&lt;/p&gt;

&lt;p&gt;พอเราใช้ไปซักพักความต้องการเริ่มมากขึ้น เริ่มมี requirement ให้เราไปต่อกับ on-premise network ในส่วนของ connetivity นั้นก็ใช้ VPN หรือ Express Route ได้ตามปกติ แต่ในเรื่อง domain name resolution ล่ะ จะทำอย่างไรดี เพราะ on-prem ไม่สามารถ connect มาที่ &lt;code&gt;168.63.129.16&lt;/code&gt; ที่อยู่บน Azure ของเราได้&lt;/p&gt;

&lt;p&gt;ก่อนการมาของ &lt;strong&gt;Azure DNS Private Resolver&lt;/strong&gt; เราใช้วิธีการสร้าง   VM-Based DNS server ขึ้นมา พูดง่ายๆ สร้าง VM แล้วเอา DNS software เช่น Bind9 มา install เอง ถ้าอยากได้ SLA ที่เพิ่มขึ้นก็เอา VM มาใส่ VM Scale Set แล้ว ขั้นหน้าด้วย Load Balancer&lt;/p&gt;

&lt;p&gt;นั่นหมายความว่าเราต้อง manage ทุกอย่างเอง ไม่ว่าจะเป็นการ hardening, OS update, Software upgrade, Backup และอื่นๆ อีกมากมาย  &lt;/p&gt;

&lt;p&gt;แล้วให้ on-premise DNS server ทำ Conditional Forward มาที่ VM-Based DNS server ของเรา &lt;/p&gt;

&lt;p&gt;ในทางกลับกัน เราต้องต้อง configure Conditional Forward ลงไปที่ on-prem ด้วย เพื่อให้ server บน Azure สามารถ resolve domain name ของ on-premise ได้&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Conditional Forward&lt;/strong&gt; คือการ configure ใน DNS server ทำการ forward request ต่อยัง อีก DNS server หนึ่ง เช่น เรามี zone &lt;code&gt;*.example.com&lt;/code&gt; อยู่ที่ server-A เราสามารถ configure ให้ DNS ของเราไปถามต่อที่ server-A ได้ หากมี client ถามว่า &lt;code&gt;demo.example.com&lt;/code&gt; เป็น IP อะไร โดยที่ client ไม่ต้องรู้จัก server-A เลย&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Azure DNS Private Resolver ทำงานอย่างไร
&lt;/h3&gt;

&lt;p&gt;การที่จะทำ Hybrid DNS Solution บน Azure จะมี 2 resources ที่ทำงานร่วมกัน&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DNS Private Resolver&lt;/li&gt;
&lt;li&gt;DNS Forwarding Ruleset&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  DNS Private Resolver
&lt;/h4&gt;

&lt;p&gt;ทำหน้าที่เป็น endpoint ในการทำ name resolution โดยประกอบด้วย 2 endpoints ที่ deploy อยู่คนละ dedicated subnet คือ&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inbound endpoints&lt;/strong&gt;: เป็น ingress subnet โดย 1 endpoint จะมี 1 IP address ของ Load Balancer สำหรับ รับ domain name resolution request จาก on-premise หรือ resource ใน virtual network อื่น&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far0cjtwq2mgvg5zndjng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far0cjtwq2mgvg5zndjng.png" alt="Inbound endpoints" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Outbound endpoints&lt;/strong&gt;: เป็น egress subnet ที่ใช้ออกไป query DNS servers อื่นๆ ใน subnet นี้จะไม่มี IP address ปรากฏ นั่นหมายความว่า outgoing IP address มีโอกาสเป็นไปได้ทั้ง subnet (ผมเดาว่าน่าจะใช้ technic เดียวกับ vnet integration ของ app service)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8c2f8w5essyc7noyr7u5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8c2f8w5essyc7noyr7u5.png" alt="Outbound endpoints" width="800" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  DNS Forwarding Ruleset
&lt;/h4&gt;

&lt;p&gt;ทำหน้าที่เป็น conditional forward configuration ใน vnet ของเรา โดย Ruleset ก็ตามชื่อเลยครับ เป็นกลุ่มของ conditional forward rule โดย rule จะมีหน้าตาดังนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ick5jz3xflcpfwmd47m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ick5jz3xflcpfwmd47m.png" alt="DNS Forwarding Rule" width="565" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;และการจะใช้งาน DNS Forwarding Ruleset ได้ ก็ต้องเอามันไป link กับ virtual network ตาม concept เดียวกับ Private DNS Zone&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlquy5zo2034p8cghmx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlquy5zo2034p8cghmx3.png" alt="DNS Forwarding Ruleset Link" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ยังจำได้ไหม ตัว resolver มี outbound endpoint ซึ่ง deploy ใน dedicated subnet ใน virtual network ดังนั้น ตัว outbound endpoint ก็จะทำตาม DNS Forwarding Ruleset ที่เรา link ไว้ด้วย&lt;/p&gt;

&lt;p&gt;เอ๊ะ! ถ้าเรามีทั้ง Private DNS Zone Link และ DNS Forwarding Ruleset Link ด้วยมันจะทำงานอย่างไร ?&lt;/p&gt;

&lt;p&gt;มันจะ resolve ตาม order นี้ครับ &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Private DNS Zone&lt;/li&gt;
&lt;li&gt;DNS Forwarding Ruleset&lt;/li&gt;
&lt;li&gt;Azure-provided DNS Servers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;สรุป&lt;/strong&gt; ถ้า resolver ได้รับ name resolution request มาจาก client ผ่านทาง inbound endpoint IP address มันจะ forward ออกไปยัง outbound endpoint IP address ซึ่งจะทำการ resolve domain name ตามลำดับข้างต้น แล้ว response สิ่งที่ได้ไปให้กับ client&lt;/p&gt;

&lt;h3&gt;
  
  
  ถ้าพอนึกภาพออกแล้วเรามาลองลงมือทำกันดีกว่า
&lt;/h3&gt;

&lt;p&gt;Architecure ที่เราจะลองทำหน้าตาประมาณนี้ครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7jvarmgfacjh427qzpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7jvarmgfacjh427qzpr.png" alt="demo-architecture" width="746" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เราจะมี 3 virtual network ดังนี้&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;On-Prem&lt;/strong&gt;: เป็นตัวแทนของ on-premise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hub&lt;/strong&gt;: เป็นตัวแทนของ Hub network บน Azure ที่เป็นศูนย์รวมของ connectivity ต่างๆ &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spoke&lt;/strong&gt;: เป็น virtual network ของ application ที่จะมา deploy ใน Azure&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;โดย &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ที่ &lt;strong&gt;On-Prem&lt;/strong&gt; จะมี DNS server ที่มี DNS records ของ domain &lt;code&gt;*.onprem.contoso.com&lt;/code&gt; และทำ conditional forward domain &lt;code&gt;*.private.contoso.com&lt;/code&gt; ไปที่ Resolver ที่ Hub&lt;/li&gt;
&lt;li&gt;ที่ &lt;strong&gt;Hub&lt;/strong&gt; จะมี DNS Private Resolver พระเอกของเรา, Private DNS Zone &lt;code&gt;*.private.contoso.com&lt;/code&gt; และ DNS Forwarding Ruleset พระเอกของเราอีกคน เอาไว้ทำ conditional forward &lt;code&gt;*.onprem.contoso.com&lt;/code&gt; ลงไปที่ On-Prem โดยใน virtual network นี้จะมี VM 1 server เอาไว้ทดสอบ name resolution&lt;/li&gt;
&lt;li&gt;ที่ &lt;strong&gt;Spoke&lt;/strong&gt; มี VM 1 server เอาไว้ทดสอบ name resolution เช่นกัน&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;เรามาเริ่มสร้างกันเลย&lt;/p&gt;

&lt;h5&gt;
  
  
  Create Resource Group
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az group create \
--name rg-demodns-az-asse-sbx-001 \
--location southeastasia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Create Hub
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Virtual Network
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az network vnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnshub-az-asse-sbx-001 \
--address-prefixes 10.1.0.0/16

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--address-prefixes 10.1.1.0/24

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-inbound-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--address-prefixes 10.1.2.0/28

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--address-prefixes 10.1.2.16/28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Virtual Machine
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az vm create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vm-hub-az-asse-sbx-001 \
--computer-name vm-hub-az-asse-sbx-001 \
--image UbuntuLTS \
--size Standard_D2s_v4 \
--accelerated-networking true \
--subnet ${SUBNET_ID} \
--nsg-rule SSH \
--admin-username azureuser \
--ssh-key-values /home/sawitmee/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Private DNS Zone
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az network private-dns zone create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name private.contoso.com

az network private-dns record-set a add-record \
--resource-group rg-demodns-az-asse-sbx-001 \
--zone-name private.contoso.com \
--record-set-name test \
--ipv4-address 10.10.10.10

az network private-dns link vnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name link-vnet-demodnshub-az-asse-sbx-001 \
--zone-name private.contoso.com \
--virtual-network vnet-demodnshub-az-asse-sbx-001 \
-e false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Private Resolver
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VNET_ID=$(
az network vnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnshub-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az dns-resolver create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name adpr-hub-az-asse-sbx-001 \
--id ${VNET_ID}

SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--name snet-inbound-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az dns-resolver inbound-endpoint create \
--resource-group rg-demodns-az-asse-sbx-001 \
--dns-resolver-name adpr-hub-az-asse-sbx-001 \
--name snet-inbound-az-asse-sbx-001 \
--ip-configurations "[{private-ip-address:'',private-ip-allocation-method:'Dynamic',id:'$SUBNET_ID'}]"

SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az dns-resolver outbound-endpoint create \
--resource-group rg-demodns-az-asse-sbx-001 \
--dns-resolver-name adpr-hub-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--id "${SUBNET_ID}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Create Spoke ( + Peering )
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Virtual Network
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az network vnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnsspoke-az-asse-sbx-001 \
--address-prefixes 10.2.0.0/16 \
--dns-servers 10.1.2.4

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--address-prefixes 10.2.1.0/24

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-inbound-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--address-prefixes 10.2.2.0/28

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--address-prefixes 10.2.2.16/28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Peering
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az network vnet peering create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vpeer-vnet-demodnsspoke-az-asse-sbx-001-vnet-demodnshub-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--remote-vnet vnet-demodnshub-az-asse-sbx-001 \
--allow-vnet-access

az network vnet peering create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vpeer-vnet-demodnshub-az-asse-sbx-001-vnet-demodnsspoke-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--remote-vnet vnet-demodnsspoke-az-asse-sbx-001 \
--allow-vnet-access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Virtual Machine
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnsspoke-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az vm create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vm-spoke-az-asse-sbx-001 \
--computer-name vm-spoke-az-asse-sbx-001 \
--image UbuntuLTS \
--size Standard_D2s_v4 \
--accelerated-networking true \
--subnet ${SUBNET_ID} \
--nsg-rule SSH \
--admin-username azureuser \
--ssh-key-values /home/sawitmee/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Test
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nslookup test.private.contoso.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Create On-Premise ( +Peering )
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Virtual Network
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az network vnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnsonprem-az-asse-sbx-001 \
--address-prefixes 10.3.0.0/16

az network vnet subnet create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--vnet-name vnet-demodnsonprem-az-asse-sbx-001 \
--address-prefixes 10.3.1.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Peering
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az network vnet peering create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vpeer-vnet-demodnsonprem-az-asse-sbx-001-vnet-demodnshub-az-asse-sbx-001 \
--vnet-name vnet-demodnsonprem-az-asse-sbx-001 \
--remote-vnet vnet-demodnshub-az-asse-sbx-001 \
--allow-vnet-access

az network vnet peering create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vpeer-vnet-demodnshub-az-asse-sbx-001-vnet-demodnsonprem-az-asse-sbx-001 \
--vnet-name vnet-demodnshub-az-asse-sbx-001 \
--remote-vnet vnet-demodnsonprem-az-asse-sbx-001 \
--allow-vnet-access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;DNS Server ( Windows )
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBNET_ID=$(
az network vnet subnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--vnet-name vnet-demodnsonprem-az-asse-sbx-001 \
--name snet-default-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az vm create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vm-dns-az-asse-sbx-001 \
--computer-name vmdns101s01 \
--image Win2019Datacenter \
--size Standard_D4s_v3 \
--accelerated-networking true \
--subnet ${SUBNET_ID} \
--nsg-rule RDP \
--admin-username azureuser \
--admin-password "&amp;lt;YOUR-PASSWORD&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Setup DNS server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ทำตามนี้นะครับ &lt;a href="https://computingforgeeks.com/install-and-configure-dns-server-in-windows-server/" rel="noopener noreferrer"&gt;Install and Configure DNS Server on Windows Server 2019&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Zone And Conditional Forward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pyvo7m31non5ypuc2tf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pyvo7m31non5ypuc2tf.png" alt="1 Open DNS Manager" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqatdad2rwyjjgf6kqjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqatdad2rwyjjgf6kqjr.png" alt="2 Create New Zone" width="748" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8kyr9an38rgl7excbv0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8kyr9an38rgl7excbv0.png" alt="3 Create New Zone" width="574" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkkg97juhtmap62defyz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkkg97juhtmap62defyz.png" alt="4 Create New Zone" width="580" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1k4wtjo5a6qyyagcc4e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1k4wtjo5a6qyyagcc4e.png" alt="5 Create New Zone" width="612" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbttu08dnl80hhvphs0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbttu08dnl80hhvphs0f.png" alt="6 Create New Zone" width="589" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faahirzswey2caht8aeak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faahirzswey2caht8aeak.png" alt="7 Create New Zone" width="595" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9vsbt44q2wgoq743t2x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9vsbt44q2wgoq743t2x.png" alt="8 Create New Zone" width="435" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wjlpyer8kd2zh1z7zyz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wjlpyer8kd2zh1z7zyz.png" alt="9 Create New Zone" width="559" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxv4ctrope8c95bop5kq4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxv4ctrope8c95bop5kq4.png" alt="10 Set conditional forward to cloud" width="392" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpq9cnful64eccysj3tp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpq9cnful64eccysj3tp4.png" alt="11 Set conditional forward to cloud" width="645" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nslookup test.onprem.contoso.com 127.0.0.1
nslookup test.private.contoso.com 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create DNS Forwarding Ruleset
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Create Rulesets
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
OUTBOUND_ID=$(
az dns-resolver outbound-endpoint show \
--resource-group rg-demodns-az-asse-sbx-001 \
--dns-resolver-name adpr-hub-az-asse-sbx-001 \
--name snet-outbound-az-asse-sbx-001 \
--query id \
--output tsv
)

az dns-resolver forwarding-ruleset create \
--resource-group rg-demodns-az-asse-sbx-001 \
--dns-forwarding-ruleset-name dfrs-onpremcontosocom-az-asse-sbx-001 \
--outbound-endpoints "[{id:'${OUTBOUND_ID}'}]"

VM_ID=$(
az vm show \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vm-dns-az-asse-sbx-001 \
--query id \
--output tsv
)

VM_IPADDR=$(
az vm list-ip-addresses \
--ids ${VM_ID} \
--query '[].virtualMachine.network.privateIpAddresses' \
--output tsv
)

az dns-resolver forwarding-rule create \
--resource-group rg-demodns-az-asse-sbx-001 \
--ruleset-name dfrs-onpremcontosocom-az-asse-sbx-001 \
--forwarding-rule-name onprem-contoso-com \
--domain-name onprem.contoso.com. \
--target-dns-servers "[{ip-address:'${VM_IPADDR}',port:53}]"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Link DNS forwarding ruleset to VNET
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VNET_ID=$(
az network vnet show \
--resource-group rg-demodns-az-asse-sbx-001 \
--name vnet-demodnshub-az-asse-sbx-001 \
--query "id" \
--output tsv
)

az dns-resolver vnet-link create \
--resource-group rg-demodns-az-asse-sbx-001 \
--name link-vnet-demodnshub-az-asse-sbx-001 \
--ruleset-name dfrs-onpremcontosocom-az-asse-sbx-001 \
--id ${VNET_ID}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Test from VM in Hub
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nslookup test.private.contoso.com
nslookup test.onprem.contoso.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Test from VM in Spoke
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nslookup test.private.contoso.com
nslookup test.onprem.contoso.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;เพิ่มเติม&lt;/strong&gt; &lt;br&gt;
ถ้าเรามี firewall ที่ Hub &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;แนะนำให้ enable DNS Proxy ที่ firewall แล้วชี้ DNS Server ของ firewall มาที่ Inbound Endpoint ของ DNS Private Zone Resolver&lt;/li&gt;
&lt;li&gt;ส่วนที่ Spoke Virtual Network ให้ set DNS Server เป็น Private IP address ของ Firewall&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-name-resolution-for-vms-and-role-instances" rel="noopener noreferrer"&gt;Name resolution for resources in Azure virtual networks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/updates/general-availability-azure-dns-private-resolver-hybrid-name-resolution-and-conditional-forwarding/" rel="noopener noreferrer"&gt;General availability: Azure DNS Private Resolver – hybrid name resolution and conditional forwarding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/hybrid/hybrid-dns-infra" rel="noopener noreferrer"&gt;Design a hybrid Domain Name System solution with Azure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
    </item>
    <item>
      <title>CLI to get your current IP address</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Sun, 10 Oct 2021 03:44:02 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/cli-to-get-my-current-ip-address-256p</link>
      <guid>https://dev.to/peepeepopapapeepeepo/cli-to-get-my-current-ip-address-256p</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl ifconfig.co
curl ifconfig.co/json
curl ifconfig.me
curl ifconfig.io
curl wtfismyip.com/text
curl wtfismyip.com/json
curl icanhazip.com
curl icanhazip.com
curl ipinfo.io
curl ipinfo.io/ip
curl checkip.amazonaws.com
curl api.ipify.org
curl ipecho.net/plain
curl whatismyip.akamai.com
curl ident.me

dig +short myip.opendns.com @resolver1.opendns.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;REF: &lt;a href="https://www.reddit.com/r/devops/comments/blh7kv/url_to_get_just_an_ip_with_no_garbage/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/devops/comments/blh7kv/url_to_get_just_an_ip_with_no_garbage/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>LFS258 [11/15]: Logging and Troubleshooting</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Sun, 19 Apr 2020 10:39:15 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/lfs258-11-15-logging-and-troubleshooting-3ldb</link>
      <guid>https://dev.to/peepeepopapapeepeepo/lfs258-11-15-logging-and-troubleshooting-3ldb</guid>
      <description>&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
Basic Troubleshooting Steps: ต้องไล่จากจุดที่เห็นชัดที่สุด ที่เล็กที่สุด แล้วจึงค่อยๆ ขยาย scope ขึ้นไปเรื่อยๆ&lt;/li&gt;
&lt;li&gt;
Ephemeral Containers: เป็นวิธีที่จะช่วยให้เรา debug ปัญหาที่ยากต่อการ reproduce ได้สะดวกยิ่งขึ้น โดยการเพิ่ม container เข้าไปยัง Pod ที่ run อยู่ feature นี้ยังเป็น alpha อยู่ อาจเปลี่ยนแปลงได้ตลอดเวลา และ มัน disable โดย default ถ้าจะใช้ต้องไปเปิด feature gates ที่ api-server ก่อน&lt;/li&gt;
&lt;li&gt;
Cluster Start Sequence: เริ่มจาก systemd ไป start kubelet และ container engine จากนั้น kubelet start static pod ที่เป็น control plane จากนั้น kube-controller-manager ซึ่งเป็น 1 ใน static pods ทำการอ่านค่าจาก etcd แล้ว create resource ต่างๆ ที่เหลือทั้งหมด&lt;/li&gt;
&lt;li&gt;
Monitoring: ตอนนี้ใช้ Metric Server เป็นตัวรวบรวม metric ต่างๆ และ provide standard API ให้คนอื่นเรียกใช้ผ่านมัน&lt;/li&gt;
&lt;li&gt;
Plugins: ถ้า command ของ kubectl ไม่เพียงพอกับความต้องการของเรา เราสามารถเขียนเพิ่มเองได้ในรูปของ plugins&lt;/li&gt;
&lt;li&gt;
Managing Plugins: มี project ชื่อ krew เป็น plugin ของ kubelet ที่ทำหน้าที่เป็น plugin manager คอยจัดการ plugin ต่างๆ ให้ ทั้ง install, upgrade และ uninstall&lt;/li&gt;
&lt;li&gt;
Sniffling Traffic With Wireshark: ใช้ plugin หนึ่งของ krew ชื่อ sniff โดยมันจะไป start docker container ที่มี tcpdump มา dump network ของ container เรา&lt;/li&gt;
&lt;li&gt;
Logging Tools: kuebernetes ใช้ fluentd เป็น unified logging layer เพื่อรวบรวม log แล้ว ship ไป ยัง logging backend ต่างๆ&lt;/li&gt;
&lt;li&gt;More Resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Basic Troubleshooting Steps
&lt;/h1&gt;

&lt;p&gt;ในการ troubleshoot ปัญหาที่เกิดขึ้นใน kubernetes cluster ของเราควรเริ่มต้นจากอะไรที่เห็นชัดเจนและเล็กที่สุดก่อน จากนั้นค่อยๆ ขยายขึ้นมาในระดับ cluster โดย steps ทำงานเป็นได้ดังนี้&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Error ที่เกิดขึ้นจาก command line&lt;/li&gt;
&lt;li&gt;Log และ สถานะของ Pods&lt;/li&gt;
&lt;li&gt;ใช้ shell เพื่อเข้าไป troubleshoot ใน pods หรือ container&lt;/li&gt;
&lt;li&gt;ตรวจสอบสถานะ และ resources ของ Node&lt;/li&gt;
&lt;li&gt;RBAC, SELinux หรือ AppArmor สำหรับ security settings&lt;/li&gt;
&lt;li&gt;ตรวจสอบ API calls ของ kube-apiserver&lt;/li&gt;
&lt;li&gt;Enable auditing&lt;/li&gt;
&lt;li&gt;ตรวจสอบ network ระหว่าง node รวมทั้ง DNS และ firewall&lt;/li&gt;
&lt;li&gt;ตรวจสอบ master server controller ว่า run ครบหรื่อมี error อะไรเกิดขึ้นหรือไม่&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Ephemeral Containers
&lt;/h1&gt;

&lt;p&gt;ตั้งแต่ v1.16 เป็นต้นมา เราสามารถเพิ่ม container เข้าไปยัง Pod ที่ run อยู่ได้ โดยที่ไม่ต้อง re-create pod ใหม่ ด้วยความสามารถนี้ช่วยให้ investigate ปัญหาที่ยากต่อการ reproduce ได้สะดวกยิ่งขึ้น container ทีเพิ่มเข้าไปใน Pods ที่ run อยู่เรียกว่า "&lt;code&gt;ephemeral containers&lt;/code&gt;"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;feature นี้ยังคงเป็น alpha อยู่ อาจเปลี่ยนแปลงได้ &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ด้วยความที่มันถูกเพิ่มเข้าไปใน Pod run อยู่แล้ว ทำให้มันไม่มีความสามารถบางเหมือน container ทั่วไป เช่น&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ไม่มี ports ดังนั้นจึงระบุ  &lt;code&gt;ports&lt;/code&gt;, &lt;code&gt;livenessProbe&lt;/code&gt; และ &lt;code&gt;readinessProbe&lt;/code&gt; ไม่ได้&lt;/li&gt;
&lt;li&gt;เนื่องจาก Pod resource มีคุณสมบัติ immutable (เปลี่ยนแปลงไม่ได้) ดังนั้น ephemeral containers ใช้ &lt;code&gt;resources&lt;/code&gt; ไม่ได้&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;สามารถดูคุณสมบัติเพิ่มเติมได้จาก &lt;a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#ephemeralcontainer-v1-core" rel="noopener noreferrer"&gt;EphemeralContainer reference documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;การเพิ่ม ephemeral containers ไม่สามารถทำผ่าน &lt;code&gt;pod.spec&lt;/code&gt; โดยใช้ &lt;code&gt;kubectl edit&lt;/code&gt; ได้ &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ephemeral containers&lt;/strong&gt; ถูก disable โดย default ดังนั้นต้องทำการ enable ก่อนโดยเพิ่ม &lt;code&gt;--feature-gates=EphemeralContainers=true&lt;/code&gt; ใน options ของ api-server ใน &lt;code&gt;/etc/kubernetes/manifests/ kube-apiserver.yaml&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;การใช้งาน Ephemeral Container ก่อน v1.18 ต้องสั่งงานผ่าน API ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# สร้าง pod ตัวอย่างที่ไม่มี shell &lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl run ephemeral-demo &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;k8s.gcr.io/pause:3.1 &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never
pod/ephemeral-demo created

&lt;span class="c"&gt;# เพิ่ม ephemeral container เข้าไปใน pod&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ec.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
    "apiVersion": "v1",
    "kind": "EphemeralContainers",
    "metadata": {
            "name": "ephemeral-demo"
    },
    "ephemeralContainers": [{
        "command": [
            "sh"
        ],
        "image": "busybox",
        "imagePullPolicy": "IfNotPresent",
        "name": "debugger",
        "stdin": true,
        "tty": true,
        "terminationMessagePolicy": "File"
    }]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl replace &lt;span class="nt"&gt;--raw&lt;/span&gt; /api/v1/namespaces/default/pods/ephemeral-demo/ephemeralcontainers &lt;span class="nt"&gt;-f&lt;/span&gt; ec.json
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe pod ephemeral-demo
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
Ephemeral Containers:
debugger:
    Image:      busybox
    Port:       &amp;lt;none&amp;gt;
    Host Port:  &amp;lt;none&amp;gt;
    Command:
    sh
    Environment:  &amp;lt;none&amp;gt;
    Mounts:       &amp;lt;none&amp;gt;
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Attach ไปยัง ephemeral container เพื่อทำการ debug&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl attach &lt;span class="nt"&gt;-it&lt;/span&gt; example-pod &lt;span class="nt"&gt;-c&lt;/span&gt; debugger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้าใน v1.18 สามารถ run &lt;code&gt;kubectl alpha debug -it ...&lt;/code&gt; ได้เลย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# สร้าง pod ตัวอย่างที่ไม่มี shell &lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl run ephemeral-demo &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;k8s.gcr.io/pause:3.1 &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never
pod/ephemeral-demo created

&lt;span class="c"&gt;# เพิ่ม ephemeral container เข้าไปใน pod เพื่อทำการ debug&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl alpha debug &lt;span class="nt"&gt;-it&lt;/span&gt; ephemeral-demo &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;busybox &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ephemeral-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Cluster Start Sequence
&lt;/h1&gt;

&lt;p&gt;ถ้าเรา setup cluster ของเราด้วย &lt;code&gt;kubeadm&lt;/code&gt; หรือ tool automation อื่นๆ ที่ใช้ &lt;code&gt;kubeadm&lt;/code&gt; ลำดับการ startup ของ cluster เราจะเริ่มต้นดังนี้&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;systemd&lt;/code&gt; ของแต่ละเครื่องใน cluster จะทำการ start &lt;strong&gt;kubelet&lt;/strong&gt; ขึ้นมาก่อน ซึ่งเราสามารถตรวจสอบ parameter ต่างๆ ในการ start &lt;code&gt;kubelet&lt;/code&gt; ได้จาก configuration file ของมันที่ &lt;code&gt;{/etc,/usr/lib/systemd}/system/kubelet.service.d/10-kubeadm.conf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubelet&lt;/code&gt; จะทำการ start &lt;strong&gt;Static Pod&lt;/strong&gt; โดยเข้าไปอ่าน file ที่ระบุมาใน parameter &lt;code&gt;--config=&lt;/code&gt; ซึ่ง default จะเป็น &lt;code&gt;/var/lib/kubelet/config.yaml&lt;/code&gt; จากนั้น หาตัวแปร &lt;code&gt;staticPodPath&lt;/code&gt; ซึ่งเป็นตัวที่ระบุ path ที่เก็บ configuration file ของแต่ละ static pod ซึ่ง โดย default จะชี้ไปที่ &lt;code&gt;/etc/kubernetes/manifests/&lt;/code&gt; จากนั้นจึงทำการ start static pod ตาม configuration file โดยปกติจะมี 4 control plane pods นี้

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;etcd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kube-controller-manager&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kube-scheduler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kube-apiserver&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kube-controller-manager&lt;/code&gt; ที่ทำหน้าที่เป็น watch loop และ controllers จะอ่าน data จาก &lt;code&gt;etcd&lt;/code&gt; เพื่อทำการสร้าง resources ที่เหลือทั้งหมด&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fl8wmakbhaqt25l44pej5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fl8wmakbhaqt25l44pej5.png" alt="Cluster Start Sequence" width="731" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Monitoring
&lt;/h1&gt;

&lt;p&gt;การ monitoring ทุกคนคงรู้จักกันดีอยู่แล้วว่ามันคือการไปเอาค่า (metric) ต่างๆ จาก infrastructure และ application เพื่อนำมาตรวจวัดและวิเตราะห์ เพื่อต่อยอดในการทำงานอื่นๆ ต่อไป &lt;/p&gt;

&lt;p&gt;ใน kubernetes เมื่อก่อนจะใช้ &lt;strong&gt;Heapster&lt;/strong&gt; ในการรวบรวม metrics ตาม components ต่างๆ ใน cluster แต่ตอนนี้ &lt;strong&gt;Heapster&lt;/strong&gt; ได้ deplicated ไปแล้ว และมีสิ่งที่มาแทนเรียกว่า &lt;strong&gt;Metrics Server&lt;/strong&gt; โดย &lt;strong&gt;Metric Server&lt;/strong&gt; ทำหน้าที่ตัวรวบรวม metrics เช่นเดิม เพิ่มเติมคือมี standard API ซึ่งจะให้ agent ต่างๆ เข้ามาดึงข้อมูลไปทำงานต่อ เช่น การทำ autocalers (เพิ่มจำนวน replica ตาม workload) เป็นต้น&lt;/p&gt;

&lt;p&gt;ในส่วนของ data store และ visualization ของที่คู่กับ &lt;strong&gt;Heapster&lt;/strong&gt; ก็คือ &lt;strong&gt;cAdvisor&lt;/strong&gt; เมื่อ &lt;strong&gt;Heapster&lt;/strong&gt; ไม่อยู่แล้วจึงมี tool ตัวอื่นขึ้นมมาแทนนั่นคือ &lt;strong&gt;Prometheus&lt;/strong&gt; ซึ่งเป็น application ที่อยู่ใน &lt;a href="https://www.cncf.io/" rel="noopener noreferrer"&gt;CNCF&lt;/a&gt; เช่นเดียวกับ Kubernetes โดย &lt;strong&gt;Prometheus&lt;/strong&gt; ทำหน้าที่เป็น plugin ของ kubernetes ที่ใช้ในการดึงค่าของ resources ต่างๆ ใน cluster ผ่าน &lt;strong&gt;Metric Server&lt;/strong&gt; นอกจากนั้นมันยังมี library ในภาษาต่างๆ เพื่อ integrate และ ดึง metric ในระดับ application ด้วย&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Plugins
&lt;/h1&gt;

&lt;p&gt;ถ้า &lt;code&gt;kubectl&lt;/code&gt; ไม่พอความต้องการของเรา เราสามารถเขียน sub-command เพิ่มได้เอง เพียงแค่ตั้งชื่อให้ขึ้นต้นว่า &lt;code&gt;kubectl-&lt;/code&gt; เช่น &lt;code&gt;kubectl-sniff&lt;/code&gt; แล้ววางไว้ใน directory ไหนก็ได้ที่ระบุไว้ใน environment variable ชื่อ &lt;code&gt;PATH&lt;/code&gt; จากนั้นเปลี่ยน mode ให้ execute ได้ เราก้ได้ sub-comand ใหม่ขึ้นมาแล้ว&lt;/p&gt;

&lt;p&gt;ตัวอย่างการสร้าง plugin ขึ้นมาใช้เอง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# สร้าง plugin และ register ให้ kubectl เรียกได้&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl-foo &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
#!/bin/bash

# optional argument handling
if [[ "&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;1" == "version" ]]
then
    echo "1.0.0"
    exit 0
fi

# optional argument handling
if [[ "&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;1" == "config" ]]
then
    echo "&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;KUBECONFIG"
    exit 0
fi

echo "I am a plugin named kubectl-foo"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./kubectl-foo
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/admin/.local/bin:/home/admin/bin
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ./kubectl-foo /usr/local/bin

&lt;span class="c"&gt;# List plugins&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl plugin list
The following compatible plugins are available:

/usr/local/bin/kubectl-foo

&lt;span class="c"&gt;# ทดลองใช้งาน&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl foo
I am a plugin named kubectl-foo
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl foo version
1.0.0

&lt;span class="c"&gt;# จะเห็นว่า kubectl pass environment variables ทุกอย่างมาให้ script เราด้วย&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.kube/config
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl foo config
/home/admin/.kube/config
&lt;span class="nv"&gt;$ KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/kube/config kubectl foo config
/etc/kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;&lt;strong&gt;ข้อควรรู้&lt;/strong&gt;&lt;/u&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ถ้าเราตั้งชื่อ plugin เป็น &lt;code&gt;kubectl-foo-bar-baz&lt;/code&gt; เราจะใช้ได้แบบ &lt;code&gt;kubectl foo bar baz&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\n\necho "My first command-line argument was $1"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl-foo-bar-baz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x ./kubectl-foo-bar-baz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ./kubectl-foo-bar-baz /usr/local/bin

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl plugin list
The following compatible plugins are available:

/usr/local/bin/kubectl-foo
/usr/local/bin/kubectl-foo-bar-baz

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl foo bar baz arg1 &lt;span class="nt"&gt;--meaningless-flag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;My first command-line argument was arg1
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ในการเรียกใช้ plugin จะใช้ concept best match&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\n\necho "My fizz buzz command-line argument was $1"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl-fizz-buzz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\n\necho "My fizz command-line argument was $1"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl-fizz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x ./kubectl-fizz-buzz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x ./kubectl-fizz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ./kubectl-fizz-buzz /usr/local/bin
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ./kubectl-fizz /usr/local/bin

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl plugin list
The following compatible plugins are available:

/usr/local/bin/kubectl-fizz
/usr/local/bin/kubectl-fizz-buzz

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl fizz buzz arg1
My fizz buzz command-line argument was arg1
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl fizz arg1
My fizz command-line argument was arg1
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ถ้าตั้งชื่อมี &lt;code&gt;underscore (_)&lt;/code&gt; เวลาเรียกใช้สามารถใช้ได้ทั้ง &lt;code&gt;underscore (_)&lt;/code&gt; และ &lt;code&gt;dash(-)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\n\necho "I am a plugin with a dash in my name"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./kubectl-foo_bar
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x ./kubectl-foo_bar
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ./kubectl-foo_bar /usr/local/bin

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl plugin list
The following compatible plugins are available:

/usr/local/bin/kubectl-foo_bar

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl foo_bar
I am a plugin with a dash &lt;span class="k"&gt;in &lt;/span&gt;my name
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl foo-bar
I am a plugin with a dash &lt;span class="k"&gt;in &lt;/span&gt;my name
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ถ้ามี file ชื่อซ้ำกันในแต่ละ directory ของ &lt;code&gt;PATH&lt;/code&gt; file ที่อยู่ใน directory แรกจะถูกเรียกใช้งาน (overshadow)&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\n\necho "My first command-line argument was $1"'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /usr/local/bin/kubectl-overshadow
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\n\necho "My second command-line argument was $1"'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt;  /usr/sbin/kubectl-overshadow
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/kubectl-overshadow
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/sbin/kubectl-overshadow

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl plugin list
The following compatible plugins are available:

/usr/local/bin/kubectl-overshadow
/usr/sbin/kubectl-overshadow
- warning: /usr/sbin/kubectl-overshadow is overshadowed by a similarly named plugin: /usr/local/bin/kubectl-overshadow

error: one plugin warning was found

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl overshadow 1234
My first command-line argument was 1234
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ไม่สามารถ override built-in sub-command ของ kubelet ได้&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\n\necho "My version is 1.0.0"'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /usr/local/bin/kubectl-version
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/kubectl-version

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl plugin list
The following compatible plugins are available:

/usr/local/bin/kubectl-version
- warning: kubectl-version overwrites existing &lt;span class="nb"&gt;command&lt;/span&gt;: &lt;span class="s2"&gt;"kubectl version"&lt;/span&gt;

error: one plugin warning was found

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl version
Client Version: version.Info&lt;span class="o"&gt;{&lt;/span&gt;Major:&lt;span class="s2"&gt;"1"&lt;/span&gt;, Minor:&lt;span class="s2"&gt;"18"&lt;/span&gt;, GitVersion:&lt;span class="s2"&gt;"v1.18.2"&lt;/span&gt;, GitCommit:&lt;span class="s2"&gt;"52c56ce7a8272c798dbc29846288d7cd9fbae032"&lt;/span&gt;, GitTreeState:&lt;span class="s2"&gt;"clean"&lt;/span&gt;, BuildDate:&lt;span class="s2"&gt;"2020-04-16T11:56:40Z"&lt;/span&gt;, GoVersion:&lt;span class="s2"&gt;"go1.13.9"&lt;/span&gt;, Compiler:&lt;span class="s2"&gt;"gc"&lt;/span&gt;, Platform:&lt;span class="s2"&gt;"linux/amd64"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
Server Version: version.Info&lt;span class="o"&gt;{&lt;/span&gt;Major:&lt;span class="s2"&gt;"1"&lt;/span&gt;, Minor:&lt;span class="s2"&gt;"18"&lt;/span&gt;, GitVersion:&lt;span class="s2"&gt;"v1.18.2"&lt;/span&gt;, GitCommit:&lt;span class="s2"&gt;"52c56ce7a8272c798dbc29846288d7cd9fbae032"&lt;/span&gt;, GitTreeState:&lt;span class="s2"&gt;"clean"&lt;/span&gt;, BuildDate:&lt;span class="s2"&gt;"2020-04-16T11:48:36Z"&lt;/span&gt;, GoVersion:&lt;span class="s2"&gt;"go1.13.9"&lt;/span&gt;, Compiler:&lt;span class="s2"&gt;"gc"&lt;/span&gt;, Platform:&lt;span class="s2"&gt;"linux/amd64"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Managing Plugins
&lt;/h1&gt;

&lt;p&gt;ถ้าหากคุณ develop plugin ออกมาได้ดี และ อยากจะ distribute ให้คนอื่นใช้ด้วย มี project ที่ชื่อว่า &lt;a href="https://krew.sigs.k8s.io/" rel="noopener noreferrer"&gt;krew&lt;/a&gt; ซึ่งทำหน้าที่เป็น plugin manager ของ &lt;code&gt;kubectl&lt;/code&gt; อ่านเพิ่มเติมการ upload plugin ของตัวเองได้จาก &lt;a href="https://krew.sigs.k8s.io/docs/developer-guide/" rel="noopener noreferrer"&gt;Developer Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในส่วนนี้จะขอแสดงเพียงติดตั้งและใช้งานบน CentOS&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://krew.sigs.k8s.io/docs/user-guide/setup/install/" rel="noopener noreferrer"&gt;Install and setup krew&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Installation&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;git
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSLO&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/kubernetes-sigs/krew/releases/latest/download/krew.{tar.gz,yaml}"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="nb"&gt;tar &lt;/span&gt;zxvf krew.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="nv"&gt;KREW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./krew-&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'[:upper:]'&lt;/span&gt; &lt;span class="s1"&gt;'[:lower:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;_amd64"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KREW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--manifest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;krew.yaml &lt;span class="nt"&gt;--archive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;krew.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KREW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; update
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
export PATH="&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;{KREW_ROOT:-&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;HOME/.krew}/bin:&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;PATH"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc

&lt;span class="c"&gt;# Verification&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl krew version
OPTION            VALUE
GitTag            v0.3.4
GitCommit         324f5ed
IndexURI          https://github.com/kubernetes-sigs/krew-index.git
BasePath          /home/admin/.krew
IndexPath         /home/admin/.krew/index
InstallPath       /home/admin/.krew/store
BinPath           /home/admin/.krew/bin
DetectedPlatform  linux/amd64
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://krew.sigs.k8s.io/docs/user-guide/quickstart/" rel="noopener noreferrer"&gt;วิธีการใช้งาน&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download the plugin list&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl krew update
Updated the &lt;span class="nb"&gt;local &lt;/span&gt;copy of plugin index.

&lt;span class="c"&gt;# Discover plugins available on Krew&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl krew search
NAME                            DESCRIPTION                                         INSTALLED
access-matrix                   Show an RBAC access matrix &lt;span class="k"&gt;for &lt;/span&gt;server resources     no
advise-psp                      Suggests PodSecurityPolicies &lt;span class="k"&gt;for &lt;/span&gt;cluster.           no
apparmor-manager                Manage AppArmor profiles &lt;span class="k"&gt;for &lt;/span&gt;cluster.               no
auth-proxy                      Authentication proxy to a pod or service            no
bulk-action                     Do bulk actions on Kubernetes resources.            no
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Choose a plugin from the list and install it&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl krew &lt;span class="nb"&gt;install whoami
&lt;/span&gt;Updated the &lt;span class="nb"&gt;local &lt;/span&gt;copy of plugin index.
Installing plugin: &lt;span class="nb"&gt;whoami
&lt;/span&gt;Installed plugin: &lt;span class="nb"&gt;whoami&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Use the installed plugin&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;whoami
&lt;/span&gt;kubecfg:certauth:admin

&lt;span class="c"&gt;# List existing plugins&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl plugin list
The following compatible plugins are available:

/home/admin/.krew/bin/kubectl-krew
/home/admin/.krew/bin/kubectl-whoami

&lt;span class="c"&gt;# Keep your plugins up-to-date&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl krew upgrade
Updated the &lt;span class="nb"&gt;local &lt;/span&gt;copy of plugin index.
Upgrading plugin: krew
Skipping plugin krew, it is already on the newest version
Upgrading plugin: &lt;span class="nb"&gt;whoami
&lt;/span&gt;Skipping plugin &lt;span class="nb"&gt;whoami&lt;/span&gt;, it is already on the newest version

&lt;span class="c"&gt;# Uninstall a plugin you no longer use&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl krew uninstall &lt;span class="nb"&gt;whoami
&lt;/span&gt;Uninstalled plugin &lt;span class="nb"&gt;whoami&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Sniffling Traffic With Wireshark
&lt;/h1&gt;

&lt;p&gt;จากหัวข้อที่แล้วเรารู้จัก knew กันไปแล้ว คราวนี้เรามาใช้ plugin ของมันที่ชื่อว่า sniff เพื่อทำการ tcpdump network ภายใน container ของเรากัน โดยสามารถอ่านรายละเอียดได้จาก &lt;a href="https://github.com/eldadru/ksniff" rel="noopener noreferrer"&gt;github ของ eldadru&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;wireshark
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl krew &lt;span class="nb"&gt;install &lt;/span&gt;sniff
Updated the &lt;span class="nb"&gt;local &lt;/span&gt;copy of plugin index.
Installing plugin: sniff
Installed plugin: sniff
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl run demo &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx  &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never
pod/demo created

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl sniff &lt;span class="nt"&gt;-p&lt;/span&gt; demo &lt;span class="nt"&gt;-c&lt;/span&gt; demo &lt;span class="nt"&gt;-o&lt;/span&gt; - | tshark &lt;span class="nt"&gt;-r&lt;/span&gt; -
INFO[0000] sniffing method: privileged pod              
INFO[0000] using tcpdump path at: &lt;span class="s1"&gt;'/home/admin/.krew/store/sniff/v1.4.1/static-tcpdump'&lt;/span&gt; 
INFO[0000] sniffing on pod: &lt;span class="s1"&gt;'demo'&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;namespace: &lt;span class="s1"&gt;'default'&lt;/span&gt;, container: &lt;span class="s1"&gt;'demo'&lt;/span&gt;, filter: &lt;span class="s1"&gt;''&lt;/span&gt;, interface: &lt;span class="s1"&gt;'any'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; 
INFO[0000] creating privileged pod on node: &lt;span class="s1"&gt;'kube-0002.novalocal'&lt;/span&gt; 
INFO[0000] pod created: &amp;amp;Pod&lt;span class="o"&gt;{&lt;/span&gt;ObjectMeta:&lt;span class="o"&gt;{&lt;/span&gt;ksniff-k9vxv ksniff- default
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
 45         37 192.168.93.128 -&amp;gt; 192.168.52.75 TCP 76 40910 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; http &lt;span class="o"&gt;[&lt;/span&gt;SYN] &lt;span class="nv"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;Win&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;28000 &lt;span class="nv"&gt;Len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;MSS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1400 &lt;span class="nv"&gt;SACK_PERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;TSval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3831288949 &lt;span class="nv"&gt;TSecr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;WS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;128
 46         37 192.168.52.75 -&amp;gt; 192.168.93.128 TCP 76 http &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 40910 &lt;span class="o"&gt;[&lt;/span&gt;SYN, ACK] &lt;span class="nv"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;Ack&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;Win&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;27760 &lt;span class="nv"&gt;Len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;MSS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1400 &lt;span class="nv"&gt;SACK_PERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;TSval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3831285944 &lt;span class="nv"&gt;TSecr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3831288949 &lt;span class="nv"&gt;WS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;128
 47         37 192.168.93.128 -&amp;gt; 192.168.52.75 TCP 68 40910 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; http &lt;span class="o"&gt;[&lt;/span&gt;ACK] &lt;span class="nv"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;Ack&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;Win&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;28032 &lt;span class="nv"&gt;Len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;TSval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3831288949 &lt;span class="nv"&gt;TSecr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3831285944
 48         37 192.168.93.128 -&amp;gt; 192.168.52.75 HTTP 145 GET / HTTP/1.1 
 49         37 192.168.52.75 -&amp;gt; 192.168.93.128 TCP 68 http &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 40910 &lt;span class="o"&gt;[&lt;/span&gt;ACK] &lt;span class="nv"&gt;Seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;Ack&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;78 &lt;span class="nv"&gt;Win&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;27776 &lt;span class="nv"&gt;Len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;TSval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3831285945 &lt;span class="nv"&gt;TSecr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3831288950
 50         37 192.168.52.75 -&amp;gt; 192.168.93.128 TCP 307 &lt;span class="o"&gt;[&lt;/span&gt;TCP segment of a reassembled PDU]
 51         37 192.168.52.75 -&amp;gt; 192.168.93.128 HTTP 680 HTTP/1.1 200 OK  &lt;span class="o"&gt;(&lt;/span&gt;text/html&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จะเห็นว่า หลักการของมันคือการ ไปสร้าง docker container &lt;code&gt;corfr/tcpdump&lt;/code&gt; ที่ attach เข้า network เดียวกับ pod เรา แล้วทำการ tcpdump&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Logging Tools
&lt;/h1&gt;

&lt;p&gt;เรื่อง Log ถือเป็นเรื่องใหญ่มากในวงการ IT หลักการโดยทั่วไปของคือ ทำการ ingest log เข้ามาเก็บไว้ที่ seach engine แล้วทำการ query ด้วย search syntax เพื่อแสดงผลผ่าน dashboard ซึ่ง application ที่ได้รับความนิยมคงหนีไม่พ้น Elastic Stack ซึ่งประกอบไปด้วย Elasticsearch, Logstash และ Kibana&lt;/p&gt;

&lt;p&gt;ใน kubernetes, &lt;code&gt;kubelet&lt;/code&gt; จะทำการ write container log ลง local file ผ่าน Docker logging driver จึงมักใช้ &lt;code&gt;Fluentd&lt;/code&gt; หรือ &lt;code&gt;Fluent Bit&lt;/code&gt; ในการ รวม log แล้วจึงส่งออกไปยัง log server ไม่ว่าจะเป็น Elasticsearch หรือ Prometheus&lt;/p&gt;

&lt;p&gt;Logging Architecture ของ kubernetes มีดังนี้ &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Logging at the node level&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwcqojynvftzj6ocsdm1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwcqojynvftzj6ocsdm1v.png" alt="Logging at the node level" width="500" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ไม่มีการ ship ออกไปยัง logging backend ทุกอย่างอยู่บนเครื่องใช้ log rotate engine ของ OS นั้นๆ ในการ rotate log ถ้าใน linux ก็จะเป็น &lt;a href="https://linux.die.net/man/8/logrotate" rel="noopener noreferrer"&gt;logrotate&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cluster-level logging architectures&lt;/p&gt;

&lt;p&gt;2.1 Using a node logging agent&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz65n8xfvdl6a4zx0mpyc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz65n8xfvdl6a4zx0mpyc.png" alt="Cluster-level logging architectures" width="500" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;container ทำการเขียน log ออกทาง &lt;code&gt;stdout&lt;/code&gt; และ &lt;code&gt;stderr&lt;/code&gt; จากนั้น log driver ของ container runtime จะทำการ write log ลง local file จากนั้น เราจึงใช้ logging agent เช่น &lt;code&gt;fluentd&lt;/code&gt; ในการ ship log ออกไปยัง logging backend อย่างเช่น Elasticsearch หรือ Stackdriver Logging&lt;/p&gt;

&lt;p&gt;2.2 Using a sidecar container with the logging agent&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming sidecar container&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxaqtlqrrqe8em9vvxgb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxaqtlqrrqe8em9vvxgb1.png" alt="Streaming sidecar container" width="500" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในกรณีที่ application container ไม่สามารถ write log ไปยัง &lt;code&gt;stdout&lt;/code&gt; และ &lt;code&gt;stderr&lt;/code&gt; ได้ จึงต้องมี sidecar ซึ่งเป็น container ที่อยู่ใน Pod เดียวกับ application container เพื่อทำการอ่าน file, socket หรือ journald เพื่อ redirect มายัง &lt;code&gt;stdout&lt;/code&gt; และ &lt;code&gt;stderr&lt;/code&gt; เพื่อให้ log driver ของ container runtime จะทำการ write log ลง local file และ logging agent ทำการอ่าน file นั้นแล้ว ship ไปยัง logging backend ต่อไป&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sidecar container with a logging agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnjzymot4kil7wemub7os.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnjzymot4kil7wemub7os.png" alt="Sidecar container with a logging agent" width="500" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ถ้าคุณไม่สะดวกใช้ node-level logging agent ในการ ship log คุณสามารถ install sidecar ที่ทำหน้าที่เป็น logging agent คู่กับ application container เพื่อ ship log ตรงไปยัง logging backend เลยก็ได้&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exposing logs directly from the application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ft2n1r2467zgrctflvinl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ft2n1r2467zgrctflvinl.png" alt="Exposing logs directly from the application" width="500" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;วิธีสุดท้ายถ้าคุณสามารถปรับ application ให้ ship log ไปยัง logging agent เองได้เลย วิธีนี้ก็สามารถทำได้เช่นกัน&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  More Resources
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/debug-application-cluster/troubleshooting/" rel="noopener noreferrer"&gt;General Guidelines and instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/debug-application-cluster/debug-application/" rel="noopener noreferrer"&gt;Troubleshoot Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/debug-application-cluster/debug-cluster/" rel="noopener noreferrer"&gt;Troubleshoot Clusters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/debug-application-cluster/debug-pod-replication-controller/" rel="noopener noreferrer"&gt;Debug Pods and ReplicationControllers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/debug-application-cluster/debug-service/" rel="noopener noreferrer"&gt;Debug Services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kubernetes/kubernetes/issues" rel="noopener noreferrer"&gt;Kubernetes Issues and Bug Tracking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.slack.com/" rel="noopener noreferrer"&gt;Slack Channel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>docker</category>
      <category>beginners</category>
    </item>
    <item>
      <title>LFS258 [10/15]: Scheduling</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Tue, 14 Apr 2020 01:51:53 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/lfs258-10-15-scheduling-419n</link>
      <guid>https://dev.to/peepeepopapapeepeepo/lfs258-10-15-scheduling-419n</guid>
      <description>&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
Kube-Scheduler: เป็น process หลักในการทำเรื่อง schedule โดยมีหลักการทำงาน 2 ขั้นตอนคือ &lt;code&gt;Filtering&lt;/code&gt; คือ filter nodes ที่ไม่เหมาะสมออกไป และ &lt;code&gt;Scoring&lt;/code&gt; เป็นการให้คะแนน node ที่ผ่านการ filering เพื่อหา node ที่้เหมาะสมกับ pod มากที่สุด&lt;/li&gt;
&lt;li&gt;
Predicates and Priorities: เป็น schedule policy ตัว stable ของ kubenetes&lt;/li&gt;
&lt;li&gt;
Extension Points and Plugins: เป็น schedule policy ตัวใหม่ของ kubernetes ที่เพิ่งเป็น beta ใน v1.18&lt;/li&gt;
&lt;li&gt;
Pod Specification: เราสามารถกำหนดความสามารถพิเศษและความต้องการของ Pod ใน pod specification เพื่อชี้นำการ schedule pod ไปยัง node ที่เหมาะสมของ kubernetes ได้&lt;/li&gt;
&lt;li&gt;
Specifiying the nodeName: เป็นการระบุ hostname ของ node ที่เราต้องการให้ Pod ไปอยู้&lt;/li&gt;
&lt;li&gt;
Specifiying the nodeSelector: เป็นการระบุ labels ของ node ที่ pod ต้องการ ให้ scheduler ส่ง pod ไป run&lt;/li&gt;
&lt;li&gt;
Pod Affinity Rules: เป็นการระบุให้ pod ชุดหนึ่งอยู่ใน location เดียวกับ&lt;/li&gt;
&lt;li&gt;
Node Affinity Rules: เหมือน nodeSelector แต่เพิ่มความยืดหยุ่นของ affinity เข้ามา&lt;/li&gt;
&lt;li&gt;
Taints: เป็นการระบุข้อจำกัดของ node เช่น เฉพาะกลุ่ม หรือ มี hardware พิเศษติดตั้งอยู่&lt;/li&gt;
&lt;li&gt;
Tolerations: เป็นการระบุความทนทานต่อ taints ของ pods โดย ถ้า pod มี toleration ต่อ taints ของ node จะสามารถถูก schedule ไป run ใน node นั้นได้&lt;/li&gt;
&lt;li&gt;
Custom Scheduler: ถ้า scheduler ที่เป็น default ไม่ถูกใจ เราสามารถ run ตัว scheduler นี้ขึ้นมาอีกตัว โดยที่ configuration ตามความต้องการของเรา เพื่อเป็น secondary scheduler ได้ หรือเขียนใหม่เองหมดก็ยังได้ แต่ ใน pod specification ต้องระบุ scheduler name ที่เราต้องการใช้ด้วย ไม่งั้นจะใช้ default scheduler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Kube-Scheduler
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Kube-Scheduler&lt;/code&gt; ทำหน้าที่ในการหา worker node ที่เหมาะสมให้ pod ไป run โดยใช้ algorithm ที่ชื่อว่า "topology awareness" ผู้ใช้สามารถตั้ง priority ของ pod ได้ โดย pod ไหนมี priority มากกว่าจะสามารถแซงคิว pod ที่มี priority น้อยกว่า ในการ scheduling ได้&lt;/p&gt;

&lt;p&gt;ในการ schedule pod ไป run นั้น &lt;code&gt;Kube-Scheduler&lt;/code&gt; จะทำ 2 steps คือ &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Filtering&lt;/strong&gt;: ทำการ filter ให้เหลือแค่ node คุณสมบัติที่เหมาะสมกับ pod&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoring&lt;/strong&gt;: ทำให้คิดคะแนน (ranking) ด้วย algorithm ต่างๆ เพือหา node ที่เหมาะสมที่สุดที่จะนำ pod ไป run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fezi79dffyge2seql2cis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fezi79dffyge2seql2cis.png" alt="scheduling-algorithm" width="383" height="782"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ซึ่ง Schedule Policies ที่เลือกใช้ได้มี 2 แบบ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Predicates และ Priorities&lt;/strong&gt;: predicates เป็นการ filtering โดยพิจารณาสถานะและพฤติกรรม ส่วน priority เป็นการใช้ function มาคำนวณคะแนน เพื่อหา node ที่เหมาะสมที่สุด&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extension points และ Plugins&lt;/strong&gt;: เป็นการใช้ profile ที่เกิดจากการนำ state หรือเป็นมาการเรียนว่า "Extension points" มารวมกัน เช่น &lt;code&gt;QueueSort&lt;/code&gt;, &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;Score&lt;/code&gt;, &lt;code&gt;Bind&lt;/code&gt;, &lt;code&gt;Reserve&lt;/code&gt;, &lt;code&gt;Permit&lt;/code&gt;, เป็นต้นมารวมกัน เพื่อหา node ที่เหมาะสมที่สุด&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;เราสามารถชี้นำการตัดสินใจของ &lt;code&gt;Kube-Scheduler&lt;/code&gt; ได้ด้วยการระบุคุณสมบัติพิเศษ (&lt;code&gt;Labels&lt;/code&gt;) หรือข้ออจำกัด (&lt;code&gt;taints&lt;/code&gt;) ให้กับ nodes หรือ pods ที่มีอยู่ก่อน และ ระบุความสามารถที่สอดคล้องกันให้กับ pods ที่เราจะ schedule โดยตัวอย่างการชี้น ของ default scheduler มีดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;affinity&lt;/code&gt;: ระบุว่า pods นี้ต้อง run ใน node ที pods ที่มี labels นี้ run อยู่ก่อนแล้ว&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;antiaffinity&lt;/code&gt;: ระบุว่า pods นี้ run ใน node ทีไม่มี pods ที่มี labels นี้ run อยู่&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;taint&lt;/code&gt;: เป็นข้อจำกัดของ nodes ที่เราระบุเข้าไป&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toleration&lt;/code&gt;: เป็น property ของ pod ที่ทนต่อ taints ที่ node มีได้ (ทนได้ ก็คือ schedule pods ไป run ได้)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;นอกจากนี้เรายังสามารถเขียน program ของเราเองเพื่อมา run เป็น custom scheduler ใน kubernetes cluster ก็&lt;/p&gt;

&lt;p&gt;รายละเอียดเพิ่มเติมดูได้ในหัวข้อด้านล่างเลย 😁&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Predicates and Priorities
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Predicates&lt;/strong&gt; (Deplicated in v1.18)&lt;/p&gt;

&lt;p&gt;เป็นชุดของ Filter ที่ใช้ในการตรวจสอบความพร้อมของ nodes โดยถ้า nodes ไหนมีคุณสมบัติไม่ผ่านก็จะถูก filter ทิ้งไป โดย list ของ predicates มีดังนี้ &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CheckNodeConditionPred&lt;/li&gt;
&lt;li&gt;CheckNodeUnschedulablePred&lt;/li&gt;
&lt;li&gt;GeneralPred&lt;/li&gt;
&lt;li&gt;HostNamePred&lt;/li&gt;
&lt;li&gt;PodFitsHostPortsPred&lt;/li&gt;
&lt;li&gt;MatchNodeSelectorPred&lt;/li&gt;
&lt;li&gt;PodFitsResourcesPred&lt;/li&gt;
&lt;li&gt;NoDiskConflictPred&lt;/li&gt;
&lt;li&gt;PodToleratesNodeTaintsPred&lt;/li&gt;
&lt;li&gt;PodToleratesNodeNoExecuteTaintsPred&lt;/li&gt;
&lt;li&gt;CheckNodeLabelPresencePred&lt;/li&gt;
&lt;li&gt;CheckServiceAffinityPred&lt;/li&gt;
&lt;li&gt;MaxEBSVolumeCountPred&lt;/li&gt;
&lt;li&gt;MaxGCEPDVolumeCountPred&lt;/li&gt;
&lt;li&gt;MaxCSIVolumeCountPred&lt;/li&gt;
&lt;li&gt;MaxAzureDiskVolumeCountPred&lt;/li&gt;
&lt;li&gt;MaxCinderVolumeCountPred&lt;/li&gt;
&lt;li&gt;CheckVolumeBindingPred&lt;/li&gt;
&lt;li&gt;NoVolumeZoneConflictPred&lt;/li&gt;
&lt;li&gt;CheckNodeMemoryPressurePred&lt;/li&gt;
&lt;li&gt;CheckNodePIDPressurePred&lt;/li&gt;
&lt;li&gt;CheckNodeDiskPressurePred&lt;/li&gt;
&lt;li&gt;EvenPodsSpreadPred&lt;/li&gt;
&lt;li&gt;MatchInterPodAffinityPred&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://kubernetes.io/docs/reference/scheduling/policies/#predicates" rel="noopener noreferrer"&gt;เพิ่มเติม&lt;/a&gt;&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Priorities&lt;/strong&gt; (Deplicated in v1.18)&lt;/p&gt;

&lt;p&gt;เป็นการจัดลำดับความสำคัญของ resources ถ้า Pod และ Node Affinity ไม่ได้ configure ไว้ว่าเป็น &lt;code&gt;SelectorSpreadPriority&lt;/code&gt; (จัดลำดับตามจำนวน pods ที่ run อยู่ใน node) &lt;code&gt;Kube-Scheduler&lt;/code&gt; จะเลือก node ที่มีจำนวน pod ถูก schedule ไปน้อยที่สุด list ของ priorities มีดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EqualPriority&lt;/li&gt;
&lt;li&gt;MostRequestedPriority&lt;/li&gt;
&lt;li&gt;RequestedToCapacityRatioPriority&lt;/li&gt;
&lt;li&gt;SelectorSpreadPriority&lt;/li&gt;
&lt;li&gt;ServiceSpreadingPriority&lt;/li&gt;
&lt;li&gt;InterPodAffinityPriority&lt;/li&gt;
&lt;li&gt;LeastRequestedPriority&lt;/li&gt;
&lt;li&gt;BalancedResourceAllocation&lt;/li&gt;
&lt;li&gt;NodePreferAvoidPodsPriority&lt;/li&gt;
&lt;li&gt;NodeAffinityPriority&lt;/li&gt;
&lt;li&gt;TaintTolerationPriority&lt;/li&gt;
&lt;li&gt;ImageLocalityPriority&lt;/li&gt;
&lt;li&gt;ResourceLimitsPriority&lt;/li&gt;
&lt;li&gt;EvenPodsSpreadPriority&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://kubernetes.io/docs/reference/scheduling/policies/#priorities" rel="noopener noreferrer"&gt;เพิ่มเติม&lt;/a&gt;&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Scheduling Policies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;เราสามารถ custom Predicates และ Priorities เพื่อสร้างเป็น custom scheduler ได้ โดยสร้าง file ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;{&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Policy"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;predicates"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PodFitsHostPorts"&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PodFitsResources"&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NoDiskConflict"&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NoVolumeZoneConflict"&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MatchNodeSelector"&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HostName"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;],&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priorities"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LeastRequestedPriority"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BalancedResourceAllocation"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServiceSpreadingPriority"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EqualPriority"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;],&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hardPodAffinitySymmetricWeight"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alwaysCheckAllPredicates"&lt;/span&gt; &lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;false&lt;/span&gt;
&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;และ run scheduler ขึ้นมาอีกตัวโดยแก้ option &lt;code&gt;--policy-config-file=&amp;lt;file&amp;gt;&lt;/code&gt;, &lt;code&gt;--use-legacy-policy-config=true&lt;/code&gt; และ &lt;code&gt;--scheduler-name=&amp;lt;name&amp;gt;&lt;/code&gt; จากนั้น run pod โดย ระบุ scheduler name ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;annotation-second-scheduler&lt;/span&gt;
&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;multischeduler-example&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;schedulerName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod-with-second-annotation-container&lt;/span&gt;
    &lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s.gcr.io/pause:2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Extension Points and Plugins
&lt;/h1&gt;

&lt;p&gt;เป็น feature ใหม่ใน v1.18 เลย แต่ยังเป็น beta อยู่ &lt;/p&gt;

&lt;p&gt;เป็น scheduling profile ที่เกิดจากการรวม Extension points เข้าด้วยกัน เราสามารถระบุ profile ด้วยการ เพิ่ม option ให้ &lt;code&gt;kube-scheduler&lt;/code&gt; ดังนี้ &lt;code&gt;kube-scheduler --config &amp;lt;filename&amp;gt;&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;สามารถระบุหลาย profile และให้ pods เรียกใช้โดยการบ้างอิง scheduler name ใน pod specification&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extension points&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;QueueSort&lt;/code&gt;: เป็นการ sort pods ที่อยู่ใน scheduling queue&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PreFilter&lt;/code&gt;: เป็นการ pre-process หรือ check Pod และ cluster ก่อนทำการ Filter&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Filtering&lt;/code&gt;: เหมือน predicates เป็นการ filter เอา nodes ที่มีคุณสมบัติไม่ผ่านออกไป&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PreScore&lt;/code&gt;: เป็นการทำ pre-scoring &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Score&lt;/code&gt;: ให้คะแนนแต่ละ node และ เลือก node ที่มีคะแนนมากที่สุด&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Reserve&lt;/code&gt;: เป็น stage ที่ notify plugin ว่า resource กำลังถูก reserve ให้กับ pod&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Permit&lt;/code&gt;: สามารถ ป้องกัน (prevent) หรือ น่วงเวลา (delay) การ bind pod ไปยัง node&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PreBind&lt;/code&gt;: ทำงานต่างๆ ก่อนที่จะ bind pod&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Bind&lt;/code&gt;: ทำการ bind pod ไปยัง node ที่ reserve resource ไว้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PostBind&lt;/code&gt;: เป็น stage หลังจาก bind pod ไปยัง node เรียบร้อย&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UnReserve&lt;/code&gt;: เป็น stage ที่ Pod ถูก reject หลังจากทำการ reserve resource ไม่สำเร็จ โดย pod จะกลับไปอยู่ stage &lt;code&gt;Permit&lt;/code&gt; อีกครั้ง&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Scheduling plugins&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Enable โดย default

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DefaultTopologySpread&lt;/code&gt; (PreScore, Score): กระจาย pods ไปยัง node ตาม Services, ReplicaSets และ StatefulSets ของมัน&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ImageLocality&lt;/code&gt; (Score): กระจาย posd ไม่ยัง node ที่มี image ของ pod นั้นอยู่แล้ว&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TaintToleration&lt;/code&gt; (Filter, Prescore, Score): กระจายตามหลัก Taint and Toleration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeName&lt;/code&gt; (Filter): แจก Pod ไปยัง Node ทีมี name node ตรงกับที่ระบุไว้ใน pod specification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodePorts&lt;/code&gt; (PreFilter, Filter): แจก Pod ไปยัง Node ที่มี port ที่ต้องการใช้ว่างอยู้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodePreferAvoidPods&lt;/code&gt; (Score): ให้คะแนน node ตาม annotation &lt;code&gt;scheduler.alpha.kubernetes.io/preferAvoidPods&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeAffinity&lt;/code&gt; (Filter, Score): แจก pod ตาม &lt;code&gt;nodeSelector&lt;/code&gt; and &lt;code&gt;nodeAffinity&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PodTopologySpread&lt;/code&gt; (Filter, PreScore, Score): แจก pod ตาม Pod topology&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeUnschedulable&lt;/code&gt; (Filter): Filter node ที่มี &lt;code&gt;.spec.unschedulable=true&lt;/code&gt; ออกไป&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeResourcesFit&lt;/code&gt; (PreFilter, Filter): Filter node ที่มี resource ไม่พอตามที่ pod request ออกไป&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeResourcesBallancedAllocation&lt;/code&gt; (Score): เลือก node ที่ถ้า schedule pod ไป จะทำให้ resource ในแต่ละ node ใน cluster balance ขึ้น&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeResourcesLeastAllocated&lt;/code&gt; (Score): เลือก node ที่มี resource เหลือเยอะที่สุด&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VolumeBinding&lt;/code&gt; (Filter): เลือก node ที่มี volume ที่  pod ต้องการ bind อยู่&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VolumeRestrictions&lt;/code&gt; (Filter): เลือก node ที่มี volume ที่ pod ต้องการ bind อยู่ และ pod มีสิทธิ์ใช้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VolumeZone&lt;/code&gt; (Filter): เลือก node ที่มี volume ที่อยู่ใน zone ที่ pod ต้องการ&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeVolumeLimits&lt;/code&gt; (Filter): เลือก node ที่ satisfy CSI volume limits &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EBSLimits&lt;/code&gt; (Filter): เลือก node ที่ satisfy AWS EBS volume limits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GCEPDLimits&lt;/code&gt; (Filter): เลือก node ที่ satisfy GCP-PD volume limits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AzureDiskLimits&lt;/code&gt; (Filter): เลือก node ที่ satisfy Azure disk volume limits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;InterPodAffinity&lt;/code&gt; (PreFilter, Filter, PreScore, Score): แจก pod ตาม inter-Pod affinity and anti-affinity&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PrioritySort&lt;/code&gt; (QueueSort): เลือก node ตาม priority based sorting แบบ default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DefaultBinder&lt;/code&gt; (Bind): เลือก node ตาม binding mechanism แบบ default&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;ต้อง manual enable เองผ่าน API

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NodeResourcesMostAllocated&lt;/code&gt; (Score): เลือก node ที่มี resource เหลือน้อยที่สุด&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RequestedToCapacityRatio&lt;/code&gt; (Score): เลือก node ตาม function ของ resource ที่ allocate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeResourceLimits&lt;/code&gt; (PreScore, Score): เลือก node ที่ มี resource พอ ตามที่ Pod request&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CinderVolume&lt;/code&gt; (Filter): เลือก node ที่ satisfy OpenStack Cinder volume limits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NodeLabel&lt;/code&gt; (Filter, Score): filter และ ให้คะแนน node ตาม labels ที่ configure ไว้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ServiceAffinity&lt;/code&gt; (PreFilter, Filter, Score): กระจาย Pods ที่อยู่ใน service เดียวกันไปยัง แต่ละ node ไม่ซ้ำกัน&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Pod Specification
&lt;/h1&gt;

&lt;p&gt;ในการ schedule pod ไป run ใน node ที่เหมาะสมนอกจากความพร้อมของ node แล้ว อีกปัจจัยที่สำคัญคือ Pod Specification โดย parameter ที่มีผลต่อการ schedule เช่น&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nodeName&lt;/code&gt;: ใช้ระบุชื่อ node ที่ต้องการให้ scheduler นำ Pod ไป run&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nodeSelector&lt;/code&gt;: ใช้ระบุ label ของ nodes ที่เหมาะสมกับ pod ตัวนั้นๆ&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;affinity&lt;/code&gt;: มีทั้ง affinity ซึ่งบอกว่า pod นี้ต้องอยู่ร่วมกับ pod ใด และ anti-affinity คือไม่อยู่ร่วมกับ pod ใด&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schedulerName&lt;/code&gt;: ระบุชื่อของ scheduler ที่ใช้ในการ schedule pod ถ้าไม่ระบุใช้ default scheduler&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tolerations&lt;/code&gt;: บอกว่า Pod นี้ทนต่อข้อจำกัด (taints) อะไรของ node ได้บ้าง&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Specifiying the nodeName
&lt;/h1&gt;

&lt;p&gt;เป็นการระบุที่ง่ายที่สุด &lt;u&gt;ที่ไม่แนะนำ&lt;/u&gt; ซึ่งมาด้วยข้อจำกัดหลายอย่าง เช่น &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ถ้า node name ที่เราระบุไปไม่มีอยู่จริง pod ก็จะไม่ถูก run&lt;/li&gt;
&lt;li&gt;ถ้า node name ที่เราระบุไว้มี resource ไม่เพียงพอ pod ก็จะไม่ถูก run เช่นกัน&lt;/li&gt;
&lt;li&gt;ใน cloud environment เราไม่สามารถคาดเดา node name ได้ ทำให้เราต้องแก้ไข configure ของเราทุกครั้งที่ย้าย cluster หรือ node ที่เราระบุตายไป&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ตัวอย่างของ pod specification ที่ใช้ nodeName&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;$ kubectl get nodes&lt;/span&gt;
&lt;span class="s"&gt;NAME                  STATUS   ROLES    AGE   VERSION&lt;/span&gt;
&lt;span class="s"&gt;kube-0001.novalocal   Ready    master   88d   v1.17.1&lt;/span&gt;
&lt;span class="s"&gt;kube-0002.novalocal   Ready    &amp;lt;none&amp;gt;   88d   v1.17.1&lt;/span&gt;
&lt;span class="s"&gt;kube-0003.novalocal   Ready    &amp;lt;none&amp;gt;   88d   v1.17.1&lt;/span&gt;
&lt;span class="s"&gt;$ cat &amp;gt; nodename.yaml &amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;nodeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kube-0003.novalocal&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;span class="s"&gt;$ kubectl get pods -o wide&lt;/span&gt; 
&lt;span class="s"&gt;NAME    READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES&lt;/span&gt;
&lt;span class="s"&gt;nginx   1/1     Running   0          3m    192.168.32.60   kube-0003.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frs0mglyi9y1so1y0at2z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frs0mglyi9y1so1y0at2z.png" alt="Alt Text" width="513" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Specifiying the nodeSelector
&lt;/h1&gt;

&lt;p&gt;เป็นการระบุที่ง่ายที่สุด &lt;u&gt;ที่แนะนำ&lt;/u&gt; เนื่องจากเราสามารถสร้าง labels เดียวกันที่หลาย node ทำให้ถ้า node กใด node หนึ่งตายไป หรือไม่พร้อมด้วยสาเหตุใดก็ตาม ก็ยังมี node อื่นให้ schedule ไป &lt;/p&gt;

&lt;p&gt;โดยเราต้องทำการ label node ก่อน ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl label nodes kube-0003.novalocal &lt;span class="nv"&gt;disktype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ssd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จากนั้น create pod ด้วย specification ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;$ kubectl get nodes --show-labels&lt;/span&gt;
&lt;span class="s"&gt;NAME                  STATUS   ROLES    AGE   VERSION   LABELS&lt;/span&gt;
&lt;span class="s"&gt;kube-0001.novalocal   Ready    master   88d   v1.17.1   (...),node-role.kubernetes.io/master=&lt;/span&gt;
&lt;span class="s"&gt;kube-0002.novalocal   Ready    &amp;lt;none&amp;gt;   88d   v1.17.1   (...)&lt;/span&gt;
&lt;span class="s"&gt;kube-0003.novalocal   Ready    &amp;lt;none&amp;gt;   88d   v1.17.1   (...)&lt;/span&gt;
&lt;span class="s"&gt;$ kubectl label nodes kube-0003.novalocal disktype=ssd&lt;/span&gt;
&lt;span class="s"&gt;node/kube-0003.novalocal labeled&lt;/span&gt;
&lt;span class="s"&gt;$ kubectl get nodes --show-labels&lt;/span&gt;
&lt;span class="s"&gt;NAME                  STATUS   ROLES    AGE   VERSION   LABELS&lt;/span&gt;
&lt;span class="s"&gt;kube-0001.novalocal   Ready    master   88d   v1.17.1   (...),node-role.kubernetes.io/master=&lt;/span&gt;
&lt;span class="s"&gt;kube-0002.novalocal   Ready    &amp;lt;none&amp;gt;   88d   v1.17.1   (...)&lt;/span&gt;
&lt;span class="s"&gt;kube-0003.novalocal   Ready    &amp;lt;none&amp;gt;   88d   v1.17.1   (...),disktype=ssd,(...)&lt;/span&gt;
&lt;span class="s"&gt;$ cat &amp;gt; nodeselector.yaml &amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
  &lt;span class="na"&gt;nodeSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;disktype&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ssd&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;span class="s"&gt;$ kubectl create -f nodeselector&lt;/span&gt;
&lt;span class="s"&gt;pod/nginx created&lt;/span&gt;
&lt;span class="s"&gt;$ kubectl get pods -o wide&lt;/span&gt; 
&lt;span class="s"&gt;NAME    READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES&lt;/span&gt;
&lt;span class="s"&gt;nginx   1/1     Running   0          19s   192.168.32.61   kube-0003.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F74o3n368gq1jrlp4y7t2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F74o3n368gq1jrlp4y7t2.png" alt="Alt Text" width="513" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;โดย pod จะถูก schedule ไปยัง node ที่มี ทุก label ที่เรากำหนดไว้ใน pod specification ถ้าหาไม่มี node ไหน ตรงกับ specification pod นั้นจะมีสถานะเป็น &lt;strong&gt;Pending&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;เราสามารถใช้ nodeAffinity แทน nodeSelector ก็ได้&lt;/p&gt;

&lt;p&gt;Node ใน Kubernetes cluster โดยปกติจะมี Built-in Labels ที่เรานำมาใช้ได้ โดยที่เราไม่ต้องกำหนดเอง ได้แก่&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubernetes.io/hostname&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;failure-domain.beta.kubernetes.io/zone&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;failure-domain.beta.kubernetes.io/region&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;topology.kubernetes.io/zone&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;topology.kubernetes.io/region&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;beta.kubernetes.io/instance-type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;node.kubernetes.io/instance-type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubernetes.io/os&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubernetes.io/arch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Pod Affinity Rules
&lt;/h1&gt;

&lt;p&gt;Pod Affinity เป็นการ schedule pod ตาม pod ที่มีอยู่ก่อนหน้า เนื่องจาก scheduler ต้องเข้าไปตรวจสอบทุก nodes ดังนั้น ใน cluster ใหญ่ๆ ที่มี node หลักร้อย nodes การใช้วิธีนี้จะมีผลต่อ performance ของ cluster&lt;/p&gt;

&lt;p&gt;โดย Affinity Rules มี 2 แบบคือ&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Affinity&lt;/code&gt; คือ pods เหล่านี้ต้องอยู่ใน location เดียวกัน (co-location) มักจะเป็น pod ที่ share data กันเป็นจำนวนมาก&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Anti-affinity&lt;/code&gt; คือ pods เหล่านี้ต้องอยู่คนละ location กัน เหมาะกับ pods ที่ต้องการ fault tolerance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Operations ที่สามารถใช้ในการระบุ labels เช่น&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;In&lt;/code&gt;: เลือก pod ที่มี labels ที่มีค่าตามที่ระบุไว้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NotIn&lt;/code&gt;: เลือก pod ที่ไม่มี labels ที่มีค่าตามที่ระบุไว้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Exists&lt;/code&gt;: เลือก pod ที่มี labels ที่ระบุ&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DoesNotExist&lt;/code&gt;: เลือก pod ที่ไม่มี labels ที่ระบุ &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;นอกจากนี้ ยังมีการระบุรายละเอียดการ schedule pods ลงไปอีก 2 parameters คือ&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;: ต้องมี labels ตาม conditions ที่กำหนดเท่านั้น ถ้าไม่มี ไม่ schedule (hard affinity)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;: ถ้าไม่มี labels ตาม conditions ที่กำหนด ก็ไม่เป็นไร แต่ถ้ามีก็จะ schedule ตาม conditions ที่กำหนดนั้น (soft affinity)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;มากไปกว่านั้น เรายังสามารถใช้ร่วมกับ &lt;code&gt;topologyKey&lt;/code&gt; ได้อีกด้วย ดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ถ้าระบุ &lt;code&gt;Affinity&lt;/code&gt; พร้อมกับ &lt;code&gt;Anti-affinity&lt;/code&gt; ที่เป็น &lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt; ต้องระบุ &lt;code&gt;topologyKey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ถ้าระบุ &lt;code&gt;Anti-affinity&lt;/code&gt; ที่เป็น &lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt; เท่านั้น &lt;code&gt;topologyKey&lt;/code&gt; ต้องเป็น &lt;code&gt;kubernetes.io/hostname&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ถ้าระบุ &lt;code&gt;Anti-affinity&lt;/code&gt; ที่เป็น &lt;code&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/code&gt; เท่านั้น &lt;code&gt;topologyKey&lt;/code&gt; ต้องระบุ &lt;code&gt;topologyKey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;นอกเหนือจากข้างบน &lt;code&gt;topologyKey&lt;/code&gt; เป็นค่าอะไรก็ได้ ใน built-in Labels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;u&gt;ตัวอย่าง&lt;/u&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;หากเราต้องการ deploy ของ Redis cluster ที่มี 3 replicas ซึ่งแต่ละ replica อยู่คนละ node กัน เพื่อเพิ่ม fault-tolerance deployment จะเป็นดังนี้&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-cache&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;affinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;podAntiAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
                &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
                &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store&lt;/span&gt;
            &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes.io/hostname"&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:3.2-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;แต่ละ Pods ใน Deployment มี Labels เป็น &lt;code&gt;app: store&lt;/code&gt; และใน &lt;code&gt;podAntiAffinity&lt;/code&gt; ว่าไม่ให้อยู่ร่วมกับ pods ที่มี label &lt;code&gt;app: store&lt;/code&gt; นั้นหมายความว่า ห้าม pod Redis นี้อยู่ร่วมกัน&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# เนื่องจาก มี 3 nodes cluster ดังนั้นต้องทำการ untaint master ก่อน&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl taint nodes &lt;span class="nt"&gt;--all&lt;/span&gt; node-role.kubernetes.io/master-
node/kube-0001.novalocal untainted
taint &lt;span class="s2"&gt;"node-role.kubernetes.io/master"&lt;/span&gt; not found
taint &lt;span class="s2"&gt;"node-role.kubernetes.io/master"&lt;/span&gt; not found

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; redis.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; redis.yaml
deployment.apps/redis-cache created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
NAME                           READY   STATUS    RESTARTS   AGE   IP               NODE                  NOMINATED NODE   READINESS GATES
redis-cache-6bc7d5b59d-ngnsf   1/1     Running   0          33s   192.168.93.152   kube-0001.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
redis-cache-6bc7d5b59d-swzjm   1/1     Running   0          33s   192.168.32.63    kube-0003.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
redis-cache-6bc7d5b59d-vzs7h   1/1     Running   0          33s   192.168.52.66    kube-0002.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgfbiw4e9v040xxlh49da.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgfbiw4e9v040xxlh49da.png" alt="Alt Text" width="513" height="187"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;หากเราต้องการ deploy frontend ของ web ecommerce ที frontend และ redis ต้องอยู่ node เดียวกัน โดยแต่ละคู่ต้องอยูู่คนละ node กัน เพื่อเพิ่ม fault-tolerance deployment จะเป็นดังนี้&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-server&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-store&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-store&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;affinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;podAntiAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
                &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
                &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;web-store&lt;/span&gt;
            &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes.io/hostname"&lt;/span&gt;
        &lt;span class="na"&gt;podAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
                &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
                &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store&lt;/span&gt;
            &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes.io/hostname"&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-app&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.16-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;เราให้ pod ของ frontend มี label เป็น &lt;code&gt;app: web-store&lt;/code&gt; โดย&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ระบุ &lt;code&gt;podAntiAffinity&lt;/code&gt; ด้วย label &lt;code&gt;app:  web-store&lt;/code&gt; ซึ่งหมายความว่า ห้าม frontend อยู่ร่วม node กัน&lt;/li&gt;
&lt;li&gt;ระบุ &lt;code&gt;podAffinity&lt;/code&gt; ด้วย label &lt;code&gt;app: store&lt;/code&gt; ซึ่งหมายความว่า frontend ต้องอยู่ร่วมใน node เดียวกับ backend
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; web-store.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; web-store.yaml 
deployment.apps/web-server created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
NAME                           READY   STATUS    RESTARTS   AGE    IP               NODE                  NOMINATED NODE   READINESS GATES
redis-cache-6bc7d5b59d-brsp5   1/1     Running   0          118s   192.168.52.67    kube-0002.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
redis-cache-6bc7d5b59d-crgrb   1/1     Running   0          118s   192.168.93.153   kube-0001.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
redis-cache-6bc7d5b59d-kp4kq   1/1     Running   0          118s   192.168.32.1     kube-0003.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
web-server-75b987f474-4qggk    1/1     Running   0          10s    192.168.32.2     kube-0003.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
web-server-75b987f474-jswff    1/1     Running   0          10s    192.168.52.68    kube-0002.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
web-server-75b987f474-kkddh    1/1     Running   0          10s    192.168.93.154   kube-0001.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5v5ut5rznhqipytq8wml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5v5ut5rznhqipytq8wml.png" alt="Alt Text" width="513" height="187"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Node Affinity Rules
&lt;/h1&gt;

&lt;p&gt;เหมือนกับ &lt;code&gt;nodeSelector&lt;/code&gt; แต่เพิ่มเติมความสามารถของ affinity เช่น operators (&lt;code&gt;In&lt;/code&gt;, &lt;code&gt;NotIn&lt;/code&gt;, &lt;code&gt;Exists&lt;/code&gt;, &lt;code&gt;DoesNotExist&lt;/code&gt;) และ &lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;/&lt;code&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/code&gt; เข้ามา &lt;/p&gt;

&lt;p&gt;พร้อมกันนั้นก็มีแผนที่จะทำ &lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt; เพิ่มด้วย ซึ่ง ถ้ามีการเปลี่ยน labels ของ nodes จะมีผลกับ pod ที่ run อยู่ด้วย&lt;/p&gt;

&lt;p&gt;&lt;u&gt;ตัวอย่าง&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;with-node-affinity&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;affinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nodeAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;nodeSelectorTerms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/e2e-az-name&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
            &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;e2e-az1&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;e2e-az2&lt;/span&gt;
      &lt;span class="na"&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="na"&gt;preference&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;another-node-label-key&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
            &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;another-node-label-value&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;with-node-affinity&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s.gcr.io/pause:2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถ้ามี node ที่มี label &lt;code&gt;another-node-label-key: another-node-label-value&lt;/code&gt; pod นี้จะถูก schedule ไปยัง node นั้น แต่ถ้าไม่มีจะไปดู node ที่มี labels &lt;code&gt;kubernetes.io/e2e-az-name: e2e-az1&lt;/code&gt; หรือ &lt;code&gt;kubernetes.io/e2e-az-name: e2e-az2&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl label nodes kube-0002.novalocal kubernetes.io/e2e-az-name&lt;span class="o"&gt;=&lt;/span&gt;e2e-az2
node/kube-0002.novalocal labeled
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get nodes &lt;span class="nt"&gt;--show-labels&lt;/span&gt;
NAME                  STATUS   ROLES    AGE   VERSION   LABELS
kube-0001.novalocal   Ready    master   88d   v1.17.1   &lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
kube-0002.novalocal   Ready    &amp;lt;none&amp;gt;   88d   v1.17.1   &lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;,kubernetes.io/e2e-az-name&lt;span class="o"&gt;=&lt;/span&gt;e2e-az2,&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
kube-0003.novalocal   Ready    &amp;lt;none&amp;gt;   88d   v1.17.1   &lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node-affinity.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; node-affinity.yaml 
pod/with-node-affinity created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
NAME                 READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES
with-node-affinity   1/1     Running   0          23s   192.168.52.69   kube-0002.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnwjj170s2703h5pembxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnwjj170s2703h5pembxj.png" alt="Alt Text" width="513" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Taints
&lt;/h1&gt;

&lt;p&gt;taints คือความสามารถของ node ในการที่จะไม่ให้ pod ที่ไม่เหมาะสม มา run ที่มันได้ โดย pod ที่ไม่มี toleration ต่อ taints ของ node จะไม่สามารถ schedule pod มา run ที่ node นั้นได้&lt;/p&gt;

&lt;p&gt;taints จะมีลักษณะแบบนี้ &lt;code&gt;key=value:effect&lt;/code&gt; โดย &lt;code&gt;key&lt;/code&gt; กับ &lt;code&gt;value&lt;/code&gt; เป็นค่าใดๆ ก็ได้ที่กำหนดโดย adminitrator และ &lt;code&gt;effect&lt;/code&gt; มีให้เลือก 3 แบบคือ&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NoSchedule&lt;/code&gt;: ห้าม schedule pod มายัง node โดย pod ที่ run อยู่ก่อน assign taints จะไม่ได้รับผลกระทบ&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PreferNoSchedule&lt;/code&gt;: ถ้าหา node ที่เหมาะสมไม่ได้แล้วจริงๆ ก็ schedule pod มายัง node นี้ได้&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NoExecute&lt;/code&gt;: pod ใหม่ก็ห้าม schedule มา pod เก่าก็ต้องอพยบไปที่อื่น โดยถ้า pod มี &lt;code&gt;tolerationSeconds&lt;/code&gt; กำหนดอยู่มันจะรอจนกว่าจะครบ &lt;code&gt;tolerationSeconds&lt;/code&gt; ถึงจะ อบยพออกไป&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ถ้า node มีหลาย taints pods ที่มี tolerations ทุก taints ถึงจะมา run ใน node นั้นๆ ได้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# วิธีการเพิ่ม taints ให้กับ node&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl taint nodes node1 &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;value:NoSchedule

&lt;span class="c"&gt;# วิธีการลบ taints ออกจาก node&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl taint nodes node1 key:NoSchedule-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Tolerations
&lt;/h1&gt;

&lt;p&gt;เป็นการใส่ความสามารถให้ pod ทนต่อ taints ของ node ได้ โดย &lt;strong&gt;ต้อง match ทั้ง key, value และ effect&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ซึ่ง key และ value เราสามารถกำหนด tolerations ได้ด้วย 2 operators คือ&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Exists&lt;/code&gt;: ถ้า tolerations ที่เรากำหนด มี key ตรงกับ taints ของ node ก็ถือว่า match แล้ว&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key"&lt;/span&gt;
  &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exists"&lt;/span&gt;
  &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NoSchedule"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Equal&lt;/code&gt;: ต้องตรงทั้ง key และ value ถึงจะถือว่า match (default operator)&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key"&lt;/span&gt;
  &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Equal"&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value"&lt;/span&gt;
  &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NoSchedule"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ถ้าไม่ระบุ effect ถือว่า ทน (toleration) ได้ทุก effect ดังนั้น เราสามารถระบุ toleration level 999 ที่ทนทานได้แทนทุก taint ดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ระบุแค่ &lt;code&gt;operator: Exists&lt;/code&gt; -&amp;gt; &lt;strong&gt;ทนได้ทุก taints&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exists"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ระบุแค่ &lt;code&gt;operator: Exists&lt;/code&gt; และ &lt;code&gt;key: "key"&lt;/code&gt; -&amp;gt; &lt;strong&gt;ทนได้ทุก taints ที่มี key ตรงกัน&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key"&lt;/span&gt;
  &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exists"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ถ้า node มีหลาย taints pods ที่จะ schedule ได้ ต้องมี tolerations ครบทุก taints ถ้าไม่ครบ pods จะถูกปฏิบัติด้วย effect ของ taints ที่มีความแรงที่สุดที่ไม่ match&lt;/p&gt;

&lt;p&gt;Use cases ของ taints และ toleration เช่น&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dedicated Nodes ที่เราจะ reserve ไว้ให้แค่บางกลุ่มของ users ใช้งาน&lt;/li&gt;
&lt;li&gt;Nodes with Special Hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Custom Scheduler
&lt;/h1&gt;

&lt;p&gt;ถ้า affinity, taints และ policies ยังไม่พอดับความต้องการของเรา เราสามารถสร้าง scheduler ของเราเองได้ โดยสามารถเข้าไปดูได้ที่ &lt;a href="https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เพื่อใช้ scheduler ใหม่ของเรา เราต้องระบุ scheduler name เข้าไปใน pod specification ด้วย ถ้าไม่ระบุจะใช้ default scheduler แต่ถ้าเราระบุ scheduler ผิด pod ของเราจะอยู่ในสถานะ Pending&lt;/p&gt;

&lt;p&gt;เราสามารถดูข้อมูลของ scheduler และ information อื่นๆ ได้ด้วย &lt;code&gt;kubectl get events&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get events
LAST SEEN   TYPE     REASON      OBJECT      MESSAGE
41s         Normal   Killing     pod/nginx   Stopping container nginx
2s          Normal   Scheduled   pod/nginx   Successfully assigned default/nginx to kube-0003.novalocal
1s          Normal   Pulled      pod/nginx   Container image &lt;span class="s2"&gt;"nginx"&lt;/span&gt; already present on machine
1s          Normal   Created     pod/nginx   Created container nginx
1s          Normal   Started     pod/nginx   Started container nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>Django เลือก template มา render อย่างไร และทำไม Best Practice ถึงถูก</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Tue, 07 Apr 2020 15:30:44 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/django-template-render-best-practice-4d33</link>
      <guid>https://dev.to/peepeepopapapeepeepo/django-template-render-best-practice-4d33</guid>
      <description>&lt;p&gt;จากการลองเล่น framework ตัวหนึ่งชื่อ Django (จังโก้) เพื่อนำมาใช้ในการทำ Web GUI สำหรับ project หนึ่ง&lt;/p&gt;

&lt;p&gt;พอลองเล่นมาถึง &lt;a href="https://docs.djangoproject.com/en/3.0/intro/tutorial03/" rel="noopener noreferrer"&gt;Writing your first Django app, part 3&lt;/a&gt; เกิดงงเรื่อง directory structure ของ template ที่ต้องเป็น &lt;code&gt;project_name/app_name/templates/app_name/&lt;/code&gt; และการสั่ง render template ต้องอ้างอิงชื่อเป็น &lt;code&gt;app_name/home.html&lt;/code&gt; ซึ่งเค้าบอกว่าเป็น Best Practice &lt;/p&gt;

&lt;p&gt;ที่ผมงงคือ... ทำไม directory structure ต้องมี &lt;code&gt;app_name&lt;/code&gt; ซ้ำ 2 ครั้งเลยล่ะ ครั้งเดียวมันน่าจะรู้แล้วไหม และ ตอนเรียก template มา render ก็ต้องใส่ &lt;code&gt;app_name&lt;/code&gt; ข้างหน้าชื่อ template อีก ซึ่งเราทำที่ app นี้อยู่แล้วทำไม่ยังต้องระบุอีกล่ะ&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;OK! ถ้ามันเป็น Best Practice ความไม่ make sense ต้องมีคำอธิบาย&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;มาดูกัน ผมเจออะไร (ต้องบอกก่อนว่าผมเพิ่งหัดใช้นะครับ เรื่องนี้หลายคนอาจรู้อยู่แล้ว 😁)&lt;/p&gt;

&lt;p&gt;ใน &lt;code&gt;settings.py&lt;/code&gt; ของ Django ที่เป็น "DjangoTemplate" จะเป็นแบบนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BACKEND&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.template.backends.django.DjangoTemplates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DIRS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APP_DIRS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OPTIONS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;context_processors&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.template.context_processors.debug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.template.context_processors.request&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth.context_processors.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages.context_processors.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เราจะสนใจ 2 parameters นี้ก่อนคือ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;DIRS&lt;/code&gt; คือ path ที่เราต้องการให้ django ไปค้นหา เป็น list of strings (default = [])&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APP_DIRS&lt;/code&gt; คือ เป็น boolean บอกว่าให้หา template ใน directory ของ app หรือ ไม่ (default = True)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;โดย พฤติกรรมในการหา template ของ "DjangoTemplate" จะเป็นลำดับตามนี้ &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;หาใน &lt;code&gt;DIRS&lt;/code&gt; ก่อน โดยหาตามลำดับของ list ที่เรากำหนด &lt;br&gt;
เช่น &lt;code&gt;DIRS = ['path/to/template1', 'path/to/template2', 'path/to/template3']&lt;/code&gt; จะหาตามลำดับดังนี้&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;/path/to/template1/&lt;br&gt;
/path/to/template2/&lt;br&gt;
/path/to/template3/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;หาใน &lt;code&gt;APP_DIRS&lt;/code&gt; โดยถ้าเราตั้งค่าเป็น &lt;code&gt;True&lt;/code&gt; Django จะไปดูจาก ตัวแปร &lt;code&gt;INSTALLED_APPS&lt;/code&gt; ใน &lt;code&gt;settings.py&lt;/code&gt; ซึ่ง เป็น list of string เช่น &lt;code&gt;INSTALLED_APPS = ['app-a', 'app-b', 'app-c']&lt;/code&gt; Django จะหาตามลำดับของ list นี้ ดังนี้&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;/path/to/project/app-a/templates/&lt;br&gt;
/path/to/project/app-b/templates/&lt;br&gt;
/path/to/project/app-b/templates/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;จากตัวอย่างข้างต้น ลำดับของ directory ที่ Django ใช้หา template คือ&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;/path/to/template1/&lt;br&gt;
/path/to/template2/&lt;br&gt;
/path/to/template3/&lt;br&gt;
/path/to/project/app-a/templates/&lt;br&gt;
/path/to/project/app-b/templates/   👈 เราจะเขียน template ที่นี่&lt;br&gt;
/path/to/project/app-b/templates/&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ด้วย logic การทำงานแบบนี้ ถ้าเราเขียน template ที่ &lt;code&gt;app-b&lt;/code&gt; ชื่อ &lt;code&gt;home.html&lt;/code&gt; แล้วเราระบุ ชื่อ template ในการ render แค่ &lt;code&gt;home.html&lt;/code&gt; ถ้าบังเอิญว่า &lt;code&gt;app-a&lt;/code&gt; ก็มี &lt;code&gt;home.html&lt;/code&gt; อยู่เช่นกัน มันจะไป render &lt;code&gt;home.html&lt;/code&gt; ของ app-a แทน เพราะมันหาเจอก่อน&lt;/p&gt;

&lt;p&gt;ด้วยพฤติกรรมการทำงานแบบนี้ เราจึงต้องทำตาม Best Practice โดยสร้าง sub directory ที่เป็นชื่อ app ใต้ directory templatate ของ app นั้นๆ ด้วย ดังนี้&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;/path/to/template1/&lt;br&gt;
/path/to/template2/&lt;br&gt;
/path/to/template3/&lt;br&gt;
/path/to/project/app-a/templates/app-a/&lt;br&gt;
/path/to/project/app-b/templates/app-b/   👈 เราจะเขียน template ที่นี่&lt;br&gt;
/path/to/project/app-b/templates/app-c/&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;และเวลาที่เราเรียก template มาใช้งานก็จะเรียกด้วยการระบุชื่อ template ดังนี้ &lt;code&gt;app-b/home.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ด้วยวิธีนี้ การ render จะไม่หยิบ template มาผิด app&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>templates</category>
    </item>
    <item>
      <title>LFS258 [9/15]: Ingress</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Mon, 30 Mar 2020 14:56:54 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/lfs258-9-15-ingress-pjh</link>
      <guid>https://dev.to/peepeepopapapeepeepo/lfs258-9-15-ingress-pjh</guid>
      <description>&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
Ingress Overview: เป็น L7 load balancer หลักๆ มี GCE, Nginx, HAProxy และ Traefix เป็นต้น&lt;/li&gt;
&lt;li&gt;
Ingress Controller: เป็น controller ที่ monitor URI &lt;code&gt;/ingresses&lt;/code&gt; ของ API server เพื่อนำมา configure rule &lt;/li&gt;
&lt;li&gt;
Nginx: เป็น Ingress Controller ที่ใช้ Nginx เป็น reverse proxy&lt;/li&gt;
&lt;li&gt;
Google Load Balancer Controller (GLBC): เป็น reverse proxy ของ Google ใช้งานได้บน Goole Cloud ทั้ง GCE และ GKE&lt;/li&gt;
&lt;li&gt;
Ingress API Resources: แสดง YAML file ในการสร้าง Ingress&lt;/li&gt;
&lt;li&gt;
Deploying the Ingress Controller: แสดงการ setup Nginx Ingress&lt;/li&gt;
&lt;li&gt;
Creating an Ingress Rule: สร้าง rule และทำสอบ Nginx Ingress&lt;/li&gt;
&lt;li&gt;
Multiple Rules: สร้าง rule แบบ multi-domain และทำสอบ&lt;/li&gt;
&lt;li&gt;
Intelligent Connected Proxies: คือ service mesh เช่น Istio, Enovy และ Linkerd&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Ingress Overview
&lt;/h1&gt;

&lt;p&gt;ใน article ก่อนหน้านี้ เราได้เรียนรู้เกี่ยวกับ Service ซึ่งใช้ในการ expose application ให้ client ที่อยู่นอก cluster ใช้งานได้ ในบทนี้เราจะเรียนรู้เกี่ยวกับ &lt;strong&gt;Ingress Controllers&lt;/strong&gt; และ &lt;strong&gt;Rules&lt;/strong&gt; ซึ่งทำหน้าที่เดียวกัน แต่ต่างกันที่ประสิทธิภาพ แทนที่จะต้องใช้ load balancer เป็นตัวช่วย Ingress สามารถทำแบบนั้นได้เลย ไม่ว่าจะเป็น route ตาม domain หรือ URI Path&lt;/p&gt;

&lt;p&gt;Ingress Controllers ไม่ได้เป็นส่วนหนึ่งของ &lt;code&gt;kube-controller-manager&lt;/code&gt; binary แต่มันจะมี controller เป็นของตัวเอง ซึ่งก็มีได้หลาย controller โดย controller จะใช้ Ingress Rules ในการจัดการกับ traffic ทั้งขาเข้าและขาออก&lt;/p&gt;

&lt;p&gt;Ingress รองรับ 2 controllers หลัก คือ &lt;strong&gt;GCE&lt;/strong&gt; และ &lt;strong&gt;nginx&lt;/strong&gt; จริงๆ แล้ว tool อื่นที่ทำหน้าที่เป็น reverse proxy เช่น &lt;strong&gt;HAProxy&lt;/strong&gt; และ &lt;strong&gt;Traefix&lt;/strong&gt; ก็ใช้งานได้ โดยมันจะ route traffic มายัง Service ที่ on แบบ &lt;code&gt;ClusterIP&lt;/code&gt; ได้เลยตาม Ingress Rule ที่เรา configure ไว้&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Ingress Controller
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Ingress Controller&lt;/strong&gt; อยู่ใน &lt;code&gt;networking.k8s.io/v1beta1&lt;/code&gt; resource group เป็น daemon ที่ run ใน Pod ซึ่งจะคอย monitor การเปลี่ยนแปลงของ &lt;code&gt;/ingresses&lt;/code&gt; บน &lt;code&gt;kube-apiserver&lt;/code&gt; ถ้าหากพบการเปลี่ยนแปลง มันจะไป configure rules เพื่อ support ให้ โดย traffic ส่วนใหญ่จะเป็น HTTP วิธีนี้จะทำให้สามารถเข้าถึง Services ได้ โดยไม่ต้องสนใจว่า Pods นั้นถูก deploy ไว้ที่ node ไหน&lt;/p&gt;

&lt;p&gt;เราสามารถ deploy &lt;strong&gt;Ingress Controller&lt;/strong&gt; ได้มากกว่า 1 controller ใน cluster เดียวกัน traffic จะใช้ &lt;code&gt;annotation&lt;/code&gt; ในการเลือก controller ทีละตัวจนกว่าจะพบตัวที่เหมาะสม &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7bx890ligule8t1cxsca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7bx890ligule8t1cxsca.png" alt="inbound-ingress" width="651" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Nginx
&lt;/h1&gt;

&lt;p&gt;การ deploy Nginx Controller สามารถดูตัวอย่างของหลากหลาย platform ได้จาก &lt;a href="https://github.com/kubernetes/ingress-nginx/tree/master/deploy" rel="noopener noreferrer"&gt;ingress-nginx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เราสามารถ configure &lt;strong&gt;Nginx Controller&lt;/strong&gt; ได้ผ่านทาง &lt;code&gt;ConfigMap&lt;/code&gt; และ &lt;code&gt;Annotations&lt;/code&gt; แต่ถ้ายังไม่พอใจเราสามารถ configure จาก &lt;a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/custom-template/" rel="noopener noreferrer"&gt;&lt;code&gt;custom template&lt;/code&gt;&lt;/a&gt; ได้ด้วย&lt;/p&gt;

&lt;p&gt;คุณสมบัติของ Nginx Controller&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;integrate กับ RBAC ได้ง่าย&lt;/li&gt;
&lt;li&gt;ใช้ annotation &lt;code&gt;kubernetes.io/ingress.class: "nginx"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ถ้าจะใช้เป็น L7 revere proxy ต้องระบุ &lt;code&gt;proxy-real-ip-cidr&lt;/code&gt; ด้วย&lt;/li&gt;
&lt;li&gt;ถ้าจะใช้ feature &lt;strong&gt;session affinity&lt;/strong&gt; ต้อง bypass &lt;code&gt;kube-proxy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ไม่ได้ใช้ &lt;a href="http://conntrack-tools.netfilter.org/" rel="noopener noreferrer"&gt;&lt;code&gt;conntrack&lt;/code&gt;&lt;/a&gt; ที่เกิดจาก DNAT ของ iptables&lt;/li&gt;
&lt;li&gt;ถ้าจะให้ terminate TLS ให้ต้องระบุุ host field ด้วย&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h1&gt;
  
  
  Google Load Balancer Controller (GLBC)
&lt;/h1&gt;

&lt;p&gt;GLBC คือ GCE L7 load balancer controller ที่จัดการ external loadbalancers ด้วย Kubernetes Ingress API &lt;/p&gt;

&lt;p&gt;ในสร้าง 1 Ingress ต้องมี cloud resource เหล่านี้&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Global Forwarding Rule&lt;/strong&gt;: เป็นตัว manage VIP ของ Ingress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TargetHttpProxy&lt;/strong&gt;: เป็นตัว manage SSL certs และ proxy ระหว่าง VIP และ Backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL Map&lt;/strong&gt;: routing rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Service&lt;/strong&gt;: รวม หลายๆ Instance Groups เป็น 1 Service Node Port &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance Group&lt;/strong&gt;: VM ที่ เป็น kubernetes node&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pipeline เป็นแบบนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdkzsb2n78oqvz4b7p0na.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdkzsb2n78oqvz4b7p0na.png" alt="pipeline" width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;โดย&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;แต่ละ Backend Service ต้องทำการ health check ไปยัง NodePort ของ Service&lt;/li&gt;
&lt;li&gt;แต่ละ port ของ Backend Service ต้องตรงกับ Instance Group&lt;/li&gt;
&lt;li&gt;แต่ละ port ของ Backend Service จะถูก expose ผ่าน firewall บน GCE LB IP ranges (130.211.0.0/22 and 35.191.0.0/16)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ตอนนี้ Ingress สามารถใช้ TLS และ TLS termination ที่ port 443 และยังไม่ support SNI&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Ingress API Resources
&lt;/h1&gt;

&lt;p&gt;Ingress object ยังเป็น extension API มีตัวอย่าง YAML file ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghost&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/testpath&lt;/span&gt;
        &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
        &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
          &lt;span class="na"&gt;servicePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เราสามารถ manage มันได้ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get ingress
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete ingress &amp;lt;ingress_name&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl edit ingress &amp;lt;ingress_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Deploying the Ingress Controller
&lt;/h1&gt;

&lt;p&gt;ในการ deploy Nginx Ingress ทำได้ง่ายๆ โดย ดูได้จาก &lt;a href="https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในตัวอย่างจะขอ install แบบ Bare-Metal โดยใช้ NodePort ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
service/ingress-nginx created

&lt;span class="c"&gt;# Verify&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;ingress-nginx &lt;span class="nt"&gt;--watch&lt;/span&gt;
NAMESPACE       NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx   nginx-ingress-controller-7f74f657bd-9fm44   1/1     Running   0          82s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating an Ingress Rule
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ทำการ create deployment และ expose service ที่ port 2368&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create deployment ghost &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghost
deployment.apps/ghost created

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl expose deployment ghost &lt;span class="nt"&gt;--port&lt;/span&gt; 2368
service/ghost exposed

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get svc
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;    AGE
service/ghost        ClusterIP   10.107.189.29   &amp;lt;none&amp;gt;        2368/TCP   11s
service/kubernetes   ClusterIP   10.96.0.1       &amp;lt;none&amp;gt;        443/TCP    74d

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods
NAME                         READY   STATUS    RESTARTS   AGE
pod/ghost-577564ddbd-nfqrc   1/1     Running   0          178m
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ทำการสร้าง ingress rule โดย request ที่เข้ามา domain เป็น "ghost.192.168.99.100.nip.io" ให้ส่งไปที่ service &lt;code&gt;ghost&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ingress.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
    name: ghost
spec:
    rules:
    - host: ghost.192.168.99.100.nip.io
    http:
        paths:
        - backend:
            serviceName: ghost
            servicePort: 2368
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; ingress.yaml
ingress.networking.k8s.io/ghost created

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get ingress
NAME    HOSTS                         ADDRESS   PORTS   AGE
ghost   ghost.192.168.99.100.nip.io             80      20s

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe ingress/ghost
Name:             ghost
Namespace:        default
Address:          10.107.151.119
Default backend:  default-http-backend:80 &lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;none&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;
Rules:
Host                         Path  Backends
&lt;span class="nt"&gt;----&lt;/span&gt;                         &lt;span class="nt"&gt;----&lt;/span&gt;  &lt;span class="nt"&gt;--------&lt;/span&gt;
ghost.192.168.99.100.nip.io  
                                ghost:2368 &lt;span class="o"&gt;(&lt;/span&gt;192.168.32.59:2368&lt;span class="o"&gt;)&lt;/span&gt;
Annotations:
Events:
Type    Reason  Age    From                      Message
&lt;span class="nt"&gt;----&lt;/span&gt;    &lt;span class="nt"&gt;------&lt;/span&gt;  &lt;span class="nt"&gt;----&lt;/span&gt;   &lt;span class="nt"&gt;----&lt;/span&gt;                      &lt;span class="nt"&gt;-------&lt;/span&gt;
Normal  CREATE  3m12s  nginx-ingress-controller  Ingress default/ghost
Normal  UPDATE  2m40s  nginx-ingress-controller  Ingress default/ghost
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ทำการ test ดูซิว่าถ้า doamin เป็น &lt;strong&gt;ghost.192.168.99.100.nip.io&lt;/strong&gt; จะวิ่งไปที่ service &lt;code&gt;ghost&lt;/code&gt; หรือไม่ โดยถ้าไปใช่ต้องได้ page ที่มี title เป็น &lt;strong&gt;ghost&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'host: ghost.192.168.99.100.nip.io'&lt;/span&gt; http://10.107.151.119:80 &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null
&amp;lt;title&amp;gt;Ghost&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Multiple Rules
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ทำการ create deployment ชื่อ nginx และ expose service ที่ port 80&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create deployment nginx &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx
deployment.apps/nginx created

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl expose deployment nginx &lt;span class="nt"&gt;--port&lt;/span&gt; 80
service/nginx exposed

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;    AGE
ghost        ClusterIP   10.107.189.29   &amp;lt;none&amp;gt;        2368/TCP   12m
kubernetes   ClusterIP   10.96.0.1       &amp;lt;none&amp;gt;        443/TCP    74d
nginx        ClusterIP   10.106.200.39   &amp;lt;none&amp;gt;        80/TCP     29s

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
ghost-577564ddbd-nfqrc   1/1     Running   0          3h11m
nginx-86c57db685-vb6gk   1/1     Running   0          63s
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ทำการ update ingress rule โดย เพิ่มให้ request ที่มี domain เป็น "nginx.192.168.99.100.nip.io" ให้ส่งไปที่ service &lt;code&gt;nginx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ingress-2.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
    name: ghost
spec:
    rules:
    - host: ghost.192.168.99.100.nip.io
    http:
        paths:
        - backend:
            serviceName: ghost
            servicePort: 2368
    - host: nginx.192.168.99.100.nip.io
    http:
        paths:
        - backend:
            serviceName: nginx
            servicePort: 80
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; ingress-2.yaml
ingress.networking.k8s.io/ghost configured

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get ingress
NAME    HOSTS                                                     ADDRESS          PORTS   AGE
ghost   ghost.192.168.99.100.nip.io,nginx.192.168.99.100.nip.io   10.107.151.119   80      10m

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe ingress/ghost
Name:             ghost
Namespace:        default
Address:          10.107.151.119
Default backend:  default-http-backend:80 &lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;none&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;
Rules:
Host                         Path  Backends
&lt;span class="nt"&gt;----&lt;/span&gt;                         &lt;span class="nt"&gt;----&lt;/span&gt;  &lt;span class="nt"&gt;--------&lt;/span&gt;
ghost.192.168.99.100.nip.io  
                                ghost:2368 &lt;span class="o"&gt;(&lt;/span&gt;192.168.32.59:2368&lt;span class="o"&gt;)&lt;/span&gt;
nginx.192.168.99.100.nip.io  
                                nginx:80 &lt;span class="o"&gt;(&lt;/span&gt;192.168.52.65:80&lt;span class="o"&gt;)&lt;/span&gt;
Annotations:
kubectl.kubernetes.io/last-applied-configuration:  &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"apiVersion"&lt;/span&gt;:&lt;span class="s2"&gt;"networking.k8s.io/v1beta1"&lt;/span&gt;,&lt;span class="s2"&gt;"kind"&lt;/span&gt;:&lt;span class="s2"&gt;"Ingress"&lt;/span&gt;,&lt;span class="s2"&gt;"metadata"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"annotations"&lt;/span&gt;:&lt;span class="o"&gt;{}&lt;/span&gt;,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"ghost"&lt;/span&gt;,&lt;span class="s2"&gt;"namespace"&lt;/span&gt;:&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"spec"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"rules"&lt;/span&gt;:[&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"host"&lt;/span&gt;:&lt;span class="s2"&gt;"ghost.192.168.99.100.nip.io"&lt;/span&gt;,&lt;span class="s2"&gt;"http"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"paths"&lt;/span&gt;:[&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"backend"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"serviceName"&lt;/span&gt;:&lt;span class="s2"&gt;"ghost"&lt;/span&gt;,&lt;span class="s2"&gt;"servicePort"&lt;/span&gt;:2368&lt;span class="o"&gt;}}]}}&lt;/span&gt;,&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"host"&lt;/span&gt;:&lt;span class="s2"&gt;"nginx.192.168.99.100.nip.io"&lt;/span&gt;,&lt;span class="s2"&gt;"http"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"paths"&lt;/span&gt;:[&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"backend"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"serviceName"&lt;/span&gt;:&lt;span class="s2"&gt;"nginx"&lt;/span&gt;,&lt;span class="s2"&gt;"servicePort"&lt;/span&gt;:80&lt;span class="o"&gt;}}]}}]}}&lt;/span&gt;

Events:
Type    Reason  Age                  From                      Message
&lt;span class="nt"&gt;----&lt;/span&gt;    &lt;span class="nt"&gt;------&lt;/span&gt;  &lt;span class="nt"&gt;----&lt;/span&gt;                 &lt;span class="nt"&gt;----&lt;/span&gt;                      &lt;span class="nt"&gt;-------&lt;/span&gt;
Normal  CREATE  10m                  nginx-ingress-controller  Ingress default/ghost
Normal  UPDATE  79s &lt;span class="o"&gt;(&lt;/span&gt;x2 over 9m50s&lt;span class="o"&gt;)&lt;/span&gt;  nginx-ingress-controller  Ingress default/ghost
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ทำการ test ดูซิว่า&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ถ้า doamin เป็น &lt;strong&gt;ghost.192.168.99.100.nip.io&lt;/strong&gt; จะต้องวิ่งไปที่ service &lt;code&gt;ghost&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ถ้า doamin เป็น &lt;strong&gt;nginx.192.168.99.100.nip.io&lt;/strong&gt; จะต้องวิ่งไปที่ service &lt;code&gt;nginx&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'host: ghost.192.168.99.100.nip.io'&lt;/span&gt; http://10.107.151.119:80 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;title&amp;gt;'&lt;/span&gt;
&amp;lt;title&amp;gt;Ghost&amp;lt;/title&amp;gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'host: nginx.192.168.99.100.nip.io'&lt;/span&gt; http://10.107.151.119:80 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;title&amp;gt;'&lt;/span&gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Intelligent Connected Proxies
&lt;/h1&gt;

&lt;p&gt;ในกรณีที่ต้องการ feaature ที่เพิ่มขึ้น เช่น service discovery, rate limiting, traffic management หรือ advanced metric เราต้องเปลี่ยนมาใช้ &lt;strong&gt;service mesh&lt;/strong&gt; โดย มันจะประกอบด้วย edge proxy และ embeded proxy ซึ่งจะทำงานร่วมกันเพื่อจักการ traffic ตาม rules ที่มาจาก control plane ตัวอย่างของ service mesh เช่น &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Envoy&lt;/strong&gt;: เป็น proxy ที่มีการออกแบบแบบ modular และ extensible มักถูกใช้เป็น data plane ของ tool อื่นๆ ของ service mesh&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Istio&lt;/strong&gt;: เป็น tool ที่ช่วยเพิ่มความสามารถของ &lt;strong&gt;Envoy&lt;/strong&gt; ด้วยการมี control plane ได้มากกว่า 1 ตัว เป็นตัวที่ทำให้ service mesh flexible ขึ้นและมี feature มากขึ้น&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fistio.io%2Fdocs%2Fops%2Fdeployment%2Farchitecture%2Farch.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fistio.io%2Fdocs%2Fops%2Fdeployment%2Farchitecture%2Farch.svg" alt="istio-envoy" width="960" height="540"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Linkerd&lt;/strong&gt;: เป็นอีกหนึ่ง service mesh ที่ออกแบบมาให้่ใช้งานได้ง่าย เร็ว และ เบา&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>nginx</category>
    </item>
    <item>
      <title>LFS258 [8/15]: Kubernetes Volumes and Data</title>
      <dc:creator>8P Inc.</dc:creator>
      <pubDate>Mon, 23 Mar 2020 10:34:49 +0000</pubDate>
      <link>https://dev.to/peepeepopapapeepeepo/lfs258-8-15-kubernetes-volumes-and-data-17de</link>
      <guid>https://dev.to/peepeepopapapeepeepo/lfs258-8-15-kubernetes-volumes-and-data-17de</guid>
      <description>&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
Overview: Volume มองง่ายๆ มันคือ hard disk ของ container มีทั้งแบบที่ถ้า Pods ตาย แล้ว data หาย (Ephemeral) และ data ไม่หาย (Persistent) โดย Kubernetes นั้น support storage หลายชนิด ที่เป็นแบบนั้นเพราะ มี FlexVolume และ CSI ซึ่งทำให้ vendor ต่างๆ สามารถพัฒนา library ที่ support product ของตนเองแล้วนำมาต่อกับ Kubernetes ได้เลย&lt;/li&gt;
&lt;li&gt;
Introducing Volumes: Kubernetes mount Volume ในระดับ Pods แสดงว่า container ต่างๆ ที่อยู่ใน Pods จะเห็น Volume เดียวกัน ขึ้นอยู่กับ container จะเลือกเอาอะไรไปใช้ การ access volume ขึ้นอยู่กับ Permission ที่เรากำหนดไว้ โดย Permission จะเป็นระดับ Node เช่น &lt;code&gt;ReadWriteOnce&lt;/code&gt;, &lt;code&gt;ReadOnlyMany&lt;/code&gt; และ &lt;code&gt;ReadWriteMany&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
Volume Types: Kubernetes support backend storage ได้หลากหลายชนิด เรียกชนิดเหล่านั้นว่า &lt;code&gt;StorageClass&lt;/code&gt; โดย &lt;code&gt;StorageClass&lt;/code&gt; นี้เองจะเป็นคนไปคุยกับ backend storage เพื่อทำงานต่างๆ ให้&lt;/li&gt;
&lt;li&gt;
Volume Spec: แสดงการใช้งาน &lt;code&gt;emptyDir&lt;/code&gt; ซึ่งเป็น &lt;code&gt;StorageClass&lt;/code&gt; ที่เป็น Ephemeral Storage&lt;/li&gt;
&lt;li&gt;
Share Volumes: แสดงการใช้ 2 containers ที่อยู่ใน Pod เดียวกัน mount Volume เดียวกัน โดยจะเห็นว่าทั้ง 2 containers เห็น data เดียวกัน&lt;/li&gt;
&lt;li&gt;
Persistence Volumes and Volume Claims: ในการใช้งาน Persistent Volume ต้องสร้าง PV ก่อน จากนั้นสร้าง PVC เพื่อจองพื้นที่จาก PV มาใช้ จากนั้นจึงจะสามารถ attach ไปให้ Pods ใช้งานได้&lt;/li&gt;
&lt;li&gt;
Secrets: เป็นวิธีช่วยให้ Pods การเข้าถึง data ได้ โดย Secret จะถูก encode แบบ base64 โดย Pods สามารถใช้งาน Secret ได้ทั้งแบบ Volume และ Environment Variable&lt;/li&gt;
&lt;li&gt;
ConfigMap: เป็นวิธีช่วยให้ Pods การเข้าถึง data ได้ โดย ConfigMap เป็น plain textโดย Pods สามารถใช้งาน ConfigMap ได้ทั้งแบบ Volume และ Environment Variable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;Volume ก็คือ storage นั่นเอง มองง่ายๆ มันคือ hard disk ของ container โดยทั่วไป ถ้า container ตายหรือ restart data ในนั้นก็จะหายไป ใน Kubernetes volume จะผูกอยู่กับ Pods ไม่ใช้ container ภายใน โดยถ้า Pods มี หลาย container อยู่ภายใน container นะเห็น volume และ data เดียวกัน&lt;/p&gt;

&lt;p&gt;ถ้าไม่อยากให้ data หายสามารถใช้ &lt;strong&gt;Persistent Volumes&lt;/strong&gt; ได้ ซึ่ง Pods จะต้องทำการ claim volume ผ่านทาง &lt;strong&gt;Persistent Volumes Claim&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;การเขียนหรืออ่านอะไรใน volume จะต้องทำผ่าน backend storage ซึ่งใน v1.17 รองรับถึง &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes" rel="noopener noreferrer"&gt;28 ชนิด&lt;/a&gt; ไม่ว่าจะเป็น Ceph, NFS หรือ storage ของ cloud providers เจ้าต่างฟๆ ซึ่งอาจมีการ setting ต่างกันตามชนิดของ backend&lt;/p&gt;

&lt;p&gt;Backend storage ของ kubernetes มี 2 แบบ คือ &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;in-tree&lt;/strong&gt;: ส่วนใหญ่เป็นมากับ kubernetes มาอย่างยาวนาน โดย code จะถูก built, linked, compiled และ shipped มากับ core ของ Kubernetes เลย Backend ประเภทนี้ค่อยๆ ถูก migrate ไปเป็น &lt;strong&gt;out-of-tree&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;out-of-tree&lt;/strong&gt;: หลังจากการมาของ &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/#flexVolume" rel="noopener noreferrer"&gt;FlexVolume&lt;/a&gt; ใน v1.2 และ &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/#csi" rel="noopener noreferrer"&gt;Container Storage Interface (CSI)&lt;/a&gt; ใน v1.9 ทำให้ code ของ backend storage ไม่ต้องรวมอยู่ใน core ของ Kubernetes อีกต่อไป โดย เจ้าของ product สามารถ develop เองแล้วค่อยมาต่อกับ kubernetes ด้วยวิธีนี้ทำให้ core ของ kubernetes เบาขึ้น และ เจ้าของ product สามารถ fix bug หรือ เพิ่ม feature ได้เองโดยไม่ต้องรอมาก merge code กับ kubernets การทำงานก็จะเร็วขึ้น &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;นอกจากการใช้ volume ที่กล่าวข้างต้น ยังมีอีก 2 ทางในการนำ data เข้าไปยัง Pods นั่นคือ &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: เป็นการส่งข้อมูลที่ถูก encode แล้ว เข้าไปใน pods เช่น SSH Key หรือ HTTPS certification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ConfigMap&lt;/strong&gt;: เป็นการส่งข้อมูลที่ไม่ถูก encode เข้าไปใน pods เช่น configuration file ต่างๆ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Introducing Volumes
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffab39mncp3y6vgi6kdbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffab39mncp3y6vgi6kdbf.png" alt="Pods-Volumes" width="505" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ใน 1 Pods สามารถ mount ได้หลาย volume และสามารถคละ backend type ได้ และเนื่องจาก volume ถูก present ในระดับ Pod ดังนั้น ต่าง container กันสามารถเห็น volume เดียวกันได้ อาจใช้ท่านี้เพื่อใช้ในการทำ container-to-container communition &lt;/p&gt;

&lt;p&gt;นอกจากนี้ Volume ยังสามารถถูกเข้าถึงได้จากหลายๆ pods พร้อมๆ กัน และสามารถให้สิทธิ์ที่แตกต่างกันได้ แต่เนื่องจาก volume ไม่มี concurrent checking ดังนั้น อาจเกิดปัญหา file corrupt หรือ locking ได้&lt;/p&gt;

&lt;p&gt;ในการกำหนด access mode ต้องทำ 2 ทางคือ ที่ volume เอง และ ตอนที่ Pods request claim เข้ามา โดย request จะต้องไม่มากกว่าสิทธิ์ที่ volume กำหนดไว้ โดย access mode มี ดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ReadWriteOnce&lt;/strong&gt;: อนุญาติให้อ่านและเขียนได้จาก node เดียวเท่านั้น (2 pods ที่อยู่ node เดียวกัน สามารถ read/write ได้พร้อมกัน แต่ pods ที่อยู่ต่าง node read/write ไม่ได้ จะได้ error &lt;code&gt;FailedAttachVolume&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReadOnlyMany&lt;/strong&gt;: อนุญาติให้อ่านอย่างเดียว จากหลายๆ node พร้อมๆ กัน&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReadWriteMany&lt;/strong&gt;: อนุญาติให้อ่านและเขียนได้จากหลายๆ node พร้อมๆ กัน&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;kubernetes จะจัดกลุ่มของ volume ที่มี permission เหมือนกันไว้ และ sort volume size จาก น้อยไปหามาก เมื่อมี request เข้ามาก็จะเลือก volume เหมาะสมที่สุดให้ไป &lt;/p&gt;

&lt;p&gt;เมื่อมีการ request volume &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API server ของ kubernetes จะ request ขอ storage ไปยัง &lt;code&gt;StorageClass&lt;/code&gt; plugin และ &lt;code&gt;StorageClass&lt;/code&gt; plugin จะเป็นคนไปคุยกับ backend storage เอง&lt;/li&gt;
&lt;li&gt;kubelet จะ map raw devices กับ mount point ใน container แลัวทำเป็น symbolic link บน host node filesystem เพื่อให้ container ใช้งาน storage ได้&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ถ้าเราไม่ระบุ &lt;code&gt;StorageClass&lt;/code&gt; Kubernetes จะเลือก Storage อะไรก็ได้ที่เหมาะสมกับ size และ access mode ที่เราส่งไปกลับมาให้เรา&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Volume Types
&lt;/h1&gt;

&lt;p&gt;Kubernetes รองรับ Backend Storage ได้หลากหลาย บางอย่างใช้แค่ใน local บางอย่างต้องใช้งานผ่าน network ซึ่งแต่ละชนิดก็มีข้อดี ข้อเสีย แต่ต่างกันออกไป เรามาดูตัวอย่างกันซักหน่อย&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GCEpersistentDisk&lt;/strong&gt;: เป็นการ mount disk GCE บน Google Cloud Platform เข้าไปใน Pods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;awselasticblockstore&lt;/strong&gt;: เป็นการ mount disk EBS บน AWS เข้าไปใน Pods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;emptyDir&lt;/strong&gt;: เป็นการ mount empty directory ให้กับ Pods โดย volume จะเกิดและตายไปพร้อมกับ Pod&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;hostPath&lt;/strong&gt;: เป็นการ mount resource จาก host เช่น file หรือ directoty ให้กับ Pods ซึ่งต้องมี resource ดังกล่าวอยู่ที่ host ก่อน ยกเว้น ถ้าเราใช้ option &lt;code&gt;DirectoryOrCreate&lt;/code&gt; หรือ &lt;code&gt;FileOrCreate&lt;/code&gt; ที่จะสร้าง resource ให้ Pods หากไม่มี&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nfs&lt;/strong&gt;: เป็นการ mount NFS (Network File System) ให้กับ Pods (เหมาะกับ multiple readers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iscsi&lt;/strong&gt;: เป็นการ mount iSCSI (SCSI over IP) ให้กับ Pods (เหมาะกับ multiple readers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rbd&lt;/strong&gt;: เป็นการ mount Rados Block Device ให้กับ Pods (เหมาะกับ multiple writers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cephfs&lt;/strong&gt;: เป็นการ mount CephFS volume ให้กับ Pods (เหมาะกับ multiple writers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;glusterfs&lt;/strong&gt;: เป็นการ mount  Glusterfs (an open source networked filesystem) ให้กับ Pods (เหมาะกับ multiple writers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;สามารถดู Backend Storage เพิ่มเติมได้จาก &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/" rel="noopener noreferrer"&gt;Official Doc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Volume Spec
&lt;/h1&gt;

&lt;p&gt;ตัวอย่างการ mount volume ง่ายๆ คือ &lt;strong&gt;emptyDir&lt;/strong&gt; โดย &lt;code&gt;emptyDir&lt;/code&gt; คือ การสร้าง directory ภายใน container นั่นเอง ไม่ได้ไป mount ที่ไหน การเขียน data จะอยู่ใน shared container space ทำให้ไม่ persistence และ หายไปพร้อมกับการตายของ Pod&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busy&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sleep&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;
    &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/scratch&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scratch-volume&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scratch-volume&lt;/span&gt;
    &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;YAML file ข้างต้น เป็นการสร้าง Pod ที่มี 1 container และมี volume ชื่อ scratch-volume mount ไว้ที /scratch ภายใน container &lt;/p&gt;

&lt;p&gt;สามารถทดสอบได้ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; busybox-emptyDir.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox
    name: busy
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /scratch
      name: scratch-volume
  volumes:
  - name: scratch-volume
    emptyDir: {}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; busybox-emptyDir.yml 
pod/busybox created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          9s
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; busybox &lt;span class="nt"&gt;--&lt;/span&gt; sh
/ &lt;span class="c"&gt;# df -h /scratch&lt;/span&gt;
Filesystem                Size      Used Available Use% Mounted on
/dev/mapper/rhel-var     60.0G      2.5G     57.5G   4% /scratch
/ &lt;span class="c"&gt;# exit&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; busybox-emptyDir.yml 
pod &lt;span class="s2"&gt;"busybox"&lt;/span&gt; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Share Volumes
&lt;/h1&gt;

&lt;p&gt;จากที่เรารู้แล้วว่า container ใน pod เดียวกัน จะ share volume กัน หัวข้อนี้จะมาลงมือทำกันดู ก่อนอื่นมาดู YAML กันก่อน&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busy&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sleep&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;
    &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/busy&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;box&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sleep&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;
    &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/box&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จาก YAML file ข้างต้น ใน pod มี 2 containers ชื่อ busy และ box โดย แต่ละ container mount volume ชื่อ test ไว้ที่ path /busy และ /box ตามลำดับ เดี๋ยวเราจะลองเขียนอ่าน file ดูกันว่ามันเห็นเหมือนกันไหม&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; busybox-sharedVolume.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox
    name: busy
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /busy
      name: test
  - image: busybox
    name: box
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /box
      name: test
  volumes:
  - name: test
    emptyDir: {}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; busybox-sharedVolume.yml
pod/busybox created 
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
busybox   2/2     Running   0          16s
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; busybox &lt;span class="nt"&gt;-c&lt;/span&gt; box &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;touch&lt;/span&gt; /box/foobar
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; busybox &lt;span class="nt"&gt;-c&lt;/span&gt; busy &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; /busy/
foobar
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; busybox-sharedVolume.yml
pod &lt;span class="s2"&gt;"busybox"&lt;/span&gt; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Persistence Volumes and Volume Claims
&lt;/h1&gt;

&lt;p&gt;Persistence Volumes (pv) คือ storage ที่ ถ้า pods ตาย data ก็ยังอยู่ การ assign volume ประเภทนี้ ให้กับ Pods จะต้อง define &lt;code&gt;PersistentVolume&lt;/code&gt; ก่อน จากนี้ สร้าง &lt;code&gt;persistenctVolumeClaim&lt;/code&gt; (pvc) เพื่อขอเฉือน volume มาให้ pod ใช้ จากนั้นจึงค่อย attach persistent volume ก้อนที่ claim มาให้กับ Pod  &lt;/p&gt;

&lt;p&gt;Lifecycle ของ volume และ claim มีดังนี้&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provisioning&lt;/strong&gt;: เป็นการสร้าง PV โดยทำได้ทั้ง Static และ Dynamic

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Statics&lt;/strong&gt;: admin create PV ทิ้งไว้ รอให้ users มา claim ไปใช้&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic&lt;/strong&gt;: ถ้าไม่มี PV ไหน match กับ PVC เลย kubernetes จะ create PV ให้อันโนมัติ โดย cluster ต้องระบุ &lt;code&gt;DefaultStorageClass&lt;/code&gt; ด้วย&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Binding&lt;/strong&gt;: อาจเป็นช่วงที่กำลัง match PVC กับ PV หรือ PV รอให้ &lt;code&gt;StorageClass&lt;/code&gt; provision PV ขึ้นมา&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Using&lt;/strong&gt;: เป็นช่วงที่ Pods mount volume ไปใช้แล้ว&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Releasing&lt;/strong&gt;: เป็นช่วงที่ Pods ส่งคำสั่งไปเลิกใช้ PVC และทำการ delete PVC เมื่อ PVC ถูกลบไปแล้ว data อาจยังอยู่หรือถูกลบขึ้นอยู่กับ &lt;code&gt;persistentVolumeReclaimPolicy&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Reclaiming&lt;/strong&gt;: เป็นช่วงหลังจากที่ PVC ถูก delete เรียบร้อย โดย มี 3 options คือ

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retain&lt;/strong&gt;: ยังเก็บ PV ไว้อยู่ ให้ admin เอาไปจัดการเอง&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete&lt;/strong&gt;: ลบ PV และ Backend Storage ที่ allocate ให้ไปด้วยเลย&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recycle&lt;/strong&gt;: (deprecated แล้ว) ลบข้อมูลใน PVC ด้วย (&lt;code&gt;rm -rf /thevolume/*&lt;/code&gt;) จากนั้นก็พร้อมสำหรับการถูก claim ใหม่ &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;PV นั้นไม่ได้อยู่ใน namespace ใด namespace หนึ่ง แต่ PVC จะอยู่ได้แค่ namespace เดียวเท่านั้น &lt;/p&gt;

&lt;p&gt;&lt;u&gt;ตัวอย่างการ create PV&lt;/u&gt; ชื่อ task-pv-volume ซึ่งแบบ hostPath ขนาด 10 GB ใน mode ReadWriteOnce&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pv-volume.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: PersistentVolume
metadata:
  name: task-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pv-volume.yaml 
persistentvolume/task-pv-volume created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pv task-pv-volume
NAME             CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
task-pv-volume   10Gi       RWO            Retain           Available           manual                  14s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;ตัวอย่างการ create PVC&lt;/u&gt; ชื่อ task-pv-claim ซึ่ง เฉือนมาใช้ 3 GB ใน mode ReadWriteOnce&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pv-claim.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: task-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pv-claim.yaml
persistentvolumeclaim/task-pv-claim created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pv task-pv-volume
NAME             CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS   REASON   AGE
task-pv-volume   10Gi       RWO            Retain           Bound    default/task-pv-claim   manual                  6m23s
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pvc task-pv-claim
NAME            STATUS   VOLUME           CAPACITY   ACCESS MODES   STORAGECLASS   AGE
task-pv-claim   Bound    task-pv-volume   10Gi       RWO            manual         75s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;จะเห็นว่า PVC เปลี่ยนจาก status "Available" เป็น "Bound"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;u&gt;ตัวอย่างการ attch PVC ไปยัง Pods&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ที่ Master Node&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pv-pod.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
  name: task-pv-pod
spec:
  volumes:
    - name: task-pv-storage
      persistentVolumeClaim:
        claimName: task-pv-claim
  containers:
    - name: task-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: task-pv-storage
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pv-pod.yaml
pod/task-pv-pod created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pod task-pv-pod &lt;span class="nt"&gt;-o&lt;/span&gt; wide
NAME          READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES
task-pv-pod   1/1     Running   0          71s   192.168.32.51   kube-0003.novalocal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;

&lt;span class="c"&gt;# login เข้าไปยังเครื่องที่ pods run อยู่ ในที่นี้คือ kube-0003.novalocal &lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"echo 'Hello from Kubernetes storage' &amp;gt; /mnt/data/index.html"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /mnt/data/index.html
Hello from Kubernetes storage

&lt;span class="c"&gt;# กลับไปยัง Master Node&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; task-pv-pod &lt;span class="nt"&gt;--&lt;/span&gt; /bin/bash
root@task-pv-pod:/# apt update
root@task-pv-pod:/# apt &lt;span class="nb"&gt;install &lt;/span&gt;curl
root@task-pv-pod:/# curl http://localhost/
Hello from Kubernetes storage
root@task-pv-pod:/# &lt;span class="nb"&gt;exit&lt;/span&gt;

&lt;span class="c"&gt;# Clean up&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete pod task-pv-pod
pod &lt;span class="s2"&gt;"task-pv-pod"&lt;/span&gt; deleted
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete pvc task-pv-claim
persistentvolumeclaim &lt;span class="s2"&gt;"task-pv-claim"&lt;/span&gt; deleted
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete pv task-pv-volume
persistentvolume &lt;span class="s2"&gt;"task-pv-volume"&lt;/span&gt; deleted

&lt;span class="c"&gt;# Login ไปยังเครื่องที่ สร้าง file ไว้ (kube-0003.novalocal)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /mnt/data/index.html
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo rmdir&lt;/span&gt; /mnt/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Secrets
&lt;/h1&gt;

&lt;p&gt;ถึงแม้ Pods จะสามารถเข้าถึง data ต่างๆ ด้วย volume แต่ก็มีบาง data ที่เราไม่อยากให้เห็นได้ด้วยตาเปล่า เช่น password หรือ certification จึงมี &lt;strong&gt;Secret&lt;/strong&gt; เกิดขึ้นมา โดย Secret จะถูกเก็บในรูปแบบ base64-encoded (default) &lt;/p&gt;

&lt;p&gt;แต่เราสามารถ configure ให้มัน encrypt ได้โดยการสร้าง &lt;code&gt;EncryptionConfiguration&lt;/code&gt; ด้วย key และ identity ที่เหมาะสม จากนั้นทำการเพิ่ม flag &lt;code&gt;--encryption-provider-config&lt;/code&gt; ที่ใช่ระบุวิธีการ encrypt เช่น "aescdc" หรือ "ksm" ให้กับ&lt;code&gt;kube-apiserver&lt;/code&gt; แล้วทำการ recreate Secret ใหม่ทั้งหมด&lt;/p&gt;

&lt;p&gt;ในการเปลี่ยน key ต้องสร้าง key ใหม่ก่อน แล้วจึง restart &lt;code&gt;kube-apiserver&lt;/code&gt; ทุกตัว จากนั้นจึง recreate Secret ใหม่ทั้งหมด&lt;/p&gt;

&lt;p&gt;kubernetes ไม่ได้จำกับจำนวน Secret แต่ Secret ไม่ควรมีขนาดเกิน 1 MB โดย Secret จะถูกเก็บใน &lt;code&gt;tmpfs&lt;/code&gt; ซึ่งเป็น memory ดังนั้นถ้ามีเยอะกินไปก็จะเปลือง memory ของ host&lt;/p&gt;

&lt;h2&gt;
  
  
  เราสามารถ create Secret ได้ดังนี้
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create ด้วย &lt;code&gt;kubectl create secret&lt;/code&gt; command จาก file&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; username.txt
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1f2d1e2e67df'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; password.txt
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create secret generic db-user-pass &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./username.txt &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./password.txt
secret/db-user-pass created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get secrets
NAME                  TYPE                                  DATA   AGE
db-user-pass          Opaque                                2      10s
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe secret/db-user-pass
Name:         db-user-pass
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  &amp;lt;none&amp;gt;

Type:  Opaque

Data
&lt;span class="o"&gt;====&lt;/span&gt;
password.txt:  12 bytes
username.txt:  5 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create ด้วย &lt;code&gt;kubectl create secret&lt;/code&gt; command จากการ pass argument&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create secret generic dev-db-secret &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;devuser &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'S!B\*d$zDsb'&lt;/span&gt;
secret/dev-db-secret created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get secrets
NAME                  TYPE                                  DATA   AGE
dev-db-secret         Opaque                                2      4s
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe secret/dev-db-secret
Name:         dev-db-secret
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  &amp;lt;none&amp;gt;

Type:  Opaque

Data
&lt;span class="o"&gt;====&lt;/span&gt;
password:  11 bytes
username:  7 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create ด้วย YAML file แบบ manual&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt; | &lt;span class="nb"&gt;base64
&lt;/span&gt;&lt;span class="nv"&gt;YWRtaW4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1f2d1e2e67df'&lt;/span&gt; | &lt;span class="nb"&gt;base64
&lt;/span&gt;MWYyZDFlMmU2N2Rm
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; secret.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; secret.yaml
secret/mysecret created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get secrets
NAME                  TYPE                                  DATA   AGE
mysecret              Opaque                                2      15s
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe secret/mysecret
Name:         mysecret
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  
Type:         Opaque

Data
&lt;span class="o"&gt;====&lt;/span&gt;
password:  12 bytes
username:  5 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create ด้วย YAML file โดยใช้ &lt;code&gt;stringData&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; secret_config.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Secret
metadata:
name: config.yaml
type: Opaque
stringData:
config.yaml: |-
    apiUrl: "https://my.api.com/api/v1"
    username: "user"
    password: "password"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; secret_config.yaml
secret/config.yaml created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get secrets
NAME                  TYPE                                  DATA   AGE
config.yaml           Opaque                                1      8s
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe secret/config.yaml
Name:         config.yaml
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  
Type:         Opaque

Data
&lt;span class="o"&gt;====&lt;/span&gt;
config.yaml:  73 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  เราสามารถใช้งาน Secret ได้ดังนี้
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ใช้ในรูปแบบของ file ใน Pods&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe secret/mysecret
Name:         mysecret
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  
Type:         Opaque

Data
&lt;span class="o"&gt;====&lt;/span&gt;
password:  12 bytes
username:  5 bytes
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pod-with-mysecret-01.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    readOnly: true
volumes:
- name: foo
    secret:
    secretName: mysecret
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pod-with-mysecret-01.yaml
pod/mypod created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mypod &lt;span class="nt"&gt;--&lt;/span&gt; bash
root@mypod:/data# &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /etc/foo
total 0
lrwxrwxrwx. 1 root root 15 Mar 20 15:30 password -&amp;gt; ..data/password
lrwxrwxrwx. 1 root root 15 Mar 20 15:30 username -&amp;gt; ..data/username
root@mypod:/data# &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/foo/username
admin
root@mypod:/data# &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/foo/password
1f2d1e2e67df
root@mypod:/data# &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; pod-with-mysecret-01.yaml
pod &lt;span class="s2"&gt;"mypod"&lt;/span&gt; deleted
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;เราสามารถเลือก mount บาง file แล้วเปลี่ยนที่วาง และ ชื่อ file ด้วยได้ดังนี้&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pod-with-mysecret-02.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    readOnly: true
volumes:
- name: foo
    secret:
    secretName: mysecret
    items:
    - key: username
      path: my-group/my-username
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pod-with-mysecret-02.yaml
pod/mypod created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mypod &lt;span class="nt"&gt;--&lt;/span&gt; bash
root@mypod:/data# &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /etc/foo/my-group/my-username 
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; 1 root root 5 Mar 20 15:35 /etc/foo/my-group/my-username
root@mypod:/data# &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/foo/my-group/my-username
admin
root@mypod:/data# &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; pod-with-mysecret-02.yaml
pod &lt;span class="s2"&gt;"mypod"&lt;/span&gt; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ใช้ในรูปแบบของ Environment Variable ใน Pods&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pod-with-mysecret-03.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pod-with-mysecret-03.yaml
pod/secret-env-pod created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; secret-env-pod &lt;span class="nt"&gt;--&lt;/span&gt; bash
root@secret-env-pod:/data# &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SECRET_USERNAME&lt;/span&gt;
admin
root@secret-env-pod:/data# &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SECRET_PASSWORD&lt;/span&gt;
1f2d1e2e67df
root@secret-env-pod:/data# &lt;span class="nb"&gt;exit
exit&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; pod-with-mysecret-03.yaml
pod &lt;span class="s2"&gt;"secret-env-pod"&lt;/span&gt; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ConfigMap
&lt;/h1&gt;

&lt;p&gt;ConfigMap เหมือน Secret เลย ยกเว้นแค่ไม่ได้ encode เราใช้ ConfigMap เพื่อแยก configuration file ออกมาจาก image ของ container เพื่อเวลาที่ต้องการแก้ไข configuration file จะได้ไม่ต้อง build image ใหม่ ซึ่งจะทำให้ version ของ image เพิ่มขึ้น โดยไม่ได้มีการแก้ไข code ด้วย&lt;/p&gt;

&lt;p&gt;ConfigMap สามารถเก็บข้อมูลได้ทั้งในรูปแบบ key-value pair หรือ plain configuration file&lt;/p&gt;

&lt;h2&gt;
  
  
  เราสามารถสร้าง ConfigMap ได้ดังนี้
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;สร้างจาก Directory&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; configure-pod-container/configmap/
&lt;span class="nv"&gt;$ &lt;/span&gt;wget https://kubernetes.io/examples/configmap/game.properties &lt;span class="nt"&gt;-O&lt;/span&gt; configure-pod-container/configmap/game.properties
&lt;span class="nv"&gt;$ &lt;/span&gt;wget https://kubernetes.io/examples/configmap/ui.properties &lt;span class="nt"&gt;-O&lt;/span&gt; configure-pod-container/configmap/ui.properties
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; configure-pod-container/configmap/
total 8
&lt;span class="nt"&gt;-rw-rw-r--&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; 1 admin admin 157 Mar 20 22:57 game.properties
&lt;span class="nt"&gt;-rw-rw-r--&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; 1 admin admin  83 Mar 20 22:57 ui.properties
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create configmap game-config &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;configure-pod-container/configmap/
configmap/game-config created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe configmaps game-config
Name:         game-config
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  &amp;lt;none&amp;gt;

Data
&lt;span class="o"&gt;====&lt;/span&gt;
game.properties:
&lt;span class="nt"&gt;----&lt;/span&gt;
&lt;span class="nv"&gt;enemies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aliens
&lt;span class="nv"&gt;lives&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
enemies.cheat&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;enemies.cheat.level&lt;span class="o"&gt;=&lt;/span&gt;noGoodRotten
secret.code.passphrase&lt;span class="o"&gt;=&lt;/span&gt;UUDDLRLRBABAS
secret.code.allowed&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;secret.code.lives&lt;span class="o"&gt;=&lt;/span&gt;30
ui.properties:
&lt;span class="nt"&gt;----&lt;/span&gt;
color.good&lt;span class="o"&gt;=&lt;/span&gt;purple
color.bad&lt;span class="o"&gt;=&lt;/span&gt;yellow
allow.textmode&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;how.nice.to.look&lt;span class="o"&gt;=&lt;/span&gt;fairlyNice

Events:  &amp;lt;none&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get configmaps game-config &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
apiVersion: v1
data:
game.properties: |-
    &lt;span class="nv"&gt;enemies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aliens
    &lt;span class="nv"&gt;lives&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
    enemies.cheat&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
    &lt;/span&gt;enemies.cheat.level&lt;span class="o"&gt;=&lt;/span&gt;noGoodRotten
    secret.code.passphrase&lt;span class="o"&gt;=&lt;/span&gt;UUDDLRLRBABAS
    secret.code.allowed&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
    &lt;/span&gt;secret.code.lives&lt;span class="o"&gt;=&lt;/span&gt;30
ui.properties: |
    color.good&lt;span class="o"&gt;=&lt;/span&gt;purple
    color.bad&lt;span class="o"&gt;=&lt;/span&gt;yellow
    allow.textmode&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
    &lt;/span&gt;how.nice.to.look&lt;span class="o"&gt;=&lt;/span&gt;fairlyNice
kind: ConfigMap
metadata:
creationTimestamp: &lt;span class="s2"&gt;"2020-03-20T15:58:20Z"&lt;/span&gt;
name: game-config
namespace: default
resourceVersion: &lt;span class="s2"&gt;"13587110"&lt;/span&gt;
selfLink: /api/v1/namespaces/default/configmaps/game-config
uid: efe9fc42-4c19-495f-a0f1-91b59dc77431
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;สร้างจาก File&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create configmap game-config-3 &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;game-special-key&lt;span class="o"&gt;=&lt;/span&gt;configure-pod-container/configmap/game.properties
configmap/game-config-3 created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe configmaps game-config-3 
Name:         game-config-3
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  &amp;lt;none&amp;gt;

Data
&lt;span class="o"&gt;====&lt;/span&gt;
game-special-key:
&lt;span class="nt"&gt;----&lt;/span&gt;
&lt;span class="nv"&gt;enemies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aliens
&lt;span class="nv"&gt;lives&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
enemies.cheat&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;enemies.cheat.level&lt;span class="o"&gt;=&lt;/span&gt;noGoodRotten
secret.code.passphrase&lt;span class="o"&gt;=&lt;/span&gt;UUDDLRLRBABAS
secret.code.allowed&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;secret.code.lives&lt;span class="o"&gt;=&lt;/span&gt;30
Events:  &amp;lt;none&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get configmaps game-config-3 &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
apiVersion: v1
data:
game-special-key: |-
    &lt;span class="nv"&gt;enemies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aliens
    &lt;span class="nv"&gt;lives&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
    enemies.cheat&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
    &lt;/span&gt;enemies.cheat.level&lt;span class="o"&gt;=&lt;/span&gt;noGoodRotten
    secret.code.passphrase&lt;span class="o"&gt;=&lt;/span&gt;UUDDLRLRBABAS
    secret.code.allowed&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
    &lt;/span&gt;secret.code.lives&lt;span class="o"&gt;=&lt;/span&gt;30
kind: ConfigMap
metadata:
creationTimestamp: &lt;span class="s2"&gt;"2020-03-20T16:00:46Z"&lt;/span&gt;
name: game-config-3
namespace: default
resourceVersion: &lt;span class="s2"&gt;"13587478"&lt;/span&gt;
selfLink: /api/v1/namespaces/default/configmaps/game-config-3
uid: 03e19330-0498-43f3-baf6-fce9501398df
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;สร้างจาก literal values&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create configmap special-config &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;special.how&lt;span class="o"&gt;=&lt;/span&gt;very &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;special.type&lt;span class="o"&gt;=&lt;/span&gt;charm
configmap/special-config created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe configmaps special-config
Name:         special-config
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  &amp;lt;none&amp;gt;

Data
&lt;span class="o"&gt;====&lt;/span&gt;
special.how:
&lt;span class="nt"&gt;----&lt;/span&gt;
very
special.type:
&lt;span class="nt"&gt;----&lt;/span&gt;
charm
Events:  &amp;lt;none&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get configmaps special-config &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
apiVersion: v1
data:
special.how: very
special.type: charm
kind: ConfigMap
metadata:
creationTimestamp: &lt;span class="s2"&gt;"2020-03-20T16:03:16Z"&lt;/span&gt;
name: special-config
namespace: default
resourceVersion: &lt;span class="s2"&gt;"13587836"&lt;/span&gt;
selfLink: /api/v1/namespaces/default/configmaps/special-config
uid: 7d5636bb-a677-46dd-8f49-a60f35999357
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  เราสามารถใช้งาน ConfigMap ได้ดังนี้
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Assign ConfigMap ไปยัง Environment Variable&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create configmap special-config &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;special.how&lt;span class="o"&gt;=&lt;/span&gt;very
configmap/special-config created
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pod-single-configmap-env-variable.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
    - name: test-container
    image: redis
    env:
        - name: SPECIAL_LEVEL_KEY
        valueFrom:
            configMapKeyRef:
            name: special-config
            key: special.how
restartPolicy: Never
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; pod-single-configmap-env-variable.yaml
pod/dapi-test-pod created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; dapi-test-pod &lt;span class="nt"&gt;--&lt;/span&gt; bash
root@dapi-test-pod:/data# &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SPECIAL_LEVEL_KEY&lt;/span&gt;
very
root@dapi-test-pod:/data# &lt;span class="nb"&gt;exit
exit&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; pod-single-configmap-env-variable.yaml
pod &lt;span class="s2"&gt;"dapi-test-pod"&lt;/span&gt; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;add ConfigMap เป็น data ใน Volume&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; configmap-multikeys.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
SPECIAL_LEVEL: very
SPECIAL_TYPE: charm
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; configmap-multikeys.yaml
configmap/special-config created
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pod-configmap-volume.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
    - name: test-container
    image: redis
    volumeMounts:
    - name: config-volume
        mountPath: /etc/config
volumes:
    - name: config-volume
    configMap:
        name: special-config
restartPolicy: Never
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; pod-configmap-volume.yaml
pod/dapi-test-pod created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; dapi-test-pod &lt;span class="nt"&gt;--&lt;/span&gt; bash
root@dapi-test-pod:/data# &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /etc/config/
total 0
lrwxrwxrwx. 1 root root 20 Mar 20 16:25 SPECIAL_LEVEL -&amp;gt; ..data/SPECIAL_LEVEL
lrwxrwxrwx. 1 root root 19 Mar 20 16:25 SPECIAL_TYPE -&amp;gt; ..data/SPECIAL_TYPE
root@dapi-test-pod:/data# &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/config/SPECIAL_LEVEL
very
root@dapi-test-pod:/data# &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/config/SPECIAL_TYPE 
charm
root@dapi-test-pod:/data# &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; pod-configmap-volume.yaml
pod &lt;span class="s2"&gt;"dapi-test-pod"&lt;/span&gt; deleted
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; configmap-multikeys.yaml
configmap &lt;span class="s2"&gt;"special-config"&lt;/span&gt; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>nginx</category>
    </item>
  </channel>
</rss>
