<?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>Forem: terngr</title>
    <description>The latest articles on Forem by terngr (@terngr).</description>
    <link>https://forem.com/terngr</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%2F493002%2F251c1944-882b-4caa-b228-c80f18a7675d.png</url>
      <title>Forem: terngr</title>
      <link>https://forem.com/terngr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/terngr"/>
    <language>en</language>
    <item>
      <title>API Security with encrypted value in endpoints</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Fri, 28 Nov 2025 19:08:26 +0000</pubDate>
      <link>https://forem.com/terngr/api-security-with-encrypted-value-in-endpoints-186j</link>
      <guid>https://forem.com/terngr/api-security-with-encrypted-value-in-endpoints-186j</guid>
      <description>&lt;p&gt;งานนี้จะแชร์การใช้งาน Generative AI มาทำงานแทนเรา ในงานที่เราทำเป็นอยู่แล้ว แต่อยากลดเวลา และเราสามารถตรวจงานได้ง่ายครับ โดยรวมคือใช้เวลาเพียง 5-10 นาที แทนที่จะใช้ 30-60 นาที ทำให้มีเวลาไปดูพระอาทิตย์ตกทันนั่นเอง&lt;br&gt;
.&lt;br&gt;
โจทย์คือ API Security จุดซึ่งมีการทำ End to end encryption ตัว Value ใน Key: Value เช่นยอดเงินในบัญชีธนาคาร&lt;br&gt;
.&lt;br&gt;
การจะทำ Security ให้กับ API ได้ โดยปกติก็จะต้องมี API ใช้งานก่อน หรือก็คือมีข้อมูล API มาให้ปกป้องนั่นเอง ฉะนั้นเราจะสร้าง API Application ขึ้นมา 1 ตัว โดยมีความสามารถในการ Encrypted ยอดเงินในบัญชีได้ด้วย ฉะนั้้นเมื่อปลายทางอ่านข้อมูลนี้ไปได้ แบบ End to end ก็จะได้ Encrypted value ซึ่งจะต้องนำไป decrypt ด้วย key เดียวกัน จึงจะได้ยอดเงินจริงออกมาใช้งานได้นั่นเอง&lt;br&gt;
.&lt;br&gt;
โดยปกติแล้ว เราสามารถสร้าง Rest API ด้วย Python ได้เลย และเรียกใช้ .encrypt function เพื่อทำการ encrypt ข้อมูลที่ต้องการ แต่วันนี้เราจะลองใช้ Generative Model มาช่วยเขียน&lt;/p&gt;

&lt;p&gt;ผลที่ได้ในแต่ละ Step อาจมีความต่างกันบ้าง เช่น Logic ที่ไม่ถูก อันนี้ต้องดูแล้วปรับเฉพาะจุด&lt;/p&gt;

&lt;p&gt;และความต่างเรื่องการตั้งชื่อตัวแปร ว่าสอดคล้องกับความหมายมากน้อยแค่ไหน&lt;/p&gt;

&lt;p&gt;อันนี้เป็นผลลัพธ์ที่ได้ เมื่อเรียกไปที่ /customer/1&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%2Fsb0obucdudr6pnzaa4b7.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%2Fsb0obucdudr6pnzaa4b7.png" alt=" " width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;จะพบว่า Account_number และ balance ถูก Encrypted เรียบร้อยแล้ว&lt;/p&gt;

&lt;p&gt;สามารถปรับแต่งให้บางจุด เกิดความไม่ปลอดภัย เพื่อทดสอบการทำ API Security ได้ด้วย&lt;br&gt;
เช่นที่ /customer/3 จะไม่ถูก encrypted&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%2Fuupqzwy9xa7qnm37zc4w.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%2Fuupqzwy9xa7qnm37zc4w.png" alt=" " width="433" height="289"&gt;&lt;/a&gt;&lt;br&gt;
จะพบว่า Account_number และ balance ไม่ได้ถูก Encrypted ไว้&lt;/p&gt;

&lt;p&gt;โดยเบื้องหลัง จะมี Function สำหรับ Encrypt&lt;br&gt;
&lt;strong&gt;การใช้งานจริงจำเป็นต้องปรับเปลี่ยนวิธีการเก็บ Encryption key ให้ปลอดภัย&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# === MOCK AES KEY (32 bytes = AES-256) ===
AES_KEY = b"this_is_a_32_byte_secret_key!!"

def encrypt_aes(data: str) -&amp;gt; str:
    cipher = AES.new(AES_KEY, AES.MODE_GCM)
    ciphertext, tag = cipher.encrypt_and_digest(data.encode())

    encrypted_payload = {
        "nonce": base64.b64encode(cipher.nonce).decode(),
        "ciphertext": base64.b64encode(ciphertext).decode(),
        "tag": base64.b64encode(tag).decode()
    }

    return base64.b64encode(json.dumps(encrypted_payload).encode()).decode()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;โดยใน Function จะมีการเรียก .encrypt_and_digest และ .b64encode ตามลำดับ&lt;/p&gt;

&lt;p&gt;หลังจากเพิ่ม endpoint เขาไป 5-6 ตัว จะได้ไฟล์ล่าสุด main.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from fastapi import FastAPI
from Crypto.Cipher import AES
import base64
import json

app = FastAPI()

# === MOCK AES KEY (32 bytes = AES-256) ===
AES_KEY = b"this_is_a_32_byte_secret_key!!"

def encrypt_aes(data: str) -&amp;gt; str:
    cipher = AES.new(AES_KEY, AES.MODE_GCM)
    ciphertext, tag = cipher.encrypt_and_digest(data.encode())

    encrypted_payload = {
        "nonce": base64.b64encode(cipher.nonce).decode(),
        "ciphertext": base64.b64encode(ciphertext).decode(),
        "tag": base64.b64encode(tag).decode()
    }

    return base64.b64encode(json.dumps(encrypted_payload).encode()).decode()


# =========================
# Customer Endpoints
# =========================

@app.get("/customer/1")
def get_customer_1():
    return {
        "customer_id": 1,
        "full_name": "John Smith",
        "bank_name": "Mock National Bank",
        "account_type": "Savings",
        "account_number_encrypted": encrypt_aes("123-4-56789-0"),
        "balance_encrypted": encrypt_aes("125430.75"),
        "currency": "USD",
        "status": "Active",
        "encryption": "AES-256-GCM"
    }


@app.get("/customer/2")
def get_customer_2():
    return {
        "customer_id": 2,
        "full_name": "Emma Johnson",
        "bank_name": "Mock National Bank",
        "account_type": "Checking",
        "account_number_encrypted": encrypt_aes("987-6-54321-9"),
        "balance_encrypted": encrypt_aes("98750.25"),
        "currency": "USD",
        "status": "Active",
        "encryption": "AES-256-GCM"
    }


# =========================
# Branch Endpoint (NO Encryption)
# =========================

@app.get("/branch")
def get_branches():
    return {
        "bank_name": "Mock National Bank",
        "total_branches": 3,
        "branches": [
            {
                "branch_id": 1,
                "branch_name": "Bangkok Main Branch",
                "address": "123 Silom Road, Bangkok, Thailand",
                "phone": "+66-2-123-4567",
                "status": "Open"
            },
            {
                "branch_id": 2,
                "branch_name": "Chiang Mai Branch",
                "address": "88 Nimmanhaemin Road, Chiang Mai, Thailand",
                "phone": "+66-53-987-654",
                "status": "Open"
            },
            {
                "branch_id": 3,
                "branch_name": "Phuket Branch",
                "address": "55 Patong Beach Road, Phuket, Thailand",
                "phone": "+66-76-111-222",
                "status": "Closed"
            }
        ]
    }


# =========================
# Finance Endpoint (ALL Encrypted)
# =========================

@app.get("/finance")
def get_finance_summary():
    return {
        "bank_name": "Mock National Bank",
        "report_type": "Branch Deposit Summary",
        "currency": "USD",
        "encryption": "AES-256-GCM",
        "branches": [
            {
                "branch_id": 1,
                "branch_name": "Bangkok Main Branch",
                "total_deposit_encrypted": encrypt_aes("12500000.50")
            },
            {
                "branch_id": 2,
                "branch_name": "Chiang Mai Branch",
                "total_deposit_encrypted": encrypt_aes("5420000.75")
            },
            {
                "branch_id": 3,
                "branch_name": "Phuket Branch",
                "total_deposit_encrypted": encrypt_aes("3189000.00")
            }
        ]
    }

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

&lt;/div&gt;



&lt;p&gt;จากนั้นทำการสร้าง docker compose, พร้อม Dockerfile และสร้าง tunnel published ขึ้น https&lt;/p&gt;

&lt;p&gt;สรุปต้นแบบใช้เวลาประมาณ 5 นาที รวมปรับเพิ่ม Endpoints ไม่เกิน 10 นาที ก็ได้ Apps ไว้ทดสอบ API Security 1 ตัว เทียบกับต้องเขียนเองหมด ต้องหาชื่อ library ที่จะใช้ อย่างเร็วก็น่าจะครึ่งชั่วโมง ถือว่าประหยัดเวลาได้ประมาณ 6 เท่านั่นเอง&lt;/p&gt;

&lt;p&gt;ทั้งนี้ เน้นว่าเป็นงานที่เราทำเป็นอยู่แล้ว และสามารถตรวจงานได้ว่าตอบโจทย์ที่เราต้องการไหม หรือก็คือรู้ว่าสิ่งที่ได้มาทำอะไรอยู่นั่นเอง&lt;/p&gt;

</description>
      <category>api</category>
      <category>security</category>
      <category>encrypted</category>
      <category>ai</category>
    </item>
    <item>
      <title>NGINX API Authentication/Authorization</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Tue, 21 Oct 2025 04:42:51 +0000</pubDate>
      <link>https://forem.com/terngr/nginx-api-authenticationauthorization-5hdg</link>
      <guid>https://forem.com/terngr/nginx-api-authenticationauthorization-5hdg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;From the top image: เป็นเครื่องมือวัดอุณหภูมิที่ ノーヴァ新横浜 สกีรีสอร์ทในร่มที่ Shinyokohama เมื่อสัปดาห์ที่แล้วครับ (ต้นเดือนตุลาคม 2025)&lt;br&gt;
ไว้ดับร้อนเรื่องร้อนๆ ถ้านำ NGINX ไปช่วยทำ API Management, API Security, รวมถึง Authentication จากร้อนๆ ก็จะเย็นสบายเลยครับ ขนาดใส่เสื้อกันหนาวแล้วยังเย็นอยู่เลย&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;NGINX ทั้ง Open Source และ Plus สามารถทำ Authentication/Authorization ได้&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%2Fki9zislu9a3utuacu7um.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%2Fki9zislu9a3utuacu7um.png" alt=" " width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในตัวอย่าง จะมีตั้ง Service ชื่อ cat-api ขึ้นมา เพื่อให้บริการ api แบบ unauthenticated&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%2F0hkycjw7q9am4v6i03mb.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%2F0hkycjw7q9am4v6i03mb.png" alt=" " width="654" height="323"&gt;&lt;/a&gt;&lt;br&gt;
จะเห็นได้ว่า เราสามารถเรียกใช้บริการ ถามชื่อแมว ได้ผ่าน /cat ก็จะได้ชื่อแมว โดยไม่ต้อง Authenticate&lt;/p&gt;

&lt;p&gt;เรารัน NGINX ขึ้นมา เพื่อทำหน้าที่ Authentication (ในทางปฏิบัติ สามารถใช้ MAP เพื่อทำ authorization หรือทำ RBAC ได้ด้วย)&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%2F80yf2ey3uke0pjebntem.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%2F80yf2ey3uke0pjebntem.png" alt=" " width="800" height="676"&gt;&lt;/a&gt;&lt;br&gt;
จะเห็นได้ว่า ตอนนี้ถ้าเรียกใช้งานผ่าน NGINX จำเป็นต้อง Provide valid API key เสียก่อน จึงจะใช้บริการ ถามชื่อแมว ได้&lt;/p&gt;

&lt;p&gt;สิ่งที่ต้องทำต่อ ก็คือจำกัดการเข้าใช้บริการ cat-api โดยตรง โดยเราจะ bind port เฉพาะ NGINX ที่ทำหน้าที่ Authentication/Authorization เท่านั้น แล้วให้ NGINX เรียกผ่าน network: f1-net ไปยัง cat-api อีกต่อหนึ่ง&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%2Fq8jh0f9gwdviklvyl8z8.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%2Fq8jh0f9gwdviklvyl8z8.png" alt=" " width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;สิ่งที่ได้ ก็จะทำ Authentication ให้กับ cat-api ได้นั่นเองครับ&lt;/p&gt;

&lt;p&gt;ทั้งนี้ NGINX ยังสามารถใช้ map directive ช่วยทำ Authorization และ RBAC ได้ด้วย,&lt;br&gt;
สำหรับ NGINX Plus จะมีรองรับ JWT, รวมถึง OIDC, SAML ก็รองรับ และเร็วๆ นี้ยังมี Module เฉพาะสำหรับ LDAP ด้วย ทำให้ NGINX คุยกับ LDAP ได้โดยตรงครับ (เดิมต้องผ่าน 3rd party)&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>api</category>
      <category>management</category>
      <category>security</category>
    </item>
    <item>
      <title>NGINX - Native support ACME Protocol</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Tue, 23 Sep 2025 19:10:55 +0000</pubDate>
      <link>https://forem.com/terngr/nginx-native-support-acme-protocol-2d1p</link>
      <guid>https://forem.com/terngr/nginx-native-support-acme-protocol-2d1p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;NGINX ออก Preview Release ของตัว ACME แบบ Native พร้อมใช้แล้ววันนี้ สำหรับ NGINX Plus R35 (Based on NGINX OSS 1.29.0) และ NGINX OSS 1.29.1&lt;br&gt;
เนื่องจากเป็น Preview Release ยังรองรับเฉพาะ  HTTP-01 challenges&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Automated Certificate Management Environment　(ACME) เป็นโปรโตคอลที่ใช้คุยกับ Certificate Authority (CA) หรือบางท่านเรียนคนออก Certificate ที่มาช่วยบอกว่าเว็บไซต์ของเราเป็นเว็บแท้แน่นอน โดย ACME ช่วย Automate กระบวนการ ตั้งแต่ Issuing, Validating, Renewing, Revoking ครบวงจร&lt;/p&gt;

&lt;p&gt;โดยปกติถ้าเรา Configure HTTPS บน NGINX ก็จะต้องใส่ Certificate ด้วย ซึ่งการได้มาซึ่ง Certificate นี้ เราอาจทำเอง หรือใช้เครื่องมือ (เช่น Certbot) ช่วยก็ได้&lt;br&gt;
ตอนนี้ NGINX ได้นำความสามารถนี้มารวมใน NGINX แล้ว ฉะนั้น NGINX ตั้งแต่ NGINX Plus R35 และ NGINX OSS 1.29.1 จะสามารถที่จะขอ Issuing และ Renewing Certificates ได้เอง&lt;/p&gt;

&lt;p&gt;NGINX ใช้ &lt;a href="https://blog.nginx.org/blog/native-support-for-acme-protocol" rel="noopener noreferrer"&gt;Rust SDK&lt;/a&gt; มาช่วย โดยแยกเป็น Module ชื่อ nginx-plus-module-acme และ nginx-module-acme สำหรับ NGINX Plus และ NGINX OSS ตามลำดับ&lt;/p&gt;

&lt;p&gt;การใช้งาน หลังจากติดตั้ง NGINX Plus ตามขั้นตอนปกติ ให้รันติดตั้ง nginx-plus-module-acme ด้วย เช่น&lt;br&gt;
&lt;code&gt;sudo apt install nginx-plus-module-acme&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;สำหรับ NGINX OSS ให้ติดตั้งเป็น&lt;br&gt;
&lt;code&gt;sudo apt install nginx-module-acme&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ซึ่งเป็นขั้นตอนติดตั้ง Module Package ตามปกติของ &lt;a href="https://docs.nginx.com/nginx/admin-guide/dynamic-modules/acme/#full-example" rel="noopener noreferrer"&gt;NGINX Plus&lt;/a&gt; / &lt;a href="https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#repository-contents" rel="noopener noreferrer"&gt;NGINX OSS&lt;/a&gt;&lt;br&gt;
ทั้งนี้ เราจำเป็นต้องเลือกเวอร์ชั่นของ Module ให้ตรงกับเวอร์ชั่นของ NGINX ด้วย ฉะนั้นในวันนี้เนื่องจาก Module เพิ่งออก จึงไม่ค่อยมีปัญหา(แต่มี Diff Version บ้าง) แต่หากนานไปแล้วเวอร์ชั่นห่างกัน ตอนใช้งานก็จำเป็นต้องเช็คความเข้ากันได้ (คือต้อง Version เดียวกัน) นั่นเอง&lt;/p&gt;

&lt;p&gt;หลังจากนั้นให้โหลด Module เข้าใช้งาน โดยเขียน Configuration ที่ชั้นนอกสุดของ Configuration หรือวางไว้บรรทัดแรกของ /etc/nginx/nginx.conf จะง่ายต่อการทำงานเพราะจะเห็นง่ายว่ากำลังใช้ Module อะไรบ้าง&lt;br&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%2Fydpci195hijwu2m5i1eh.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%2Fydpci195hijwu2m5i1eh.png" alt=" " width="674" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Recap การขอ Certificate
&lt;/h1&gt;

&lt;p&gt;ก่อนใช้งาน ขอ Recap การขอ Certificate ว่าเราต้องมีสิทธิ์ใน FQDN และ Resolve name ไปที่ IP ที่กำหนด การทำ Challenge แบบ HTTP-01 challenges นั้น CA จะส่ง Challenge　มายัง IP เพื่อดูว่าได้รับจริงไหม&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%2F7v5yuhaf54avotg6m7fk.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%2F7v5yuhaf54avotg6m7fk.png" alt=" " width="800" height="149"&gt;&lt;/a&gt;&lt;br&gt;
Challenge ที่ได้&lt;/p&gt;

&lt;p&gt;เมื่อ Challenge สำเร็จ ก็จะออก Certificate ให้ พร้อมนำมาวางให้ใน NGINX&lt;/p&gt;

&lt;p&gt;NGIXN มี Future Plan จะมี Challenges แบบอื่นๆ มาเพิ่มครับ: เช่น ALPN, DNS-01 &lt;/p&gt;

&lt;h1&gt;
  
  
  NGINX Configuration
&lt;/h1&gt;

&lt;p&gt;หลังจากเราโหลดโมดูลแล้ว ให้ Configure ดังนี้ ภายใต้ http {} block&lt;/p&gt;

&lt;p&gt;ตั้ง Resolver (บังคับตั้ง) เพื่อให้ Resolve Name ไปยัง Certificate Authority ได้&lt;br&gt;
&lt;code&gt;resolver 8.8.8.8;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ใส่รายละเอียดของ ACME Issuer ที่จะใช้งาน, path ที่จะเก็บ Certificate ที่ขอได้ เพื่อนำไปใช้ต่อ&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%2Fackkk1d7mh2g0alqvc2k.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%2Fackkk1d7mh2g0alqvc2k.png" alt=" " width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในตัวอย่าง ใช้ Letsencrypt ซึ่งมี directory ในการขออยู่ที่&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://acme-v02.api.letsencrypt.org/directory" rel="noopener noreferrer"&gt;https://acme-v02.api.letsencrypt.org/directory&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;และจะเก็บ เรียกใช้ Certificates ที่&lt;br&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%2Fd4xpxlmokkbbi98995ce.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%2Fd4xpxlmokkbbi98995ce.png" alt=" " width="800" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตั้ง acme_shared_zone&lt;br&gt;
&lt;code&gt;acme_shared_zone zone=acme_shared:1M;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ย้อนไปนิดหนึ่ง ตอนส่ง Challenge มา เราต้องเปิด HTTP port 80 รอรับ เพื่อเช็คว่าได้รับข้อความจริงไหม เป็นเจ้าของตัวจริงไหม โดยเปิดรับ port 80 เสมอ เพื่อทำ Challenge (ในอนาคตอาจมีท่าอื่นเพิ่ม)&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%2Fecdq41rlf1e8divv5mte.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%2Fecdq41rlf1e8divv5mte.png" alt=" " width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เมื่อ Challenge สำเร็จ จะนำ Certificate ไปใช้โดยอ้างตัวแปรชื่อ $acme_certificate สำหรับ ssl_certificate และ $acme_certificate_key สำหรับ ssl_certificate_key ตามลำดับ&lt;br&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%2F2y4twfdpxwenluzv23il.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%2F2y4twfdpxwenluzv23il.png" alt=" " width="647" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ยังสามารถดูไฟล์ Certificates จริงได้ด้วย จะเป็น Key pairs ของ Certificate กับ Key นั่นเอง&lt;br&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%2F64qxxlueig2n492n7kju.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%2F64qxxlueig2n492n7kju.png" alt=" " width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตรวจสอบ Configuration และ Restart NGINX ถ้าไม่ได้เกิดปัญหา Error log จะไม่เห็นอะไร แต่จะเห็นการขอ Challenge มาใน Access log&lt;br&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%2F7gmwscbdndh4ozznxrbk.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%2F7gmwscbdndh4ozznxrbk.png" alt=" " width="800" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;หากเกิด Error ให้แก้ไข เช่น Resolve name ติดต่อ CA ไม่ได้ ให้เปลี่ยนที่อยู่ resolver&lt;br&gt;
หรือถ้าทดสอบลบเพื่อขอ Issue ใหม่ ขอเร็วและถี่เกินไปก็จะถูกปฏิเสธไม่ให้ขอได้&lt;/p&gt;

&lt;p&gt;เมื่อเรียบร้อยแล้ว ทดลองเข้าหน้าเว็บด้วย FQDN แบบ https&lt;/p&gt;

&lt;p&gt;Connection is secure&lt;br&gt;
Certificate is valid&lt;br&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%2Flgnjkeywchctwhfbxus1.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%2Flgnjkeywchctwhfbxus1.png" alt=" " width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ระบุว่า Issued By Let's Encrypt&lt;br&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%2Fppzvczjzr3eoic5o3bk5.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%2Fppzvczjzr3eoic5o3bk5.png" alt=" " width="800" height="979"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  สรุป
&lt;/h1&gt;

&lt;p&gt;NGINX ได้ทำ ACME ได้แบบ Native แล้วโดยยังรองรับได้แค่บาง Features ของ Review release&lt;/p&gt;

&lt;p&gt;NGINX ยังมีอีกหลาย &lt;a href="https://docs.nginx.com/nginx/admin-guide/dynamic-modules/acme/" rel="noopener noreferrer"&gt;โมดูล ที่จะมาช่วยเพิ่มประสิทธิภาพงาน&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>native</category>
      <category>acme</category>
      <category>protocol</category>
    </item>
    <item>
      <title>Lost Recovery Keys with Auto Unseal – Vault</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Fri, 19 Sep 2025 23:05:24 +0000</pubDate>
      <link>https://forem.com/terngr/lost-recovery-keys-with-auto-unseal-vault-58ih</link>
      <guid>https://forem.com/terngr/lost-recovery-keys-with-auto-unseal-vault-58ih</guid>
      <description>&lt;p&gt;เป็นหนึ่งจากหลายๆ เคสที่ถูก Raise มาถึงเราในสัปดาห์นี้ และน่าจะเป็นเคสที่สนุกสุดด้วยเพราะผู้เขียนยอมอดกินเนื้อย่างคืนวันศุกร์เพื่อมาแก้เคสนี้ ＃เคสที่ดีมักจะมาเย็นวันศุกร์ ตอนที่กำลังจะกลับบ้าน&lt;/p&gt;

&lt;p&gt;ด้วยตัว HashiCorp Vault ทางทฤษฏี เคสนี้ควรจะถูกแก้ได้ แต่พอไปดูเครื่องมืออาจจะยังไม่ครอบคลุม (หรือจะมองอีกมุม ว่าเพื่อความปลอดภัยและความชัดเจนในมุมการใช้งานของ Product เองก็ได้ครับ) ในที่นี้ wrapped key ที่ต้องการมีพร้อม ขาดแค่เครื่องมือ ซึ่งเราสร้างเพิ่มขึ้นมา&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Vault provides centralized, well-audited privileged access and secret management for mission-critical data whether you deploy systems on-premises, in the cloud, or in a hybrid environment.&lt;br&gt;
With a modular design based around a growing plugin ecosystem, Vault lets you integrate with your existing systems and customize your application workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vault เป็นเครื่องมือช่วยบริหารจัดการ Secrets โดยปกติจะมี 2 Modes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unseal: พร้อมใช้งาน จัดการ Secrets ได้ เรียกขอ Secrets ไปใช้งานก็ได้&lt;/li&gt;
&lt;li&gt;Seal: ไม่พร้อมใช้งาน ต้อง Unseal ก่อน, ถ้ามี Incident เกิดขึ้นเราสามารถสั่ง Seal Vault ได้ทันที เพื่อหยุดยั้งการเข้าถึง Secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;โดยปกติการ Unseal จะใช้ผู้ถือ Key หลายๆ คน เช่น 5 ท่าน แล้วตั้งข้อกำหนดขั้นต่ำ (ที่เกินกึ่งหนึ่ง) เช่นต้องใช้ 3 ท่าน เพื่อจะทำการ Unseal เพื่อให้ Vault พร้อมใช้งาน ซึ่งกระบวนการนี้ หากต้อง Seal/Unseal บ่อยๆ ก็จะไม่สะดวก&lt;br&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%2F6ndatojq0ziq96cd1fh8.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%2F6ndatojq0ziq96cd1fh8.png" alt="Compare 1/2: Unseal ปกติ ที่ใช้ portion สร้าง Unseal key" width="666" height="299"&gt;&lt;/a&gt;&lt;br&gt;
การ &lt;a href="https://developer.hashicorp.com/vault/docs/concepts/seal" rel="noopener noreferrer"&gt;Seal/Unseal&lt;/a&gt; โดยใช้ Shamir portions&lt;/p&gt;

&lt;p&gt;อีกทางเลือกหนึ่ง เราสามารถใช้ Auto Unseal โดยให้หน้าที่ในการปกป้อง Unseal Key ตกไปอยู่ที่ Trusted device / service เช่น AWS Key Management Service (AWS KMS)&lt;br&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%2Fl0npdginyxp344g29ugo.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%2Fl0npdginyxp344g29ugo.png" alt="Compare 2/2: Auto unseal ใช้ Cloud or HSM ปกป้อง unseal key" width="625" height="235"&gt;&lt;/a&gt;&lt;br&gt;
การทำ Auto unseal&lt;/p&gt;

&lt;p&gt;ทั้งนี้ การใช้ Auto unseal จะมีการสร้าง Recovery key ในทำนองเดียวกับการสร้าง Unseal key (แต่ไม่ได้ใช้เพื่อ Unseal) เพื่อให้ครอบคลุมการทำงานบางอย่างนอกเหนือการทำ Auto unseal เช่นการสร้าง root token, rekey&lt;/p&gt;

&lt;p&gt;ฉะนั้น เมื่อเราใช้ Auto unseal แล้วต้องการสร้าง Root token ใหม่ ก็จะต้องมี Recovery key ตามจำนวนที่กำหนด เช่น 3 ท่าน จาก 5 ท่าน&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem
&lt;/h2&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%2F7apyjy23w4e3k6c338hx.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%2F7apyjy23w4e3k6c338hx.png" alt="Auto unseal enabled: Recovery Seal Type และ Sealed: false" width="586" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในระบบ มีการรัน Vault แบบ Auto unseal ไว้แล้ว เช่นใช้ AWS KMS ช่วย&lt;br&gt;
ฉะนั้นสถานะปัจจุบัน จึงเป็น unseal (Sealed = false) หรือพร้อมใช้งานนั่นเอง&lt;/p&gt;

&lt;p&gt;แต่ Recovery key หายไป &lt;a href="https://developer.hashicorp.com/vault/docs/troubleshoot/generate-root-token" rel="noopener noreferrer"&gt;ทำให้ไม่สามารถสร้าง root token ใหม่&lt;/a&gt; และ &lt;a href="https://developer.hashicorp.com/vault/docs/sysadmin/snapshots/save" rel="noopener noreferrer"&gt;ไม่สามารถ Save/Restore snapshot&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%2F929yp8t8uizffs7usf2z.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%2F929yp8t8uizffs7usf2z.png" alt="สร้าง root token ใหม่ จะถามหา Recovery key ซึ่งติดปัญหาสูญหาย/มีไม่ครบ" width="800" height="257"&gt;&lt;/a&gt;&lt;br&gt;
เมื่อพยายามสร้าง root token ใหม่ จะถามหา Recovery key portions ซึ่งติดปัญหาสูญหาย/มีไม่ครบ&lt;/p&gt;

&lt;p&gt;เราตรวจสอบได้ว่าตอนนี้เป็น unseal mode ปกติ (ใช้ Unseal key) หรือเป็น Auto unseal (ใช้ Recovery key) โดยดูจากรูป vault status แสดง Recovery Seal Type ก็คือเป็น Auto unseal นั่นเอง&lt;/p&gt;
&lt;h2&gt;
  
  
  Investigation
&lt;/h2&gt;

&lt;p&gt;หลักของการทำ Shamir's secret sharing (SSS) เริ่มจากเรามี　Secret ที่ต้องการปกป้องกันก่อน&lt;br&gt;
ในที่นี้ก็คือ มี Recovery key แล้วจึงสร้าง Recovery key portion ตามมา เพื่อที่ตอนใช้งานเราจะนำแต่ละ Recovery key portion ทั้งหมดหรือบางส่วน มาประกอบเป็น Recovery key เพื่อได้รับ Authorization จาก Vault operator ให้สร้าง Root token, rekey, etc ได้นั่นเอง ทั้งนี้ Recovery key จะยังใช้ Unseal ไม่ได้(เพราะการ Unseal ต้องใช้ Trusted device /service เช่น AWS KMS โดยท่าที่แนะนำคือเรานำเข้าคีย์เอง เพื่อให้มีสำรองไว้กรณี service ใช้งานไม่ได้ ก็จะนำเข้าคีย์เดิมเข้าไปใหม่ได้)&lt;/p&gt;

&lt;p&gt;จากข้อมูลใน Vault's Document จำเป็นต้องใช้ Recovery key portion และเราทราบว่าสามารถสร้าง Recovery key portion ได้ใหม่จาก Recovery key โดย Recovery key portion ที่สร้างใหม่นี้ อาจไม่ซ้ำกันในแต่ละครั้งก็ได้ แต่เมื่อรวม portion ในจำนวนที่กำหนดแล้วจะได้ Recovery key ฉะนั้นเราต้องหา Recovery key, เช็ค Storage Path ได้จาก Configuration file และเช็คไฟล์ข้างใน พบ _recovery-key&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%2Fdbld0wlse4vubro2mn8m.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%2Fdbld0wlse4vubro2mn8m.png" alt="Vault configuration file แสดง storage ที่ /opt/vault" width="647" height="110"&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%2Finjtr7kkgoowgb3ujpon.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%2Finjtr7kkgoowgb3ujpon.png" alt="พบไฟล์ _recovery-key" width="800" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ก่อนหน้าที่เราจะเช็คไฟล์ มีการตรวจสิทธิ์ของ &lt;a href="https://github.com/hashicorp/vault-guides/blob/master/operations/aws-kms-unseal/terraform-aws/instance-profile.tf" rel="noopener noreferrer"&gt;aws_kms_key&lt;/a&gt; ที่ใช้ พบว่าสามารถทำ Encryption, Decryption, DescribeKey ได้ ซึ่งสิทธิ์พวกนี้เพียงพอต่อการทำ Auto unseal แต่จะถูกใช้กับ Recovery key ด้วยไหม&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%2Fyf7vncv9it56g0da9e8u.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%2Fyf7vncv9it56g0da9e8u.png" alt="สิทธิ์ของ aws_kms_key" width="623" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เรายังรู้อีกว่า HashiCorp มี &lt;a href="https://github.com/hashicorp/go-kms-wrapping/blob/main/wrappers/awskms/awskms.go" rel="noopener noreferrer"&gt;go library&lt;/a&gt; ที่สามารถ Wrap (Encrypt/Decrypt secret) ด้วย aws_kms_key ได้ &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%2F5hzl73sdwe19jffigxh4.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%2F5hzl73sdwe19jffigxh4.png" alt="AWS KMS Wrapper" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ซึ่งการเรียกใช้ wrapper.Decrypt นี้ ต้องมี AWSKMS_WRAPPER_KEY_ID ซึ่งเรามีอยู่แล้ว จะอยู่ใน vault configuration file เป็นคีย์ที่ใช้ทำ Auto unseal นั่นเอง &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%2Fl2jby26yyngpuupu8y7p.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%2Fl2jby26yyngpuupu8y7p.png" alt="AWS KMS Wrapper Key ID" width="800" height="135"&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%2F9v2ptx61tk4505oakpx6.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%2F9v2ptx61tk4505oakpx6.png" alt="AWS_WRAPPER_KEY_ID" width="722" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ส่วนสิ่งที่เราจะ Decrypt ก็คือไฟล์ที่อยู่ใน storage นั่นเอง&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;/opt/vault/core/_recovery-key&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;จากไฟล์ _recovery-key เราใช้ jq ดึงเฉพาะ .Value และ Encode ด้วย Base64&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;sudo cat /opt/vault/core/_recovery-key  | jq -r .Value | base64 -d &amp;gt; key.enc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;จากนั้น go run โดยใส่ parameter ค่าที่ได้จาก _recovery-key, ระบุจำนวน shares และ threshold เพื่อใช้ในการสร้าง portions ของ SSS โดยแต่ละครั้งอาจได้ SSS portion ต่างกัน และยังต้องระบุ AWSKMS_WRAPPER_KEY_ID เพื่อใช้ decrypt&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;go run main.go -enc-key key.enc -env awskms -shamir-shares 5 -shamir-threshold 3&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%2Fscdbbkow7zdw9h8pvr8g.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%2Fscdbbkow7zdw9h8pvr8g.png" alt="จะได้ Recovery keys" width="480" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตอนนี้เราก็สามารถเรียกใช้ vault operator ที่ต้องใช้ Recovery key portion ได้แล้ว&lt;/p&gt;

&lt;p&gt;ทำการ Rekey&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;vault operator rekey \&lt;br&gt;
    -target=recovery \&lt;br&gt;
    -init \&lt;br&gt;
    -key-shares=5 \&lt;br&gt;
    -key-threshold=3&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%2Fa36uq4zvomac5ctlf95g.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%2Fa36uq4zvomac5ctlf95g.png" alt="Rekey สำเร็จ ได้ Recovery Key ใหม่ 5 portions" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ทั้งนี้ หากใช้ Recovery Key portion ชุดใหม่ แต่มาจากการทำ SSS ต่างชุดกัน จะใช้รวมกันสร้าง Recovery Key ไม่ได้&lt;br&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%2Fdbqdd67unkees1gji7wv.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%2Fdbqdd67unkees1gji7wv.png" alt="หากใช้ Recovery Key portion ที่มาจาก SSS คนละชุดกัน จะใช้รวมกันสร้าง Recovery Key ไม่ได้" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เราลองมาสร้าง root token ใหม่ แต่ใช้ Recovery Key portion ชุดเก่า (ก่อน rekey)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sukkarin@ubuntu:~$ vault operator generate-root -init
A One-Time-Password has been generated for you and is shown in the OTP field.
You will need this value to decode the resulting root token, so keep it safe.
Nonce         7d60070e-32c3-a3c0-eaa8-758992802f1a
Started       true
Progress      0/3
Complete      false
OTP           oS2IqxJ06xhPrv5yms6WP7dpwY6B
OTP Length    28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;พบว่าการ rekey สำเร็จ เพราะ Recovery Key portion ชุดเก่า เมื่อประกอบร่างกันแล้วไม่ตรง&lt;br&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%2Fvtq7e6rzricm01cvpuk7.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%2Fvtq7e6rzricm01cvpuk7.png" alt="Recovery Key portion เก่า จะใช้งานไม่ได้ หลัง rekey" width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ทดสอบใช้ Recovery Key ชุดใหม่ สร้าง root token จะได้ Encoded Token มา&lt;br&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%2Fzjg4whupahv8e02b6s28.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%2Fzjg4whupahv8e02b6s28.png" alt="สร้าง Root token ใหม่สำเร็จ" width="735" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;นำ Encoded Token ไป Decode ด้วย NONCE_OTP ที่ได้ตอน init กระบวนการ generate-root&lt;br&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%2Fnuv2afbas13v9557bqsl.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%2Fnuv2afbas13v9557bqsl.png" alt="Decoded new root token" width="800" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ทดสอบ new root token login successfully&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%2F48akii8yf07bcmjjx9a4.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%2F48akii8yf07bcmjjx9a4.png" alt="Login with new root token" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เสร็จจากเคสแล้วลองหาข้อมูลเพิ่ม ในมุมที่เราเห็น Solution แล้ว พบว่าการลงลึกๆยังคงขาดแคลน ขอฝากไว้ต่อยอดการ &lt;a href="https://youtu.be/3M7tX9dTCuc?si=H0BTlPSFlUP4TYxV" rel="noopener noreferrer"&gt;Integrate กับ Kubernetes Secrets&lt;/a&gt; ด้วยครับ&lt;/p&gt;

&lt;p&gt;ตอนที่ทำ wrapper.Decrypt แนะนำเรียกใช้จาก Library เพราะบรรทัดเดียว สะดวกและเห็นผลกระทบสิ่งที่ทำอยู่ได้ชัดเจน&lt;br&gt;
หากไม่อยากเขียนเอง &lt;a href="https://github.com/jdfriedma/vault-recovery-key.git" rel="noopener noreferrer"&gt;มี Community แชร์โค้ดไว้&lt;/a&gt; สำหรับ go run (as your own risk &lt;strong&gt;ควรตรวจสอบโค้ดก่อนรัน&lt;/strong&gt;) และไม่แนะนำให้รัน Binary โดยตรง&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &amp;amp; Future plan
&lt;/h2&gt;

&lt;p&gt;ด้วยหลักการของ Shamir's secret sharing (SSS) ถ้าเรามี Secret เราสามารถสร้าง SSS ใหม่ได้หลายชุด ทำให้เราสามารถใช้งาน Vault Operator ที่ Require Recovery Key portion ได้นั่นเอง&lt;/p&gt;

&lt;p&gt;ใน &lt;a href="https://github.com/hashicorp/vault/issues/11244" rel="noopener noreferrer"&gt;Community&lt;/a&gt; มีการพูดถึงประเด็นต้องการให้ Recovery Key สามารถ Unseal ได้ด้วย แต่ในเมื่อปัจจุบันยังไม่เปิดให้ทำได้ จึงแนะนำให้ใช้ Bring your own key เพื่อจะได้มี Key ถูกเก็บสำรองไว้ใช้ทำ Auto unseal ได้ กรณีตัว Auto unseal หลักเกิดปัญหา&lt;/p&gt;

</description>
      <category>hashicorp</category>
      <category>vault</category>
      <category>recoverykey</category>
      <category>wrapping</category>
    </item>
    <item>
      <title>Rotate certificate in NGINX Open Source without restart</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Fri, 29 Sep 2023 15:52:00 +0000</pubDate>
      <link>https://forem.com/terngr/rotate-certificate-in-nginx-open-source-without-restart-256h</link>
      <guid>https://forem.com/terngr/rotate-certificate-in-nginx-open-source-without-restart-256h</guid>
      <description>&lt;p&gt;&lt;em&gt;ตอนนี้ทีมทยอยย้ายเข้า Office ใหม่ตึก OASIS ในรูปเป็นห้องร้อง Karaoke เอ๊ยห้องประชุมขนาดย่อม ไว้ร้อง Karaoke ได้เต็มที่ 8 คน เอ๊ยนั่งทำงานได้เต็มที่ 4 คนครับ&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ท่านที่ใช้งาน NGINX คงจะคุ้นเคยกับการใส่ Certificate บน NGINX เพื่อที่เวลา Client request จะได้สามารถเปิด Connection แบบเข้ารหัสได้ ผลคือทำให้ Client สามารถเชื่อได้ว่าเป็นเว็บไซต์ เช่นเว็บผู้ให้บริการนี้ตัวจริง และยังช่วยเข้ารหัสข้อความที่ส่งไปมาระหว่างกัน&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;โดยปกติ Certificate จะถูกอ่านจากไฟล์(Disk) แล้วนำไปเก็บใน Memory ตอนที่ start NGINX ฉะนั้นการ Establish connection ก็จะเรียกใช้ key pairs ได้อย่างรวดเร็วผ่าน memory ถ้าเว็บมี server_name เดียว และไม่ได้เปลี่ยนคีย์บ่อยๆ หรือเปลี่ยนแบบมี Downtime ได้ การใช้งานแบบปกติก็ยังเพียงพอ&lt;/p&gt;

&lt;p&gt;แต่ถ้าเป็นการที่มี Dynamic name เช่นมีหลายชื่อ มีชื่อเพิ่มเข้าออกบ่อย, หรือกรณีต้องการเปลี่ยน Certificate เป็นตัวใหม่ โดยไม่ให้เกิด Downtime กรณีนี้ เดิมทำได้ทั้งบน NGINX Plus และ NGINX OSS อยู่แล้ว ต่างกันที่วิธีการและ Performance&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;โดย NGINX Plus ใช้ key-value store ในการเก็บ ซึ่งอยู่บน memory และ load ค่าใหม่จาก Disk เข้า Memory ได้หากต้องการ&lt;/li&gt;
&lt;li&gt;NGINX Open Source ใช้วิธีให้อ่านใหม่ทุกครั้ง ซึ่งมีผลกระทบคือเป็นการอ่านจาก Disk ทำให้กระทบกับ Performance กรณีที่ต้อง Establish connection บ่อยๆ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ที่จะเล่าวันนี้มาจาก &lt;a href="https://www.nginx.com/blog/ssl-tls-certificate-rotation-without-restarts-in-nginx-open-source/?utm_medium=owned-social&amp;amp;utm_source=linkedin&amp;amp;utm_campaign=ww-nx_ssap_g&amp;amp;utm_content=bg-"&gt;SSL/TLS Certificate Rotation Without Restarts in NGINX Open Source&lt;/a&gt; โดยเป็นการใช้ความสามารถของ NGINX JavaScript Module มาทดแทน key-value store บน NGINX Plus&lt;/p&gt;

&lt;p&gt;เริ่มจาก ให้ njs เป็นคนเก็บค่า certificate ไว้ใน Memory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     # Sets an njs function for the variable. Returns a value of cert/key
      js_set $dynamic_ssl_cert main.js_cert;
      js_set $dynamic_ssl_key main.js_key;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จากนั้น NGINX นำค่านี้ไปใช้ตามปกติ ก็จะเป็นค่าอ่านค่า Certificate จาก Memory ซึ่งไม่กระทบกับ Performance แล้วนั่นเอง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      # use variable's data
      ssl_certificate data:$dynamic_ssl_cert;
      ssl_certificate_key data:$dynamic_ssl_key;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เมื่อต้องการเปลี่ยนไปใช้ Certificate ใหม่ สามารถใช้ njs ให้ยกเลิกค่าใน Memory และไปอ่านค่าจากไฟล์ใหม่ได้ทันที, เมื่อ NGINX เรียกใช้ ก็จะเรียกใช้งานจาก Memory ได้ต่อเนื่อง&lt;/p&gt;

&lt;p&gt;เบื้องหลังการยกเลิกการใช้งาน Certificate เดิมที่อยู่ใน Memory ให้ไป Load Certificate ใหม่จาก Disk&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   location = /clear {
      js_content main.clear_cache;
      # allow 127.0.0.1;
      # deny all;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;โดยสรุป เราสามารถที่จะใช้ NJS หรือ NGINX JavaScript Module ซึ่งใช้งานได้ง่ายด้วยภาษา JavaScript และคนทำเว็บคุ้นเคยอยู่แล้ว มาช่วยเพิ่มความสามารถอื่นๆ ให้ NGINX ได้ครับ ในทีนี้คือทำ Function Serve certificate จาก Cache พร้อมความสามารถในการ Reload Certificate ใหม่จาก Disk ลง Cache ได้ด้วยครับ __&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>ssl</category>
      <category>opensource</category>
      <category>nginxplus</category>
    </item>
    <item>
      <title>NGINX Plus R30, 1 single diagnostic file, telemetry per worker, and more</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Wed, 16 Aug 2023 18:57:28 +0000</pubDate>
      <link>https://forem.com/terngr/nginx-plus-r30-1-single-diagnostic-file-telemetry-per-worker-and-more-g93</link>
      <guid>https://forem.com/terngr/nginx-plus-r30-1-single-diagnostic-file-telemetry-per-worker-and-more-g93</guid>
      <description>&lt;p&gt;NGINX Plus R30 ถือเป็น Commercial Version ซึ่งมีความสามารถมากกว่า มี Reliability และรองรับ Workload ใน Production ได้ดีกว่า NGINX OSS&lt;/p&gt;

&lt;h1&gt;
  
  
  Diagnostic Package
&lt;/h1&gt;

&lt;p&gt;ฟีเจอร์นี้เข้ามาเสริมเรื่องที่ Subscription จะได้ Support เปิดเคสถามได้, สิ่งที่ตามมาคือ จะทำอย่างไรให้ Support ทำงานได้เร็วขึ้น ได้ข้อมูลเร็วขึ้น และพยายามให้ได้ครบทุกอย่างที่ต้องการในการขอครั้งเดียว&lt;br&gt;
การรัน Script เก็บข้อมูลทุกอย่างจึงเป็นจุดเริ่มต้นครับ แล้ว zip เป็น single diagnostic package ส่งให้ Support&lt;br&gt;
โดยปกติจะมีเครื่องมือ และขั้นตอนช่วย Investigate Diagnostic package ที่ได้ เพื่อระบุปัญหาให้เร็วขึ้น เพราะใช้เครื่องมือทำได้เร็วกว่าทำมือครับ&lt;/p&gt;

&lt;p&gt;สิ่งที่ Diagnostic package เก็บไปนั้น NGINX เคลมว่าพยายามทำ Transparency ให้มากที่สุดโดยเปิดเผยว่ารันอะไรบ้าง เก็บอะไรบ้าง&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NGINX Information - version, configs, process information, third-party modules, logs, and API stats and endpoints&lt;/li&gt;
&lt;li&gt;System information – Host commands (ps, lsof, vmstat, etc.) อันนี้ก็แสดงถึงความ Transparency จาก NGINX&lt;/li&gt;
&lt;li&gt;Service information – systemd, etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.nginx.com/nginx-agent/overview/"&gt;NGINX Agent&lt;/a&gt; – Logs and configs (if present) อันนี้เป็น Agent ที่ทำงานร่วมกับ Management Plane เช่น NGINX Management Suite เพื่อช่วยแก้ Configuration และเก็บ Metrics&lt;/li&gt;
&lt;li&gt;NGINX App Protect – Logs and configs (if present)&lt;/li&gt;
&lt;li&gt;Support package log – Log containing a list of all files collected
&lt;a href="https://nginx.org/download/nginx-supportpkg.sh"&gt;Diagnostic Script&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Connection telemetry per worker
&lt;/h1&gt;

&lt;p&gt;worker ในที่นี้หมายถึง NGINX worker process ครับ&lt;br&gt;
โดยปกติ NGINX จะเริ่มต้นที่ Master Process, จากนั้นสร้าง Worker process มารับ connections โดยจำนวนของ Worker process จะเท่ากับจำนวน CPU Cores โดย Default และปรับตั้งได้&lt;/p&gt;

&lt;p&gt;เดิมจำนวน Connection จะนับรวม แต่ตอนนี้สามารถแยกตาม worker ได้ โดยมี PID กำกับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mqBzJWnX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qv8ceodnt9gdtvbsn88s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mqBzJWnX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qv8ceodnt9gdtvbsn88s.png" alt="Image description" width="691" height="287"&gt;&lt;/a&gt;&lt;br&gt;
จะเห็นรายละเอียด Connections และ Requests&lt;/p&gt;

&lt;p&gt;เห็น Metrics แล้วมีประโยชน์อย่างไร&lt;br&gt;
จะช่วยปรับค่าใน directive &lt;a href="http://nginx.org/en/docs/ngx_core_module.html#worker_connections"&gt;worker_connections&lt;/a&gt; &lt;br&gt;
directive worker_connections จะเป็นจำนวน connection ที่มากที่สุดที่แต่ละ Worker process จะเปิดรับได้ Default คือ 512, แต่ Default NGINX configuration file จะใส่ค่าไว้ที่ 1024, ทั้งนี้การปรับตั้ง worker_connections ค่อนข้างมีความจำเป็นในการรับ Load ปริมาณมากๆ เช่นจะตั้ง worker จำนวนเท่าใด แต่ละ Worker ควรรับ Load ไม่เกินเท่าใด ถึงจะได้ประสิทธิภาพที่ดีที่สุด ซึ่ง telemetry ที่เพิ่มมาจะช่วยได้ครับ&lt;/p&gt;

&lt;p&gt;เรื่องนี้ ยังอยู่ใน &lt;a href="https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#:~:text=The%20worker_connections%20directive%20sets%20the,maximum%2C%20not%20just%20client%20connections."&gt;Avoiding the Top 10 NGINX Configuration Mistakes&lt;/a&gt; อันดับที่ 1 เพราะการนับจำนวน connection จะนับทั้ง request ที่เปิดจาก Client มายัง NGINX, และนับจาก request ที่เปิดจาก NGINX ไปยัง Upstream ฉะนั้นถ้า Client เรียกมา 1 ครั้ง, แต่ NGINX จะต้องเปิด Connection 2 ครั้ง, นั่นก็แสดงว่า ถ้าเราตั้งค่า worker_connections ไว้ที่ 1024 ก็จะรับ requests พร้อมๆ กันได้ไม่เกิน 512 ต่อ 1 worker นั่นเอง&lt;/p&gt;
&lt;h1&gt;
  
  
  deprecation of listen  http2
&lt;/h1&gt;

&lt;p&gt;เป็น directive ที่ deprecate ครับ สามารถแก้ไขได้โดยเปลี่ยนจาก&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listen &amp;lt;port&amp;gt; http2;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เปลี่ยนเป็น&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listen &amp;lt;port&amp;gt;
http2 on;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;โดย http2 จะเป็น off โดย Default&lt;/p&gt;

&lt;p&gt;เรื่องนี้อาจจะนานๆ เห็นครั้งนึง ก็คือ directive depreciation, ผลก็คือถ้าเราอัพเกรด NGINX เป็น version ใหม่ ก็จะทำให้ configuration เดิมใช้ไม่ได้ เป็นจุดนึงที่ต้องเช็คซึ่ง NGINX มี service ช่วยตรวจเช็คให้ครับก่อน upgrade&lt;/p&gt;

&lt;p&gt;เคสนี้ถูก Deprecate ใน NGINX-1.25.1 ครับ release เมื่อ 2023-06-13, และ NGINX Plus R30 based on NGINX-1.25.1 จึง Deprecate การใช้ http2 ตามไปด้วย&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="http://nginx.org/en/docs/http/ngx_http_v2_module.html"&gt;HTTP2 module  &lt;/a&gt;&lt;br&gt;
Ref: &lt;a&gt;NGINX-1.25.1&lt;/a&gt;&lt;br&gt;
Ref: &lt;a href="https://docs.nginx.com/nginx/releases/"&gt;NGINX Plus R30 based on NGINX-1.25.1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;นอกจากข้างต้น ก็ยังมี Features ย่อยๆ, แก้ Bug และ OS บางตัวที่ End of Life ก็จะถูกหยุด Support ไปด้วยครับ &lt;a href="https://www.nginx.com/blog/nginx-plus-r30-released/"&gt;อ่าน Release Note ตัวเต็มได้ที่นี่&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>nginxplus</category>
      <category>r30</category>
      <category>telemetry</category>
    </item>
    <item>
      <title>Recursion in Terraform, with example</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Sat, 12 Aug 2023 14:29:27 +0000</pubDate>
      <link>https://forem.com/terngr/recursion-in-terraform-with-example-5ac2</link>
      <guid>https://forem.com/terngr/recursion-in-terraform-with-example-5ac2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;สัปดาห์ที่แล้วได้พูดถึงการทำ Loop รวมถึงใส่เงื่อนไขในการสร้าง Resources หลายๆ แบบได้โดยใช้ count และ module มาช่วย&lt;br&gt;
คำถามถัดไปคือ Terraform สามารถทำงานในลักษณะ Recursive ได้หรือไม่&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR;
&lt;/h2&gt;

&lt;p&gt;ได้ทดสอบโดยให้เรียก Module ซ้อนกัน(เรียกตัวเอง) พบว่าไม่สามารถเรียกใช้งานได้ จึงสรุปได้ว่าด้วย Terraform ไม่สามารถทำงานในลักษณะแบบ Recursive จริงๆได้(แต่ถ้าเป็นการเรียกซ้อนกัน ที่ไม่ใช่เรียกตัวเอง ยังทำได้ปกตินะครับ แต่เราต้องสร้าง module รองรับไว้)&lt;/p&gt;

&lt;p&gt;ถ้าจบแค่นี้อาจจะสั้นไปนิด งั้นเราลองมาแปลง Recursive ให้เป็น Iterative ก่อน&lt;/p&gt;

&lt;p&gt;ขอทดสอบด้วยโจทย์ Tower of Hanoi ที่โจทย์ให้หา solution ในการเลื่อน Disk ทั้งหมดจากเสา Source(A) ไปยัง Destination(C) โดยมีเสาพัก 1 เสาคือ Temp(B), กติกาคือ เลื่อนได้ครั้งละ 1 Disk เท่านั้น และห้าม Disk ใหญ่วางทับ Disk เล็ก &lt;a href="https://www.mathsisfun.com/games/towerofhanoi.html"&gt;ทดลองเล่นด้วยตัวเอง&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;โดยปกติ Tower of Hanoi ถูก Solved ได้ง่ายๆ โดยใช้ Recursive ในที่นี้เราจะให้ Terraform ช่วยแก้จึงต้อง&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;แปลง Algorithm จาก Recursive เป็น Iterative ก่อน &lt;/li&gt;
&lt;li&gt;จากนั้นนำ Count และ Module มาช่วยในการรัน loop แบบ Iterative
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#main.tf สำหรับใช้ทำ loop ด้านนอก โดยใช้ count
#ค่าของ count ได้มาจากการแปลง Recursive -&amp;gt; Iterative แล้วดูว่าต้อง move ทั้งหมดเท่าใด

locals {
  n=3
  n_total=pow(2,local.n)-1
}

module "my_module" {
  count = local.n_total
  source = "./my_module"
  n = local.n_total
  i = count.index
  A = "A"
  B = local.n%2==0?"C":"B"
  C = local.n%2==0?"B":"C"
}

output "n_total" {
  value = local.n_total
}
output "my_module" {
  value = module.my_module
&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;#my_module/my_module.tf ที่ใช้รันคำสั่งซึ่งเดิมจะรันใน recursive function
#ในที่นี้ก็คือให้ทำหาค่าเสาที่จะทำการ Move
#ค่าที่ส่งให้ output สามารถนำไปใช้ต่อใน main module ที่เป็นผู้เรียก my_module/my_module.tf ได้

variable "n" {}
variable "i" {}
variable "A" {}
variable "B" {}
variable "C" {}

output "move" {
  value = var.i%3==0 ? "move ${var.A} ${var.C}" : var.i%3==1?"move ${var.A} ${var.B}":"move ${var.B} ${var.C}"

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

&lt;/div&gt;



&lt;p&gt;ท่า Iterative นี้ จะบอกได้ว่าต้อง move ระหว่างเสาใด แต่ไม่ได้บอกว่าเสาใดคือต้นทางและปลายทาง ทั้งนี้การ Move จะทำได้เพียบแบบเดียวเท่านั้นเพื่อไม่ให้ผิดเงื่อนไข Disk ใหญ่ห้ามทับ Disk ที่เล็กกว่า&lt;/p&gt;

&lt;p&gt;หากต้องการรายละเอียดที่เจาะจงว่าเสาใดเป็นต้นทาง จะต้องทำ stack(terraform: list) เพิ่มด้วย โดยเก็บข้อมูลของ Disk ที่อยู่ในแต่ละเสา แล้วใช้เงื่อนไขซ้อนเข้าไปเพื่อเปรียบเทียบความใหญ่ของ Disk เพื่อตัดสินใจว่าระหว่างสองเสาที่ต้อง Move เสาใดควรเป็นต้นทาง(เสาที่ Disk บนสุดเล็กกว่า, หรือเสาปลายทางว่างนั่นเอง)&lt;/p&gt;

&lt;p&gt;อีก 1 Use case ที่น่าจะมีประโยชน์คือการทำ directory traversal แบบ recursive, ท่านี้สามารถใช้ command ช่วยรันล่วงหน้าเพื่อสร้างตัวแปรแบบเดียวกับที่ทำใน Tower of Hanoi หรือจะเรียกว่าเป็นการเลี่ยง recursive ก็ได้ครับ&lt;/p&gt;

&lt;p&gt;ช่วงนี้เป็นนิมิตหมายที่ดีที่ทุกคนในทีมต่างมีงานเยอะ เลยขอ Terraform ท่าสวยๆ อีก 1 รอบ, สัปดาห์หน้าคาดว่าจะได้ขึ้นโครงร่าง API Security ส่วนประกอบ with example ขอขอบคุณที่ติดตามครับ&lt;/p&gt;

&lt;p&gt;ผลลัพธ์ที่ได้จาก Terraform - Recursive -Tower of Hanoi&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#n=1
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

my_module = [
  {
    "move" = "move A C"
  },
]
n_total = 1
&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;#n=2
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

my_module = [
  {
    "move" = "move A B"
  },
  {
    "move" = "move A C"
  },
  {
    "move" = "move C B"
  },
]
n_total = 3
&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;#n=3
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

my_module = [
  {
    "move" = "move A C"
  },
  {
    "move" = "move A B"
  },
  {
    "move" = "move B C"
  },
  {
    "move" = "move A C"
  },
  {
    "move" = "move A B"
  },
  {
    "move" = "move B C"
  },
  {
    "move" = "move A C"
  },
]
n_total = 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>terraform</category>
      <category>hashicorp</category>
      <category>recursion</category>
      <category>loop</category>
    </item>
    <item>
      <title>Module in Terraform for conditional use cases(Advanced)</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Fri, 04 Aug 2023 16:13:51 +0000</pubDate>
      <link>https://forem.com/terngr/module-in-terraform-for-conditional-use-casesadvanced-235j</link>
      <guid>https://forem.com/terngr/module-in-terraform-for-conditional-use-casesadvanced-235j</guid>
      <description>&lt;p&gt;มาต่อเรื่อง Terraform อีกซักนิดครับ การใช้เงื่อนไขใน Terraform สามารถทำได้หลายแบบ จริงๆ เน้นท่าประหลาดๆ สำหรับ Use cases ยากๆ ที่หาจากไหนไม่ได้ ถ้าหลักง่ายๆคิดว่าหาจาก Docs ได้&lt;a href="https://google.com"&gt;google.com&lt;/a&gt; ก่อนอื่นขอปูพื้นนิดนึงครับ&lt;/p&gt;

&lt;h2&gt;
  
  
  condition
&lt;/h2&gt;

&lt;p&gt;สามารถกำหนดเงื่อนไข precondition และ/หรือ postcondition ได้&lt;br&gt;
เมื่อไม่ได้เงื่อนไขตามที่กำหนด ก็จะไม่สามารถสร้าง resources เสร็จสมบูรณ์ได้ และผลคือ Terraform apply ไม่สำเร็จ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" "example" {
  instance_type = "t3.micro"
  ami           = data.aws_ami.example.id

  lifecycle {
    # The AMI ID must refer to an AMI that contains an operating system
    # for the `x86_64` architecture.
    precondition {
      condition     = data.aws_ami.example.architecture == "x86_64"
      error_message = "The selected AMI must be for the x86_64 architecture."
    }

    # The EC2 instance must be allocated a public DNS hostname.
    postcondition {
      condition     = self.public_dns != ""
      error_message = "EC2 instance must be in a VPC that has public DNS hostnames enabled."
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จะเห็นได้ว่า การใช้ condition, precondition, postcondition ตัว condition เองจะต้องสำเร็จอย่างเดียวเท่านั้นจึงจะทำงานต่อจนจบได้ ถ้าไม่สำเร็จ/ไม่ผ่าน ก็จะ Error แล้วจบการทำงาน แต่ถ้าเป็นเงื่อนไขที่มีทางเลือกมากกว่าหนึ่งล่ะ&lt;/p&gt;

&lt;h2&gt;
  
  
  ถ้าเป็นการกำหนดค่า จะใช้
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;statement&amp;gt; ? &amp;lt;procedure when true&amp;gt; : &amp;lt; procedure when false&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ตัวอย่างเช่น จะหาค่าตำแหน่งว่าเป็น East หรือ West จากตัวเลข&lt;/p&gt;

&lt;p&gt;location = myinput &amp;lt; 50 ? "East" : "West"&lt;/p&gt;

&lt;p&gt;จะได้ว่า หาก myinput มีค่าน้อยกว่า 50 จริง จะได้ location = "East"&lt;br&gt;
แต่ถ้าเป็นเท็จ ก็คือ myinput มีค่าตั้งแต่ 50 ขึ้นไป จะได้ location = "West" นั่นเอง&lt;/p&gt;

&lt;p&gt;จะเห็นได้ว่า เราสามารถกำหนดค่าที่มีทางเลือกมากกว่า 1 ได้ ไม่จำเป็นต้อง True เสมอไป&lt;/p&gt;

&lt;p&gt;ถ้าต้องการใช้แบบ switch case หรือหลายทางเลือก ก็สามารถซ้อนกันได้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location = n &amp;lt; 26 ? "North" : n&amp;lt;50 ? "East" : West
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resource
&lt;/h2&gt;

&lt;p&gt;ถ้าเราต้องการใส่เงื่อนใน ในการสร้าง Resource ล่ะ ก็คือถ้าตรงเงื่อนไขให้สร้าง ถ้าไม่ตรงเงื่อนไขไม่สร้าง ซึ่งจะไม่เหมือนกับเคส condition ที่ต้องตรงเงื่อนไขเท่านั้น ไม่ตรงไม่ได้&lt;/p&gt;

&lt;p&gt;เคสนี้เราสามารถใช้การ count หรือ for_each ใน resource blog มาช่วยได้&lt;br&gt;
โดยถ้าเงื่อนไขที่กำหนดถูกต้อง ให้ทำการสร้าง resource นี้จำนวน count=1 ก็คือสร้างตามปกตินั่นเอง&lt;br&gt;
แต่ถ้าเงื่อนไขไม่ถูกต้อง ก็ให้ทำการสร้าง resource นี้จำนวน count=0 ก็คือไม่สร้างนั่นเอง&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case 1
&lt;/h2&gt;

&lt;p&gt;ถ้าต้องการสร้าง resource แบบ count และใส่เงื่อนไขด้วย จะทำอย่างไร&lt;br&gt;
เพราะการสร้าง resource แบบมีเงื่อนไขจะต้องใช้ count, และ terraform ไม่ยอมให้ทำ count ซ้อนกัน&lt;/p&gt;

&lt;p&gt;เราจะใช้ความสามารถของ Module มาช่วยโดย&lt;br&gt;
Count=n เพื่อทำการสร้าง Module จำนวน n module&lt;br&gt;
แล้วในแต่ละ Module ค่อยไปสร้าง Resource Blog ตามปกติ ก็จะสามาถใช้ count กับ Resource Blog ในรูปแบบปกติได้ ถ้าตรงเงื่อนไขให้สร้าง ไม่ตรงเงื่อนไขไม่ต้องสร้างได้นั่นเอง&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case 2
&lt;/h2&gt;

&lt;p&gt;Resource ที่มีการสร้าง objects ข้างในได้จำนวนเท่ากับ 1 หรือมากกว่า 1&lt;br&gt;
เช่น VM ที่มี Disk 1 หรือ มากกว่า 1 ลูก&lt;br&gt;
VM ที่ Attach Network Interface Card 1 หรือมากกว่า 1 ใบ&lt;br&gt;
เราสามารถสั่งให้ Terraform Resource สร้าง Disk ลูกที่ 2 ตามเงื่อนไขได้ไหม(เช่น ถ้ามีการกำหนดขนาด Disk &amp;gt;0 จึงค่อยสร้าง)&lt;/p&gt;

&lt;p&gt;จะเห็นได้ว่า ในเคสนี้ การใช้ Count จะเป็นการตัดสินใจสร้าง หรือไม่สร้างในระดับ Resource VM แต่ไม่สามารถตัดสินใจระดับ Object หรือจำนวน Object ใต้ Resource ได้&lt;/p&gt;

&lt;p&gt;เราใช้การออกแบบมาแก้ปัญหา โดยสร้าง Resource มา 2 แบบ&lt;br&gt;
แบบแรก มี Disk ลูกเดียว, กำหนดเงื่อนไขการสร้าง Resource นี้ต่อเมื่อค่าความจุของ Disk 2 &amp;lt;1 โดยใช้ Count เป็นตัวกำหนดเงื่อนไข&lt;br&gt;
แบบที่สอง มี Disk สองลูก, กำหนดเงื่อนไขการสร้าง Resource นี้ต่อเมื่อค่าความจุของ Disk 2 &amp;gt;0 โดยใช้ Count เป็นตัวกำหนดเงื่อนไข&lt;/p&gt;

&lt;h3&gt;
  
  
  สรุป
&lt;/h3&gt;

&lt;p&gt;จะเห็นได้ว่า HCL ถูกออกแบบมาให้ง่ายต่อ Human ในการเข้าใจและใช้งาน แต่ไม่ได้ออกแบบมาในเชิงการ Programming, หากต้องการใส่เงื่อนไขที่ซับซ้อนหน่อยจะต้องออกแรงเพิ่มเป็นครั้งๆ ไป โดยใช้การทำเงื่อนไขที่ HCL มีให้ใช้คือ "? :" และ module&lt;br&gt;
เราอาจใช้งาน module เกือบเป็น function ที่ถูกเรียกมาใช้งานก็ได้ เพราะสามารถไปทำชุดงานอีกชุด(อีก Folder) ได้ และ return ค่าได้ แต่ระวังเรื่องเงื่อนไขการเรียก Module ไม่สามารถกำหนดได้ เหตุผลคือการเรียกโมดูลต้องตายตัว จะเรียกตามเงื่อนไขไม่ได้ ต้องเรียกหรือไม่เรียกเลยเท่านั้น ในทีนี้ก็คือต้องเรียกตลอด แล้วค่อยใส่เงื่อนไขไว้ใต้ module อีกทีนั่นเองครับ&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>module</category>
      <category>if</category>
      <category>condition</category>
    </item>
    <item>
      <title>Transform list to map in Terraform(end)</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Mon, 24 Jul 2023 05:36:30 +0000</pubDate>
      <link>https://forem.com/terngr/transform-list-to-map-in-terraformend-4i89</link>
      <guid>https://forem.com/terngr/transform-list-to-map-in-terraformend-4i89</guid>
      <description>&lt;p&gt;Product ส่วนใหญ่ทำมาเพื่อให้ชีวิตง่ายขึ้น แน่นอนว่า Series นี้ว่าด้วยความง่าย คือให้ผู้ใช้ Terraform ใส่สิ่งที่ต้องการเฉพาะที่จำเป็น แล้วสร้าง Resources ออกมาตามที่ต้องการ&lt;/p&gt;

&lt;p&gt;อ่านตอนที่ 1,2&lt;br&gt;
&lt;a href="https://bit.ly/tf_for_each"&gt;1: ทำไมใช้ for_each แทน count ใน terraform&lt;/a&gt;&lt;br&gt;
&lt;a href="https://bit.ly/tf_var1"&gt;2: Enumerate the Variables in Terraform.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตอนนี้เป็นตอนสุดท้ายครับ จะแปลง List ที่เราได้ Enumerate ออกมา แปลงเป็น map&lt;/p&gt;

&lt;p&gt;การเป็น map จะต้องมี key:value ฉะนั้นการแปลง list เป็น map เราจะต้องระบุ key ให้ได้เสียก่อน&lt;/p&gt;

&lt;p&gt;Key จะต้อง unique ด้วย ก็คือไม่ซ้ำกัน เช่นจะมี user 2 ค่าไม่ได้ ในที่นี้ก็คือจะมีเครื่องที่มีตัว key เดียวกันไม่ได้นั่นเอง&lt;/p&gt;

&lt;p&gt;ในบทความก่อนหน้า(ตามลิงค์ด้านบน) เราได้สร้าง list ของแต่ละเครื่องออกมาแล้ว หนึ่งในนั้นคือ name ซึ่งประกอบไปด้วย Group name รวมกับลำดับใน Group นั้น ซึ่งมีความ Unique จึงสามารถมานำ name ตรงนี้มาใช้เป็น key ให้กับ map ที่จะสร้างได้&lt;/p&gt;

&lt;p&gt;ส่วนถัดมาคือ value, ก็คือ Property ของแต่ละ key หรือแต่ละ Resource นั่นเอง&lt;br&gt;
ในตัวอย่างจะเป็น Property ของ VM ก็คือจะนำค่าจาก property ใน list ก่อนหน้ามาใช้ได้เลยนั่นเอง&lt;/p&gt;

&lt;p&gt;จากตอนก่อนหน้า เราทราบแล้วว่าต้องการสร้าง map, และ map ใช้ {} นั่นเอง&lt;/p&gt;

&lt;p&gt;การสร้าง key:value ใน map ใช้สัญลักษณ์ =&amp;gt;&lt;/p&gt;

&lt;p&gt;ตอนก่อนหน้า เราได้ใส่ index ของ vm ในแต่ละ group แล้ว แต่ยังไม่มี global index สำหรับทุกๆ VM ซึ่งเราจะใส่ในขั้นตอนนี้ โดยใช้เป็น&lt;br&gt;
for indeex, i in local.vm_list&lt;br&gt;
จะได้ว่า i จะเป็น Element แต่ละตัวใน local.vm_list&lt;br&gt;
และ index จะเป็น incremental เริ่มที่ 0, 1, 2 ไปในแต่ละ element i ใน local.vm_list&lt;/p&gt;

&lt;p&gt;นำด้านบนมาประกอบกัน จะทำการสร้าง map ได้ดังนี้ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vm_map = {for index, i in local.vm_list: i.name =&amp;gt; {

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

&lt;/div&gt;



&lt;p&gt;ในเคสนี้ จะใช้ loop ชั้นเดียว เพราะ local.vm_list นั้นถูก Enumerate มาเป็นข้อมูลแต่ละ Resource(VM) ตั้งแต่ขั้นตอนก่อนหน้า(ซึ่งตอนนั้นทำ Loop 2 ชั้น คือ Group และ VM และแต่ละ Group ไปให้แล้ว)&lt;/p&gt;

&lt;p&gt;จากนั้นก็เหลือขั้นตอนการให้ค่า value ที่จะคู่กับ key ที่เป็น name&lt;br&gt;
value ที่ใช้จะเลือกเป็น list หรือเป็น map ในกรณีนี้แต่ละ resource มี Unique key และไม่มีซ้ำ ฉะนั้นจึงควรเลือกเป็น key โดยมี key ดังนี้ name, group, cpu, memory, index, index_all(ค่า Global Index ที่ได้จาก index ที่ใส่เพิ่มมาในบรรทัด for นั่นเอง)&lt;/p&gt;

&lt;p&gt;เมื่อประกอบ loop และการให้ค่าใน value เข้าด้วยกัน จะได้ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  vm_map = {for index, i in local.vm_list: i.name =&amp;gt; {
        name  = i.name
        group = i.group
        cpu   = i.cpu
        memory= i.memory
        index = i.index
        index_all = index+1
  }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ฉะนั้น สามารถสรุปขั้นตอนการแปลงค่าจาก list อย่างง่าย ให้ Enumerate ออกมาเป็น Map ได้ดังนี้&lt;br&gt;
Step 0: สารตั้งตั้น&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vm = tolist([                                                                                                                                                                                                      {                                                                                                                                                                                                                  "count" = 2                                                                                                                                                                                                      "cpu" = 2                                                                                                                                                                                                        "group" = "GropuA"                                                                                                                                                                                               "memory" = 2048                                                                                                                                                                                                },                                                                                                                                                                                                               {                                                                                                                                                                                                                  "count" = 1                                                                                                                                                                                                      "cpu" = 1                                                                                                                                                                                                        "group" = "GroupB"                                                                                                                                                                                               "memory" = 1024                                                                                                                                                                                                },                                                                                                                                                                                                               {                                                                                                                                                                                                                  "count" = 0                                                                                                                                                                                                      "cpu" = 2                                                                                                                                                                                                        "group" = "GroupC"
    "memory" = 2048
  },
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 1:&lt;br&gt;
Enumerate ด้วย loop 2 ชั้น ได้ list ของ VM แต่ละเครื่อง พร้อม Property&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vm_list = tolist([
  {
    "cpu" = 2
    "group" = "GropuA"
    "index" = 1
    "memory" = 2048
    "name" = "GropuA-1"
  },
  {
    "cpu" = 2
    "group" = "GropuA"
    "index" = 2
    "memory" = 2048
    "name" = "GropuA-2"
  },
  {
    "cpu" = 1
    "group" = "GroupB"
    "index" = 1
    "memory" = 1024
    "name" = "GroupB-1"
  },
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2:&lt;br&gt;
แปลง list เป็น map และเพิ่ม Global Index ให้กับแต่ละ VM ฉะนั้นแต่ละ VM จะมีทั้ง Global Index และ local index ของ group ที่ตนเองอยู่นั่นเอง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vm_map = {
  "GropuA-1" = {
    "cpu" = 2
    "group" = "GropuA"
    "index" = 1
    "index_all" = 1
    "memory" = 2048
    "name" = "GropuA-1"
  }
  "GropuA-2" = {
    "cpu" = 2
    "group" = "GropuA"
    "index" = 2
    "index_all" = 2
    "memory" = 2048
    "name" = "GropuA-2"
  }
  "GroupB-1" = {
    "cpu" = 1
    "group" = "GroupB"
    "index" = 1
    "index_all" = 3
    "memory" = 1024
    "name" = "GroupB-1"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>terraform</category>
      <category>list</category>
      <category>map</category>
      <category>foreach</category>
    </item>
    <item>
      <title>Enumerate the Variables in Terraform.</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Sat, 22 Jul 2023 15:10:45 +0000</pubDate>
      <link>https://forem.com/terngr/enumerate-the-variables-in-terraform-5862</link>
      <guid>https://forem.com/terngr/enumerate-the-variables-in-terraform-5862</guid>
      <description>&lt;p&gt;ความเดิมตอนที่แล้ว เราสามารถสร้างชุด VM ที่ซับซ้อนพร้อมกันหลายๆ ชุด โดยระบุแค่ค่าตั้งต้นเพียงเล็กน้อย&lt;br&gt;
&lt;a href="https://dev.to/terngr/thamaimaich-foreach-aethn-count-ain-terraform-1k0n"&gt;ทำไมใช้ for_each แทน count ใน terraform&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในตอนนี้เราจะ Enumerate ค่าใน list สั้นๆตั้งต้น ออกมาเป็นชุดที่แสดงข้อมูล VM แต่ละเครื่องแบบละเอียด เพื่อนำไปใช้ต่อกันครับ&lt;/p&gt;

&lt;p&gt;ตอนนี้อาจจะซับซ้อนหน่อย แต่แสดงให้เห็นว่าเราสามารถเล่นแร่แปรธาตุและทำอะไรๆ กับ Variable บน Terraform ได้มากระดับหนึ่งทีเดียว ซึ่งหากต้องการ Customize code แบบ Programming มากขึ้นก็สามารถไปท่า CDK, SDK integration ได้ด้วย&lt;/p&gt;

&lt;p&gt;เราทราบแล้วว่าการสร้าง Resources ด้วย for_each จะรับค่าจาก map หรือ set of strings เท่านั้น&lt;/p&gt;

&lt;p&gt;สารตั้งต้น ทำให้สั้นเพื่อง่ายต่อการใช้งาน&lt;br&gt;
&lt;a href="https://media.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%2Fxvrtve6kaa9ix1tudt29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fxvrtve6kaa9ix1tudt29.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ปลายทางที่ต้องการ จัดให้อยู่ในรูปแบบ Map โดยแจกแจง Properties ของแต่ละ VM เพื่อใช้ร่วมกับ for_each&lt;br&gt;
&lt;a href="https://media.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%2Flm7c4a236bhgvjv767gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Flm7c4a236bhgvjv767gp.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 0:
&lt;/h2&gt;

&lt;p&gt;สารตั้งต้น ต้องการสร้างเครื่องจำนวน 2 Group ชื่อว่า GroupA และ GroupB&lt;br&gt;
โดย GroupA ให้สร้าง 2 เครื่อง ให้แต่ละเครื่องมี CPU=2 และ memory=2048&lt;br&gt;
ส่วน GroupB ให้สร้าง 1 เครื่อง ให้แต่ละเครื่องมี CPU=1 และ memory=1024&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vm = tolist([                                                                                                                                                                                                      
  {                                                                                                                                                                                                                  
    "group" = "GroupA"                                                                                                                                                                                               
    "count" = 2                                                                                                                                                                                                      
    "cpu" = 2                                                                                                                                                                                                        
    "memory" = 2048                                                                                                                                                                                                
  },                                                                                                                                                                                                               
  {                                                                                                                                                                                                                  
    "group" = "GroupB"                                                                                                                                                                                               
    "count" = 1                                                                                                                                                                                                      
    "cpu" = 1                                                                                                                                                                                                        
    "memory" = 1024                                                                                                                                                                                                
  },                                                                                                                                                                                                               
  {                                                                                                                                                                                                                  
    "group" = "GroupC"                                                                                                                                                                                               
    "count" = 0                                                                                                                                                                                                      
    "cpu" = 2                                                                                                                                                                                                        
    "memory" = 2048                                                                                                                                                                                                
  },                                                                                                                                                                                                             ])                    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1:
&lt;/h2&gt;

&lt;p&gt;ใช้ for loop 2 ชั้น&lt;br&gt;
โดย loop ชั้นแรก จะกวาดตามจำนวน Element ใน var.vm ซึ่งในที่นี้ก็คือจะกวาดตามจำนวน Group นั่นเอง ซึ่งในที่มี 3 Group คือ "GroupA", "GroupB", "GroupC"&lt;/p&gt;

&lt;p&gt;loop ชั้นที่สอง จะกวาดตามจำนวนตัวแปร count ที่อยู่ใต้ element แต่ละตัวใน loop ชั้นแรก ซึ่งอ้างถึงตัวแปร count ได้จากตัวแปร i_name.count&lt;br&gt;
ฉะนั้นในชั้นนี้ จะวิ่งตามจำนวนเครื่องที่ต้องการในแต่ละ Group นั่นเอง โดย&lt;br&gt;
GroupA มีค่า Count=2 ก็จะมีการทำซ้ำ(loop) 2 รอบ,&lt;br&gt;
GroupB มีค่า Count=1 ก็จะมีการทำซ้ำ(loop) 1 รอบ,&lt;br&gt;
GroupA มีค่า Count=0 ก็จะมีการทำซ้ำ(loop) 0 รอบ&lt;/p&gt;

&lt;p&gt;เมื่อรัน loop ชั้นแรกและชั้นที่สองเสร็จ จะได้การทำซ้ำ(loop) ทั้งหมด 3 รอบ เท่ากับจำนวนเครื่องที่ต้องการพอดี&lt;/p&gt;

&lt;p&gt;หากปรับตัวเลข เป็นจำนวนที่ซับซ้อนขึ้น ก็จะได้การทำซ้ำที่มากขึ้นตาม เช่น GroupA Count=7, GroupB count=20, GroupC count=5 ก็จะได้จำนวนเครื่องทั้งหมด 32 เครื่องนั่นเอง&lt;/p&gt;

&lt;p&gt;การทำ loop 2 ชั้น ตามจำนวน Group และตามค่าของ count ในแต่ละ Group&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    for i_index, i_name in var.vm : [
      for j_index in range(0, i_name.count) : {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2:
&lt;/h2&gt;

&lt;p&gt;สิ่งที่ทำในแต่ละรอบ การสร้างตัวแปรค่าใหม่ขึ้นมาจากค่าตัวแปรเดิม&lt;br&gt;
จาก Step 1 เราได้สร้าง Loop 2 ชั้น เพื่อให้ทำซ้ำจำนวนรอบเท่ากับจำนวนเครื่องที่ต้องการได้แล้ว&lt;/p&gt;

&lt;p&gt;ขั้นนี้ เราจะต้องทำสิ่งที่ต้องทำในแต่ละรอบ นั่นก็คือการกำหนดค่าให้กับ VM ในแต่ละรอบนั่นเอง&lt;/p&gt;

&lt;p&gt;ตัวอย่างเช่น Loopชั้นแรกของ GroupA, Loopชั้นที่สอง ของ count=0 จะสร้าง VM สำหรับ GroupA โดยมี Index ของ GroupA เป็น count+1=1, และกำหนดค่า CPU, Memory ตาม GroupA&lt;/p&gt;

&lt;p&gt;จากนั้น จะเป็น Loopชั้นแรกของ GroupA, แต่ Loop ชั้นที่สอง ของ count=1&lt;/p&gt;

&lt;p&gt;ส่วนใน Loop ของ Group B ก็ทำเช่นเดียวกัน โดย Loop ชั้นแรกคือ GroupB, และ Loop ชั้นที่สองจะทำตามจำนวนของ count ซึ่งครั้งแรกคือ count=0 ก็จะสร้าง VM สำหรับ GroupB โดยมี Index ของ GroupB เป็น count+1=1, และกำหนดค่า CPU, Memory ตาม GroupB&lt;/p&gt;

&lt;p&gt;การกำหนดค่าให้กับ loop แต่ละรอบ ตามค่า Group และค่า count&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        name  = "${i_name.group}-${j_index+1}"
        group = i_name.group
        cpu   = i_name.cpu
        memory= i_name.memory
        index = j_index+1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3:
&lt;/h2&gt;

&lt;p&gt;ประกอบ Step 1 และ 2 เข้าด้วยกัน&lt;br&gt;
การประกอบ มีสิ่งที่ต้องทราบก่อนคือ&lt;br&gt;
การใช้ (), [], {}&lt;br&gt;
() ใช้ต่อท้าย Function เช่น flatten()&lt;br&gt;
[] ใช้กับ list&lt;br&gt;
{} ใช้กับ map&lt;/p&gt;

&lt;p&gt;จากตัวแปรตั้งต้น เป็น list ของแต่ละ Group เพื่อให้เพิ่ม list ได้จำนวนมาก หรือเพิ่ม Group ได้จำนวนมากนั่นเอง ขั้นนี้ใช้ [], และเมื่อเข้าไปในแต่ละ Group จะเป็น map เพราะมี key: value ที่แน่นอน ก็คือ group, count, CPU, Memory ขั้นนี้ใช้ {}&lt;/p&gt;

&lt;p&gt;ทั้งหมดที่ได้มา เนื่องจากเป็น map ซ้อนอยู่ใต้ชั้นของ list อีกที จึงนำมา Flatten ด้วย Function flatten ซึ่งใช้ () นั่นเอง&lt;/p&gt;

&lt;p&gt;ชั้นนอกสุดจะเป็น function distinct() ทำการเช็คว่ามีค่าซ้ำไหม หากมีค่าใน list 2 ตัวที่ซ้ำกันก็จะนำออก&lt;br&gt;
นำข้างต้นมารวมกัน จะได้เป็นการใช้งาน loop 2 ชั้น กับ list of map&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  vm_list = distinct(flatten([
    for i_index, i_name in var.vm : [
      for j_index in range(0, i_name.count) : {
        name  = "${i_name.group}-${j_index+1}"
        group = i_name.group
        cpu   = i_name.cpu
        memory= i_name.memory
        index = j_index+1
      }
    ]
  ]))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;สำหรับในบรรทัด    for i_index, i_name in var.vm : [&lt;br&gt;
โดยปกติจะใช้ i_name เพื่อแทน element แต่ละตัวใน var.vm ซึ่งในที่นี้ก็คือแต่ละ Group นั่นเอง&lt;br&gt;
แต่ครั้งนี้มีการใส่ i_index เพิ่มเข้ามาด้วย โดยจะแทน increment หรือการนับลำดับนั่นเอง ซึ่งสามารถนำ i_index ไปอ้างอิงใช้งานต่อได้หากต้องการใส่ลำดับแบบ Global เข้าไปเพิ่มเติม นอกเหนือจากการใช้ j_index ซึ่งเป็นลำดับเฉพาะแต่ละใน Group เท่านั้น&lt;/p&gt;

&lt;p&gt;ในครั้งนี้เราจะไม่ได้ใส่ค่า i_index เข้าไป เพราะจะมีการใส่ลำดับทั้งหมด(global index) ในขั้นตอนการทำ map ขั้นสุดท้ายเพียงครั้งเดียวนั่นเอง&lt;/p&gt;

&lt;p&gt;ทำการรัน จะได้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vm_list = [
{
    "cpu" = 2
    "group" = "GroupA"
    "index" = 1
    "memory" = 2048
    "name" = "GroupA-1"
  },
  {
    "cpu" = 2
    "group" = "GroupA"
    "index" = 2
    "memory" = 2048
    "name" = "GropuA-2"
  },
  {
    "cpu" = 1
    "group" = "GroupB"
    "index" = 1
    "memory" = 1024
    "name" = "GroupB-1"
  },
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;สรุปในตอนนี้ เราได้ทำการ Enumerate Variable จากชื่อ Group และ จำนวน มาใส่ในตัวแปร list พร้อมทั้งระบุชื่อ Group, ลำดับ, Property ที่ต้องการซึ่งในที่นี้คือ CPU และ Memory&lt;/p&gt;

&lt;p&gt;ตอนถัดไปจะทำการ construct map variable ขึ้นมาใช้งานครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bit.ly/tf_for_each" rel="noopener noreferrer"&gt;1: ทำไมใช้ for_each แทน count ใน terraform&lt;/a&gt;&lt;br&gt;
&lt;a href="https://bit.ly/tf_var1" rel="noopener noreferrer"&gt;2: Enumerate the Variables in Terraform.&lt;/a&gt;&lt;br&gt;
&lt;a href="https://bit.ly/tf_transform" rel="noopener noreferrer"&gt;3: Transform list to map in Terraform&lt;/a&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>variable</category>
      <category>foreach</category>
      <category>loop</category>
    </item>
    <item>
      <title>ทำไมใช้ for_each แทน count ใน terraform</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Fri, 14 Jul 2023 21:32:33 +0000</pubDate>
      <link>https://forem.com/terngr/thamaimaich-foreach-aethn-count-ain-terraform-1k0n</link>
      <guid>https://forem.com/terngr/thamaimaich-foreach-aethn-count-ain-terraform-1k0n</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;ขอออกตัวก่อนว่า for_each และ count ต่างก็มีความเด่นในตัวเองซึ่งต้องเลือกใช้ให้เหมาะสมตาม Use cases ครับ โดย for_each สามารถใช้ในเคสที่ Advanced ได้ด้วย แต่ถ้าเคสธรรมดาๆใช้ count จะเขียนได้เร็วกว่า&lt;/p&gt;

&lt;p&gt;ท่านที่คุ้นกับ Terraform count อยู่แล้ว ข้ามไปอ่าน &lt;strong&gt;for_each&lt;/strong&gt; ได้เลยครับ&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform คือ
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code คือเราสามารถเขียน Infrastructure ที่ต้องการในรูปแบบของ Code(HashiCorp Configuration Language - HCL) จากนั้นใช้ Provider เชื่อมต่อไปยัง Infrastructure ต่างๆ เช่น Cloud เพื่อสั่งการให้สร้าง Infrastructure ตามแบบที่เราระบุไว้ใน Code&lt;/p&gt;

&lt;h2&gt;
  
  
  การสร้าง Resource
&lt;/h2&gt;

&lt;p&gt;เช่นการสร้าง EC2 สามารถทำได้โดยเขียน Block&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" &amp;lt;name&amp;gt; {
  ...
}

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

&lt;/div&gt;



&lt;p&gt;หากต้องการ EC2 แบบเดียวกัน แต่เพิ่มเป็น 5 Instances สามารถเขียน count = 5 ได้เลยโดยไม่ต้องทำ Block resource ใหม่&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" &amp;lt;name&amp;gt; {
  count = 5
  ...
}

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

&lt;/div&gt;



&lt;p&gt;ทำการรัน จะได้ EC2 จำนวน 5 Instances&lt;br&gt;
ดูตัวอย่างการใช้ count ได้ที่ &lt;a href="https://bit.ly/terraform-01"&gt;Create 10 AWS EC2 with Terraform แบบเร็วๆ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ถ้าเราต้องการ EC2 เพิ่มอีก 2 Instances โดยให้มี Atrributes ต่างกัน เช่น&lt;br&gt;
5 Instances แรก  กำหนดให้ instance_type = t2.micro&lt;br&gt;
2 Instances ถัดมา กำหนดให้ instance_type = t2.small&lt;br&gt;
จะพบว่าเราไม่สามารถแก้ count จาก 5 เป็น 7 ได้ เพราะจะได้ instance_type เหมือนกันทั้ง 7 Instances&lt;/p&gt;

&lt;p&gt;เราอาจเพิ่ม Block ขึ้นมาอีก Block หนึ่งก็ได้ โดยระบุจำนวน Instances และ instance_type ให้ต่างกัน ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" &amp;lt;name&amp;gt; {
  count = 5
  instance_type = "t2.micro"
  ...
}

resource "aws_instance" &amp;lt;name2&amp;gt; {
  count = 2
  instance_type = "t2.small"
  ...
}

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

&lt;/div&gt;



&lt;p&gt;หากต้องการสร้าง resources ที่ Attributes ต่างออกไปจากนี้อีก เช่น instance_type, ami, tag, subnet_id, key_name, etc ก็ต้องสร้าง Block ใหม่เพิ่มเรื่อยๆ&lt;/p&gt;

&lt;h2&gt;
  
  
  for_each
&lt;/h2&gt;

&lt;p&gt;ต่างจาก count ที่รับค่าเป็น Integer, โดย for_each รับค่าเป็น set หรือ map โดยจะสร้าง Resources ประเภทนั้นๆ ตามค่าใน set หรือ map&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;HashiCorp: &lt;a href="https://developer.hashicorp.com/terraform/language/meta-arguments/for_each"&gt;The for_each Meta-Argument&lt;/a&gt;&lt;br&gt;
The for_each meta-argument accepts a map or a set of strings, and creates an instance for each item in that map or set. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ในที่นี้ หากเราใช้ set จะไม่รองรับ Use case ที่ต้องการปรับแต่งมากกว่า 1 Attribute จึงต้องใช้ map โดยแต่ละ item ใน map จะเก็บ set ของ Attributes เช่น instance_type, group_name, etc รวมไปถึงจำนวน Instances ที่ต้องการได้อีกด้วย&lt;/p&gt;

&lt;p&gt;** ตัวอย่าง map variable ที่ใช้กับ for_each เพื่อสร้าง&lt;br&gt;
Application จำนวน 3 Instances, instance_type = "t2.micro"&lt;br&gt;
Web         จำนวน 2 Instances, instance_type = "t2.small&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - local_aws_vm_map  = {
      - application-1 = {
          - group     = "application"
          - index     = 1
          - index_all = 1
          - name      = "application-1"
          - type      = "t2.micro"
        }
      - application-2 = {
          - group     = "application"
          - index     = 2
          - index_all = 2
          - name      = "application-2"
          - type      = "t2.micro"
        }
      - application-3 = {
          - group     = "application"
          - index     = 3
          - index_all = 3
          - name      = "application-3"
          - type      = "t2.micro"
        }
      - web-1         = {
          - group     = "web"
          - index     = 1
          - index_all = 4
          - name      = "web-1"
          - type      = "t2.small"
        }
      - web-2         = {
          - group     = "web"
          - index     = 2
          - index_all = 5
          - name      = "web-2"
          - type      = "t2.small"
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;สร้าง EC2 instances ตาม local_aws_vm_map ที่กำหนดได้ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" "aws_vm_map" {
  for_each = local.aws_vm_map
  ami           = var.ubuntu22_ami
  instance_type = each.value.type
  key_name                    = var.key
  subnet_id                   = var.subnet
  associate_public_ip_address = false

  tags = {
    Name    = "${var.prefix}-${each.value.name}"
    Name_without_prefix = each.value.name
    Group   = each.value.group
    Index   = each.value.index
    Index_all = each.value.index_all
    Creator = "Sukkarin"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จะได้&lt;br&gt;
Application จำนวน 3 Instances, instance_type = "t2.micro"&lt;br&gt;
Web         จำนวน 2 Instances, instance_type = "t2.small"&lt;br&gt;
และมี Attributes, เช่น tag Name, Group, Index, Index_all(Index แบบนับรวม EC2 ทุก Group), etc แตกต่างกันไปในแต่ละ Instances หรือก็คือสามารถกำหนด Attribute ได้ตามต้องการนั่นเอง&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YRG6yv4D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9mbwkxdh25b8sezhnhl8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YRG6yv4D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9mbwkxdh25b8sezhnhl8.jpg" alt="Image description" width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;แม้ตอนนี้เราสามารถสร้าง EC2 โดยกำหนด Attributes ได้ตามต้องการ แต่ปัญหาจะมาตกอยู่ที่ variable local_aws_vm_map, กรณีเช่นหากต้องการ Application VM จำนวน 100 Instances จะต้องเขียน item ใน map เพิ่ม 100 items, และแต่ละ item จะต้องระบุ Attributes ของ VM นั้นๆ ด้วย&lt;/p&gt;

&lt;p&gt;Thanks to Terraform ที่เราสามารถแปลงค่าใน Variable ต่างประเภทไปมาได้, Enumerate ค่าจาก Variable หนึ่ง แยกย่อยลงไปในแต่ละ Items ได้ เคสนี้เราสามารถกำหนด Attributes ของ EC2 ที่ต้องการได้ง่ายมากดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws_vm = [
    {
      "group" = "application"
      "count" = 3
      "type"  = "t2.micro"
    },
    {
      "group" = "web"
      "count" = 2
      "type"  = "t2.small"
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;จากนั้นแปลงค่าจาก variable aws_vm ให้เป็น map ชื่อ local_aws_vm_map ซึ่ง Enumerate Attributes ของแต่ละ EC2 Instances ออกมาเรียบร้อยแล้วตามตัวอย่าง variable type map&lt;/p&gt;

&lt;p&gt;ทดสอบ เพิ่ม group database กำหนดให้ count = 1 และ type = "t2.medium" ซึ่งใช้งานได้ทันทีโดยไม่ต้องสร้าง aws_instance block เพิ่มอีก&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws_vm = [
    {
      "group" = "application"
      "count" = 3
      "type"  = "t2.micro"
    },
    {
      "group" = "web"
      "count" = 2
      "type"  = "t2.small"
    },
    {
      "group" = "database"
      "count" = 1
      "type"  = "t2.medium"
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เมื่อทำการแปลงค่าให้ aws_vm ให้เป็น map variable จะได้ดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local_aws_vm_map = {
  "application-1" = {
    "group" = "application"
    "index" = 1
    "index_all" = 1
    "name" = "application-1"
    "type" = "t2.micro"
  }
  "application-2" = {
    "group" = "application"
    "index" = 2
    "index_all" = 2
    "name" = "application-2"
    "type" = "t2.micro"
  }
  "application-3" = {
    "group" = "application"
    "index" = 3
    "index_all" = 3
    "name" = "application-3"
    "type" = "t2.micro"
  }
  "database-1" = {
    "group" = "database"
    "index" = 1
    "index_all" = 6
    "name" = "database-1"
    "type" = "t2.medium"
  }
  "web-1" = {
    "group" = "web"
    "index" = 1
    "index_all" = 4
    "name" = "web-1"
    "type" = "t2.small"
  }
  "web-2" = {
    "group" = "web"
    "index" = 2
    "index_all" = 5
    "name" = "web-2"
    "type" = "t2.small"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;terraform apply จะเห็น EC2 instance ใหม่ชื่อ database ถูกสร้างขึ้นมา โดย type="t2.medium"&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NcwNwsoG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8isjravzfj44i3rwl6z5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NcwNwsoG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8isjravzfj44i3rwl6z5.jpg" alt="Image description" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;การแปลงค่า variable จาก aws_vm ไปที่ map variable local_aws_vm_map เพื่อไม่ให้เนื้อหายาวเกินไปขอขึ้นใหม่อีกตอนครับ เพราะมีเรื่อง Variables, function, loop เกี่ยวข้อง, สำหรับ Code ที่ใช้ในการแปลงค่าเป็นโค้ดของผู้เขียนบทความเองตามนี้ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locals {
  # Create "list" of AWS infrastructure resources
  aws_vm_list = distinct(flatten([
    for i_index, i_name in var.aws_vm : [
      for j_index in range(0, i_name.count) : {
        name  = "${i_name.group}-${j_index+1}"
        group = i_name.group
        type  = i_name.type
        index = j_index+1
        #index_all=??
      }
    ]
  ]))

  # Create "map" of AWS infrastructure resources
  aws_vm_map = {for index, i in local.aws_vm_list: i.name =&amp;gt; {
        name  = i.name
        group = i.group
        type  = i.type
        index = i.index
        index_all = index+1
  }}
}

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;First Publicly Published: July 15, 2023&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://bit.ly/tf_for_each"&gt;1: ทำไมใช้ for_each แทน count ใน terraform&lt;/a&gt;&lt;br&gt;
&lt;a href="https://bit.ly/tf_var1"&gt;2: Enumerate the Variables in Terraform.&lt;/a&gt;&lt;br&gt;
&lt;a href="https://bit.ly/tf_transform"&gt;3: Transform list to map in Terraform&lt;/a&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>foreach</category>
      <category>count</category>
      <category>iac</category>
    </item>
    <item>
      <title>Gateway API คือขั้นกว่าของการทำ Ingress บน Kubernetes</title>
      <dc:creator>terngr</dc:creator>
      <pubDate>Fri, 07 Jul 2023 23:28:45 +0000</pubDate>
      <link>https://forem.com/terngr/gateway-api-khuuekhankwaakhngkaartham-ingress-bn-kubernetes-10nl</link>
      <guid>https://forem.com/terngr/gateway-api-khuuekhankwaakhngkaartham-ingress-bn-kubernetes-10nl</guid>
      <description>&lt;p&gt;&lt;em&gt;Image credit: &lt;a href="https://unsplash.com/photos/_UIVmIBB3JU" rel="noopener noreferrer"&gt;https://unsplash.com/photos/_UIVmIBB3JU&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Kubernetes Gateway API เป็นการทำ Fine-grain แยกจาก Ingress Resource เดิมเพื่อให้ทีม Infra และทีม Dev แยกส่วนบริหารจัดการ ตามความรับผิดชอบได้ดีขึ้น&lt;/p&gt;

&lt;p&gt;บทความนี้เหมาะกับผู้ที่คุ้นเคยกับ Kubernetes และเคยใช้งาน Ingress Controller บน Kubernetes&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes Ingress คืออะไร
&lt;/h2&gt;

&lt;p&gt;Kubernetes เป็นระบบบริหารจัดการ Container, ซึ่ง Container ทำงานอยู่บน Overlay network ไม่สามารถถูกเข้าถึงหรือเรียกใช้งานตรงๆ ได้ จึงต้องมี Ingress มาเป็นตัวกลางเพื่อให้เข้าใช้งาน Applications ที่รันอยู่ใน Kubernetes ได้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fts7ye0sspmhyn6u260um.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fts7ye0sspmhyn6u260um.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.nginx.com/nginx-ingress-controller/intro/how-nginx-ingress-controller-works/" rel="noopener noreferrer"&gt;Ref: NGINX Ingress Controller&lt;/a&gt;&lt;br&gt;
ในภาพจะใช้ NGINX มาทำเป็น NGINX Ingress Controller ครับโดยมีขั้นตอนคือ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;สร้าง NGINX Ingress Controller กล่องสีเขียว เราอาจเทียบได้กับการสร้าง Reverse Proxy ขึ้นมาใช้งาน 1 ชุดก็ได้&lt;/li&gt;
&lt;li&gt;สร้าง Ingress โดยผ่าน Kubernetes API กล่องสีขาว เราอาจเทียบได้กับการ Configure Reverse proxy ให้ listen และส่ง Load ไปยัง Backend(services) ที่ต้องการก็ได้, ให้สังเกตคำว่า Kubernetes API โดย API ในที่นี้หมายถึง Interface ที่รองรับการติดต่อแบบ Application Programming(API = Application Programming Interface) หรือเราสามารถสร้าง Ingress โดยเรียกผ่าน API ได้นั่นเองครับ&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Gateway API ไม่เกี่ยวข้องกับ API Gateway
&lt;/h2&gt;

&lt;p&gt;เรื่องนี้ให้ดูที่คำหลัก โดย API Gateway คำหลักคือ API เป็นการจัดการการเข้าถึง API Services ต่างๆ โดยนำ Gateway มาวางคั่นแล้วบริหารจัดการผ่าน Gateway นั้น จึงเรียกว่า API Gateway สิ่งสำคัญคือเราจะต้องมี API Services เสียก่อน ถึงจะบริหารจัดการได้ ถ้ามีแต่ API Gateway ก็จะไม่มีอะไรให้บริหารจัดการนั่นเอง&lt;/p&gt;

&lt;p&gt;Gateway API คำหลักคือ Gateway ในที่นี้คือทางเข้าใช้งาน ก็คือทางเข้าใช้งาน Applications ที่รันอยู่ใน Kubernetes นั่นเองครับ ส่วน API เป็นส่วนเสริมที่แสดงถึงการเป็น Interface(Application Programming Interface) ว่าเราสามารถสร้างทางเข้า(Gateway) นี้ผ่าน API ได้&lt;/p&gt;

&lt;p&gt;ฉะนั้น API Gateway คือการบริหารจัดการการเข้าใช้งาน API Services ต่างๆ อาจเกี่ยวหรือไม่เกี่ยวกับ Kubernetes ก็ได้&lt;br&gt;
แต่ Gateway API โฟกัสไปที่ทางเข้าไปยัง Application ใน Kubernetes ฉะนั้นจึงเป็นเคสของ Kubernetes เท่านั้น ส่วน Applications ที่รันอยู่ภายใน Kubernetes อาจเป็นหรือไม่เป็น API Services ก็ได้&lt;/p&gt;

&lt;h2&gt;
  
  
  เข้าเนื้อหา ทำไมต้องใช้ Gateway API
&lt;/h2&gt;

&lt;p&gt;จากภาพ NGINX Ingress Controller ตามกล่องสีเขียวด้านบน ทำหน้าที่สองข้อด้วยกันคือ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;รับ requests จากภายนอก การเลือกเปิดรับ Port, Name(FQDN), Policies , TLS termination ซึ่งงานแบบนี้มักเป็นงานของทีม Infra&lt;/li&gt;
&lt;li&gt;ส่งต่อ requests ไปยัง services หลังบ้าน รวมไปถึงการทำ A/B testing, การอัพเกรด App version, Traffic splitting, header manipulating ซึ่งเป็นงานของทีม App&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;จะเห็นได้ว่าทั้งสองงานถูกดูแลด้วยทีม 2 ทีม ซึ่งท่าที่ใช้ทำ Ingress แบบเดิมๆ นั้นจะใช้วิธีการสร้าง Kubernetes Resource ชื่อว่า kind: ingress หรือเรียกได้ว่ามี Kubernetes Resource แค่หนึ่งเดียวที่ทุกทีมต้องมาใช้งานร่วมกัน ทำให้มีการให้สิทธิ์ที่มากเกินจำเป็น หรือหากถูกดูแลโดยทีมเดียว เมื่ออีกทีมต้องการแก้ไขก็ต้องร้องขอ ทำให้เกิดความยุ่งยากและล่าช้า&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Flvvcmasrx4pal6tvjdwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Flvvcmasrx4pal6tvjdwp.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://gateway-api.sigs.k8s.io/" rel="noopener noreferrer"&gt;Ref: Gateway API&lt;/a&gt;&lt;br&gt;
Pain point นี้ถูกแก้ด้วย Kubernetes Resources ตัวใหม่ชื่อว่า gateway และ HTTPRoute โดยทีม Infra สามารถบริหารจัดการ kind: gateway โดยจัดการ Name, Certificates และ Policies ได้ ส่วนทีม App สามารถจัดการ kind: HTTPRoute เช่น Path, การจัดการ App ต่างๆ ได้ด้วยตนเอง โดยเลือกจับคู่กับ gateway ที่มีให้ใช้งานได้&lt;/p&gt;

&lt;p&gt;ตัวอย่างการใช้งานสร้าง kind: gateway สำหรับทีม Infra โดยตั้งชื่อ Gateway และระบุ Name, Port, Policies ที่ต้องการ&lt;br&gt;
&lt;a href="https://media.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%2F11m4fephx5uwswo60nat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F11m4fephx5uwswo60nat.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;การใช้งานสร้าง kind: HTTP Route สำหรับทีม Dev โดยเลือกใช้ Gateway, ระบุ Path  และ Services ที่ต้องการ&lt;br&gt;
&lt;a href="https://media.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%2Fhqtdeygwm9g05s7jqlvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fhqtdeygwm9g05s7jqlvl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ลงไปดูมีการ Implement NGINX Pod แบบเดียวกับท่า Ingress ปกติครับ และสามารถต่อแบบ NodePort และรองรับ Cloud LoadBalancer ของ AWS, Azure, GCP ได้ &lt;br&gt;
&lt;a href="https://github.com/nginxinc/nginx-kubernetes-gateway/blob/main/docs/installation.md" rel="noopener noreferrer"&gt;Ref: NGINX Implementation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.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%2Frjfu6u53phtx6qb78oim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Frjfu6u53phtx6qb78oim.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;หนึ่งในท่าที่ทำได้คือการ Map NodePort ซึ่งทำในลักษณะเดียวกันกับ Ingress ปกติ&lt;br&gt;
&lt;a href="https://media.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%2Fe9qeu9hr8euidbo4hwnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fe9qeu9hr8euidbo4hwnu.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;จะเห็นได้ว่าฝั่ง NGINX ทั้ง NGINX pod และ Services เรียกได้ว่าถอดแบบมาจากการทำ NGINX Ingress Controller ปกติเลย แต่จุดต่างจะอยู่ที่ตัว Kubernetes Resources ที่การทำ Gateway API มี Resources ให้ใช้มากกว่า ทำ Granular ได้ดีกว่า โดย Resources ทั้งหมดที่มีให้ใช้งานคือ GatewayClass, Gateway, HTTPRoute, TCPRoute, Service&lt;/p&gt;

&lt;h2&gt;
  
  
  Gateway API เมื่อนำมาใช้จะต้อง Implement คู่กับ Gateway(เทียบได้กับกล่องสีเขียวในรูปด้านบน) ซึ่งยังไม่พร้อมใช้งานบน Production
&lt;/h2&gt;

&lt;p&gt;ณ ปัจจุบัน NGINX Ingress Controller เดิมๆ มีการใช้งานมากที่สุดเป็นอันดับ 1 อยู่แล้วครับ และมีความน่าเชื่อถือสูงมากใน Production ฉะนั้น NGINX จึงยังคงแนะนำให้ใช้ NGINX Ingress Controller บน Production&lt;/p&gt;

&lt;p&gt;ถ้าต้องการใช้งานแบบแยก Resources เป็น Infra กับ Dev บน Production ยังมีทางเลือก Commercial คือ &lt;a href="https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/" rel="noopener noreferrer"&gt;NGINX Plus Ingress Controller&lt;/a&gt; ซึ่งมี CRD เป็น Custom resources ที่ชื่อ kind: Virtualserver และ kind: Virtualserverroute พร้อมใช้งานบน Production มาก่อนหน้านี้ประมาณ 2-3 ปี&lt;br&gt;
อีกตัวหนึ่งที่ GA แล้วคือ Google Kubernetes Engine ครับ&lt;/p&gt;

</description>
      <category>gatewayapi</category>
      <category>nginx</category>
      <category>kubernetes</category>
      <category>ingress</category>
    </item>
  </channel>
</rss>
