<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>han-0315.blog</title>
        <link>https://velog.io/</link>
        <description>초보 데브옵스 엔지니어, 피드백은 언제나 환영입니다.</description>
        <lastBuildDate>Sat, 30 Sep 2023 02:53:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>han-0315.blog</title>
            <url>https://velog.velcdn.com/images/han-0315/profile/9beb1f77-9869-4d04-b288-0ce78b59ec91/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. han-0315.blog. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/han-0315" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Terraform Study #5]]></title>
            <link>https://velog.io/@han-0315/Terraform-Study-5</link>
            <guid>https://velog.io/@han-0315/Terraform-Study-5</guid>
            <pubDate>Sat, 30 Sep 2023 02:53:38 GMT</pubDate>
            <description><![CDATA[<h1 id="5주차">5주차</h1>
<aside>
💡  ‘테라폼으로 시작하는 IaC’ 책으로 진행하는 Terraform 스터디[T101] 5주차 정리내용입니다.

</aside>

<h2 id="워크플로">워크플로</h2>
<h3 id="terraform_remote_state">Terraform_remote_state</h3>
<p>데이터 소스 중 하나로, 다른 Terraform state 파일의 값을 참조할 수 있게 해주는 기능이다. 한 Terraform 프로젝트의 출력 변수를 다른 Terraform 프로젝트에서 읽어올 수 있으며, 이를 통해 다양한 프로젝트나 환경 간의 종속성을 관리할 수 있게 된다.</p>
<p>여기서 network 코드와 ec2의 코드를 분리한 뒤, 각각 terraform cloud에 올리고 이를 terraform_remote_state을 통해 연결하는 것을 테스트해본다.</p>
<ul>
<li><p>network</p>
<ul>
<li><p>backend.tf</p>
<pre><code class="language-go">  terraform {
    cloud {
      organization = &quot;kane-org&quot;         # 생성한 ORG 이름 지정
      hostname     = &quot;app.terraform.io&quot; # default

      workspaces {
        name = &quot;network&quot; # 없으면 생성됨
      }
    }
    required_providers {
      aws = {
        source  = &quot;hashicorp/aws&quot;
        version = &quot;&gt;= 4.58&quot;
      }
    }
    required_version = &quot;&gt;= 0.13&quot;
  }</code></pre>
</li>
<li><p>main.tf</p>
<pre><code class="language-go">  locals {
    additional_tags = {
      Terraform   = &quot;true&quot;
      Environment = &quot;Network&quot;
    }
  }

  resource &quot;aws_vpc&quot; &quot;kane_vpc&quot; {
    cidr_block           = &quot;10.10.0.0/16&quot;
    enable_dns_support   = true
    enable_dns_hostnames = true

    tags = {
      Name = &quot;t101-study&quot;
    }
  }

  resource &quot;aws_subnet&quot; &quot;kane_subnet&quot; {
    vpc_id            = aws_vpc.kane_vpc.id
    cidr_block        = &quot;10.10.1.0/24&quot;
    availability_zone = &quot;ap-northeast-2a&quot;
    tags = {
      Name = &quot;t101-subnet&quot;
    }
  }

  resource &quot;aws_internet_gateway&quot; &quot;kane_igw&quot; {
    vpc_id = aws_vpc.kane_vpc.id

    tags = {
      Name = &quot;t101-igw&quot;
    }
  }

  resource &quot;aws_route_table&quot; &quot;kane_rt&quot; {
    vpc_id = aws_vpc.kane_vpc.id

    tags = {
      Name = &quot;t101-rt&quot;
    }
  }

  resource &quot;aws_route_table_association&quot; &quot;kane_rtassociation1&quot; {
    subnet_id      = aws_subnet.kane_subnet.id
    route_table_id = aws_route_table.kane_rt.id
  }

  resource &quot;aws_route&quot; &quot;kane_defaultroute&quot; {
    route_table_id         = aws_route_table.kane_rt.id
    destination_cidr_block = &quot;0.0.0.0/0&quot;
    gateway_id             = aws_internet_gateway.kane_igw.id
  }

  resource &quot;aws_security_group&quot; &quot;kane_sg&quot; {
    vpc_id      = aws_vpc.kane_vpc.id
    name        = &quot;T101 SG&quot;
    description = &quot;T101 Study SG&quot;
  }

  resource &quot;aws_security_group_rule&quot; &quot;kane_sginbound&quot; {
    type              = &quot;ingress&quot;
    from_port         = 80
    to_port           = 80
    protocol          = &quot;tcp&quot;
    cidr_blocks       = [&quot;0.0.0.0/0&quot;]
    security_group_id = aws_security_group.kane_sg.id
  }

  resource &quot;aws_security_group_rule&quot; &quot;kane_sgoutbound&quot; {
    type              = &quot;egress&quot;
    from_port         = 0
    to_port           = 0
    protocol          = &quot;-1&quot;
    cidr_blocks       = [&quot;0.0.0.0/0&quot;]
    security_group_id = aws_security_group.kane_sg.id
  }</code></pre>
</li>
<li><p>output.tf</p>
<pre><code class="language-go">  output &quot;aws_vpc_id&quot; {
    value = aws_vpc.kane_vpc.id
  }
  output &quot;aws_subnet_id&quot; {
    value = aws_subnet.kane_subnet.id
  }
  output &quot;aws_security_group_id&quot; {
    value = aws_security_group.kane_sg.id
  }</code></pre>
</li>
</ul>
</li>
<li><p>ec2</p>
<ul>
<li><p>backend.tf</p>
<pre><code class="language-go">  terraform {
    cloud {
      organization = &quot;kane-org&quot;         # 생성한 ORG 이름 지정
      hostname     = &quot;app.terraform.io&quot; # default

      workspaces {
        name = &quot;ec2&quot; # 없으면 생성됨
      }
    }
    required_providers {
      aws = {
        source  = &quot;hashicorp/aws&quot;
        version = &quot;&gt;= 4.58&quot;
      }
    }
    required_version = &quot;&gt;= 0.13&quot;
  }</code></pre>
</li>
<li><p>main.tf</p>
<pre><code class="language-go">  locals {
    additional_tags = {
      Terraform   = &quot;true&quot;
      Environment = &quot;EC2&quot;
    }
  }
  data &quot;aws_ami&quot; &quot;amazonlinux2&quot; {
    most_recent = true
    filter {
      name   = &quot;owner-alias&quot;
      values = [&quot;amazon&quot;]
    }

    filter {
      name   = &quot;name&quot;
      values = [&quot;amzn2-ami-hvm-*-x86_64-ebs&quot;]
    }

    owners = [&quot;amazon&quot;]
  }

  data &quot;tfe_outputs&quot; &quot;network&quot; {
    organization = &quot;kane-org&quot;
    workspace    = &quot;network&quot;
  }
  resource &quot;aws_instance&quot; &quot;kane_ec2&quot; {
    ami                         = data.aws_ami.amazonlinux2.id
    associate_public_ip_address = true
    instance_type               = &quot;t2.micro&quot;
    vpc_security_group_ids      = [&quot;${data.tfe_outputs.network.values.aws_security_group_id}&quot;]
    subnet_id                   = data.tfe_outputs.network.values.aws_subnet_id

    user_data_replace_on_change = true
  }</code></pre>
</li>
<li><p>output.tf</p>
<pre><code class="language-go">  output &quot;instance_id&quot; {
    value       = aws_instance.kane_ec2.id
    description = &quot;The ID of the App instance&quot;
  }
  output &quot;instance_public_ip&quot; {
    value       = aws_instance.kane_ec2.public_ip
    description = &quot;The public IP address of the App instance&quot;
  }</code></pre>
</li>
</ul>
</li>
</ul>
<p>먼저 network 모듈을 실행한다. network 모듈을 실행하면 다음과 같이 vpc, subnet, 보안그룹, igw 등이 생성되고 state 파일은 terraform cloud로 업로드된다. </p>
<pre><code class="language-go">...
aws_vpc.kane_vpc: Creating...
aws_vpc.kane_vpc: Still creating... [10s elapsed]
aws_vpc.kane_vpc: Creation complete after 11s [id=vpc-0611bde7af568db76]
aws_internet_gateway.kane_igw: Creating...
aws_subnet.kane_subnet: Creating...
aws_security_group.kane_sg: Creating...
aws_route_table.kane_rt: Creating...
aws_internet_gateway.kane_igw: Creation complete after 0s [id=igw-0a40a4d39738b4bf1]
aws_route_table.kane_rt: Creation complete after 0s [id=rtb-07685b8b451a260ed]
aws_route.kane_defaultroute: Creating...
aws_subnet.kane_subnet: Creation complete after 0s [id=subnet-08e6ad517434f1842]
aws_route_table_association.kane_rtassociation1: Creating...
aws_route_table_association.kane_rtassociation1: Creation complete after 0s [id=rtbassoc-0994211199a244a68]
aws_route.kane_defaultroute: Creation complete after 0s [id=r-rtb-07685b8b451a260ed1080289494]
aws_security_group.kane_sg: Creation complete after 1s [id=sg-0002c365bfa9ad634]
aws_security_group_rule.kane_sgoutbound: Creating...
aws_security_group_rule.kane_sginbound: Creating...
aws_security_group_rule.kane_sgoutbound: Creation complete after 0s [id=sgrule-1882620294]
aws_security_group_rule.kane_sginbound: Creation complete after 1s [id=sgrule-1795632479]

Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

aws_security_group_id = &quot;sg-0002c365bfa9ad634&quot;
aws_subnet_id = &quot;subnet-08e6ad517434f1842&quot;
aws_vpc_id = &quot;vpc-0611bde7af568db76&quot;</code></pre>
<p>이후 ec2 모듈을 실행한다. 아래의 코드를 통해 위에서 실행된 network 모듈의 state 파일을 읽어 참조할 수 있다. </p>
<pre><code class="language-go">data &quot;tfe_outputs&quot; &quot;network&quot; {
  organization = &quot;kane-org&quot;
  workspace    = &quot;network&quot;
}</code></pre>
<p>데이터소스를 이용하여 EC2의 네트워크 관련 사항이 설정된다. 생성결과를 보면 데이터소스를 통해 가져오는 값들은 sensitive value로 표시되는 것을 확인할 수 있다. </p>
<pre><code class="language-bash">...
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + spot_instance_request_id             = (known after apply)
      + subnet_id                            = (sensitive value)
      + tags_all                             = (known after apply)
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = true
      + vpc_security_group_ids               = (sensitive value)
...
Changes to Outputs:
  + instance_id        = (known after apply)
  + instance_public_ip = (known after apply)
aws_instance.kane_ec2: Creating...
aws_instance.kane_ec2: Still creating... [10s elapsed]
aws_instance.kane_ec2: Still creating... [20s elapsed]
aws_instance.kane_ec2: Still creating... [30s elapsed]
aws_instance.kane_ec2: Still creating... [40s elapsed]
aws_instance.kane_ec2: Creation complete after 41s [id=i-052c74426b547ab75]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

instance_id = &quot;i-052c74426b547ab75&quot;
instance_public_ip = &quot;3.34.94.79&quot;</code></pre>
<p>AWS 콘솔에서 확인해보면 VPC,Subnet 모두 정상적으로 네트워크 모듈에서 생성된 것을 가져왔다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/20e6a38e-56f5-4c24-bd7c-0eafdb1469a2/image.png" alt=""></p>
<h3 id="규모에-따른-워크플로">규모에 따른 워크플로</h3>
<ol>
<li><p>개인: 혼자서 테라폼으로 작업할 때는 기존과 같이 3가지 방식으로 작업한다.</p>
<ol>
<li>write: 테라폼 코드 작성</li>
<li>plan: 리뷰</li>
<li>apply: 프로비저닝, 성공한 경우 VCS에 코드를 병합한다.</li>
</ol>
</li>
<li><p>단일 팀: </p>
<ol>
<li>write: 다른 브랜치 혹은 다른 작업환경에서 혼자 테라폼 코드를 테스트한다.</li>
<li>plan(review): 테스트가 완료되면, plan을 통해 다른 팀원에게 리뷰받는다.</li>
<li>apply(merge): 리뷰가 완료되면 코드를 병합하고 인프라를 프로비저닝한다.</li>
</ol>
</li>
<li><p>여러 개의 팀</p>
<p> 팀 별로 (2)번 과정을 수행한다. 이후 <code>terraform_remote_state</code>를 통해 다른 팀의 state 파일을 참조하여 인프라를 구성한다.</p>
</li>
</ol>
<h3 id="msa">MSA</h3>
<p>리소스가 적다면 모놀리식 방식으로 구성해도 좋지만 유지보수, 운영을 생각하면 프로비저닝 단위별로 분류하는 것이 좋다. 정보는 공유할 수 있지만 각 집합은 독립적으로 실행되며 다른 집합에 영향을 받지 않는 격리된 구조가 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/356c4266-9961-4fd2-b9b8-d5a5cb75290d/image.png" alt=""></p>
<p>출처: <a href="https://medium.com/@dudwls96/terraform-%ED%86%B5%ED%95%9C-iac-infrastructure-as-code-365%EC%9D%BC%EA%B0%84-%EC%9A%B4%EC%98%81-%ED%9B%84%EA%B8%B0-500737e6c1e6">https://medium.com/@dudwls96/terraform-통한-iac-infrastructure-as-code-365일간-운영-후기-500737e6c1e6</a></p>
<h2 id="cicd">CI/CD</h2>
<p>제공해주신 자료를 통해 GitHub Actions 실습을 진행했다.</p>
<p><a href="https://github.com/terraform101/terraform-aws-github-action">https://github.com/terraform101/terraform-aws-github-action</a></p>
<p>actions.yaml 파일의 내용을 요약해보면</p>
<ol>
<li>TerraScan을 통해 스캔 결과를 얻고, 업로드한다.</li>
<li>Terraform 워크플로 실행: 코드를 복사하고, fmt → init → validate → plan → apply 를 진행한다.</li>
</ol>
<pre><code class="language-yaml">name: Terraform DEV

on:
  push:
    branches:
      - main
  pull_request:

env:
  MY_PREFIX: DEV
  TF_VERSION: 1.2.5

jobs:
  SCAN:
    name: SCAN
    runs-on: ubuntu-latest
    # env:
    #   working-directory: terraform
    #   TF_WORKSPACE: my-workspace
    steps:
      # - name: Configure AWS credentials
      #   uses: aws-actions/configure-aws-credentials@v1
      #   with:
      #     aws-region: eu-west-1

      - name: Check out code
        uses: actions/checkout@v3

      - name: Run Terrascan
        id: terrascan
        uses: tenable/terrascan-action@main
        with:
          iac_type: &#39;terraform&#39;
          iac_version: &#39;v14&#39;
          policy_type: &#39;aws&#39;
          only_warn: true
          sarif_upload: true

      - name: Upload SARIF file
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: terrascan.sarif  
  Terraform:
    needs: SCAN
    name: Terraform
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v3

      - uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: $TF_VERSION
          cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

      - name: Terraform Fmt
        id: fmt
        run: terraform fmt -recursive -check
        continue-on-error: true

      - name: Terraform init
        id: init
        run: terraform init -upgrade
        # working-directory: ${{ env.working-directory }}

      - name: Terraform validate
        id: validate
        run: terraform validate -no-color

      - name: Terraform plan
        id: plan
        run: terraform plan -no-color -var=prefix=&quot;$MY_PREFIX&quot;
        # working-directory: ${{ env.working-directory }}
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          TF_LOG: info

      - name: Plan output
        id: output
        uses: actions/github-script@v3
        if: github.event_name == &#39;pull_request&#39;
        env:
          PLAN: &quot;terraform\n${{ steps.plan.outputs.stdout }}&quot;
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
            &lt;details&gt;&lt;summary&gt;Show Plan&lt;/summary&gt;
            \`\`\`hcl
            ${process.env.PLAN}
            \`\`\`
            &lt;/details&gt;
            **Pusher**: @${{ github.actor }}
            **Action**: ${{ github.event_name }}
            `;
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

      - name: Terraform apply
        id: apply
        if: github.ref == &#39;refs/heads/main&#39; &amp;&amp; github.event_name == &#39;push&#39;
        run: terraform apply -auto-approve -var=prefix=&quot;$MY_PREFIX&quot; -input=false
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}</code></pre>
<h3 id="결과">결과</h3>
<p>GitHub Actions, AWS, Terraform Cloud에 모두 정상적으로 반영되었다. Terrascan이 수행되고 코드가 실행돼 프로비저닝되었고, 결과를 AWS 콘솔과 테라폼 클라우드에서 모두 확인할 수 있었다.</p>
<ul>
<li>GitHub Actions</li>
</ul>
<p><img src="https://velog.velcdn.com/images/han-0315/post/d6e256e3-ab87-48ff-aec3-53936a6801a3/image.png" alt=""></p>
<ul>
<li>AWS 콘솔</li>
</ul>
<p><img src="https://velog.velcdn.com/images/han-0315/post/1e95cae7-ec95-4f1b-aabf-3dc3a021908e/image.png" alt=""></p>
<ul>
<li>Terraform Cloud</li>
</ul>
<p><img src="https://velog.velcdn.com/images/han-0315/post/bcda04a8-1ece-4dfb-86a8-9c4b8f62ceb1/image.png" alt=""></p>
<p>이제 로컬에서 <code>terraform plan -destroy -out=destroy.tfplan</code> 명령어를 실행하여 백엔드의 state값을 읽어와 인프라를 제거한다.</p>
<h3 id="도전과제-terrascan-설치-후-직접-검증-테스트-해보기">도전과제: Terrascan 설치 후 직접 검증 테스트 해보기</h3>
<p><a href="https://runterrascan.io/docs/getting-started/">공식사이트</a>에서 직접 설치한 후 테스트를 해본다. 아래는 macOS, 리눅스 전용 설치명령어다.</p>
<pre><code class="language-bash">$ curl -L &quot;$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest | grep -o -E &quot;https://.+?_Darwin_x86_64.tar.gz&quot;)&quot; &gt; terrascan.tar.gz
$ tar -xf terrascan.tar.gz terrascan &amp;&amp; rm terrascan.tar.gz
$ install terrascan /usr/local/bin &amp;&amp; rm terrascan
$ terrascan version
version: v1.18.3</code></pre>
<p>docker image도 제공하고 있어, gitlab 등 다른 플랫폼 파이프라인에 적용할 때 편하게 사용할 수 있다. </p>
<pre><code class="language-bash">docker run --rm tenable/terrascan version</code></pre>
<p>이제 테라폼 디렉터리로 이동하여 명령어를 실행하면, 다음과 같이 검사를 실행할 수 있다.</p>
<pre><code class="language-bash">$ terrascan init
$ terrascan scan
...
Scan Summary -

        File/Folder         :   ...
        IaC Type            :   terraform
        Scanned At          :   2023-09-30 02:36:12.552512 +0000 UTC
        Policies Validated  :   144
        Violated Policies   :   6
        Low                 :   2
        Medium              :   1
        High                :   3</code></pre>
<p>보안 그룹과 관련하여 High이 3개 있다. 3개의 포트를 열었는데 각각 취약점으로 검사되었다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/0116d4af-46b8-444e-a39f-b6bcbb9fa563/image.png" alt=""></p>
<p>scan의 exit 코드는 다음과 같이 총 5개로 구분된다. <a href="https://github.com/tenable/terrascan#step-2-scan">GitHub</a> 참고. Terraform Cloud Run task, CI/CD를 구성할 때 참고하여 코드를 작성해야 한다.</p>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Exit Code</th>
</tr>
</thead>
<tbody><tr>
<td>scan summary has errors and violations</td>
<td>5</td>
</tr>
<tr>
<td>scan summary has errors but no violations</td>
<td>4</td>
</tr>
<tr>
<td>scan summary has violations but no errors</td>
<td>3</td>
</tr>
<tr>
<td>scan summary has no violations or errors</td>
<td>0</td>
</tr>
<tr>
<td>scan command errors out due to invalid inputs</td>
<td>1</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform Study #4]]></title>
            <link>https://velog.io/@han-0315/Terraform-Study-4</link>
            <guid>https://velog.io/@han-0315/Terraform-Study-4</guid>
            <pubDate>Thu, 21 Sep 2023 08:42:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/han-0315/post/3e10dd30-363d-4dce-9959-b69e12d14e3e/image.png" alt="">
💡 ‘테라폼으로 시작하는 IaC’ 책으로 진행하는 Terraform 스터디[T101] 4주차 정리내용입니다.</p>
<h1 id="4주차">4주차</h1>
<p>이번 주차에서는 기본 문법을 넘어, 코드를 구조화하고 협업하는 방법에 대해 배운다. 구체적으로는 module과 state에 대해 학습하며, 협업과 관련된 내용은 5주차에서 더 자세하게 다룬다.</p>
<h2 id="state">State</h2>
<p>아래는 1주차 정리내용이다. 테라폼에서는 State 파일을 Serial을 기준으로 backup 관리한다.</p>
<blockquote>
<p>Terraform의 <strong><code>.tfstate</code></strong> 파일 내의 <strong><code>serial</code></strong> 값은 상태 파일의 버전을 나타내며, 동시성 제어와 데이터 무결성 확인에도 중요한 역할을 합니다. 이 값은 Terraform 명령이 실행될 때마다 자동으로 증가하여, 상태 파일의 최신성과 일관성을 유지합니다. 그렇기에 backup 파일이 현재 state 파일보다 serial 번호가 낮다.</p>
</blockquote>
<p><strong>이론적인 내용</strong></p>
<p>상태 파일은 배포할 때마다 변경되는 프라이빗 API이며, 오직 테라폼 내부에서 사용용도이니 직접 편집하거나 작성해서는 안된다. (파일 내부 데이터를 통해, API를 요청하는 것 같다.)</p>
<p>만약, 테라폼을 통해 협업을 진행해야 한다면 state 파일을 관리해야 한다. 이때는 1주차때 진행했던 원격 백엔드를 사용한다. </p>
<p>팀 단위 운영시 필요한 점은 다음과 같다.</p>
<ul>
<li>state 파일의 공유 스토리지</li>
<li>Locking(한명에 한명씩)</li>
<li>파일 격리(dev, stage 등 환경 별 격리가 필요)</li>
</ul>
<p>아래는 VCS를 사용할 때, 발생하는 문제점이다.</p>
<ul>
<li>VCS: 수동으로 상태파일을 push, pull 해야 하니 휴먼에러가 발생할 수 있다.++ Lock 기능이 없다.</li>
</ul>
<p>결국, 테라폼을 지원하는 원격 백엔드를 사용해야 한다. S3, Terraform Cloud 등이 있다.</p>
<h3 id="관련-실습">관련 실습</h3>
<p>State 파일에는 리소스에 대한 모든 것이 담겨있다. 아래의 실습을 통해, 상태 파일 관리의 중요성을 확인해본다.</p>
<ul>
<li>패스워드 리소스 코드</li>
</ul>
<pre><code class="language-go">resource &quot;random_password&quot; &quot;mypw&quot; {
  length           = 16
  special          = true
  override_special = &quot;!#$%&quot;
}</code></pre>
<p>아래의 명령어를 통해 리소스 생성</p>
<pre><code class="language-bash">terraform init &amp;&amp; terraform plan &amp;&amp; terraform apply -auto-approve</code></pre>
<p>테라폼 명령어를 통해 리소스를 확인하면, 시크릿 정보는 알려주지 않는다.</p>
<pre><code class="language-go">❯ terraform state show random_password.mypw

# random_password.mypw:
resource &quot;random_password&quot; &quot;mypw&quot; {
    bcrypt_hash      = (sensitive value)
    id               = &quot;none&quot;
    length           = 16
    lower            = true
    min_lower        = 0
    min_numeric      = 0
    min_special      = 0
    min_upper        = 0
    number           = true
    numeric          = true
    override_special = &quot;!#$%&quot;
    result           = (sensitive value)
    special          = true
    upper            = true
}</code></pre>
<p>하지만, state 파일을 확인해보면 리소스에 대한 정보가 전부 존재한다. </p>
<pre><code class="language-bash">{
  ...
  &quot;resources&quot;: [
    {
      &quot;mode&quot;: &quot;managed&quot;,
      &quot;type&quot;: &quot;random_password&quot;,
     ...
      &quot;instances&quot;: [
        {
          &quot;schema_version&quot;: 3,
          &quot;attributes&quot;: {
            &quot;bcrypt_hash&quot;: &quot;$2a$10$pLMnmRKSY52ageoVumPlpuP5dyo2GZpomOxo6MsQetO/F28dR2ge2&quot;,
            &quot;id&quot;: &quot;none&quot;,
          ...
            &quot;result&quot;: &quot;CLTOsYB9zWifY9WT&quot;,
            &quot;special&quot;: true,
            &quot;upper&quot;: true
          },
          &quot;sensitive_attributes&quot;: []
        }
      ]
    }
  ],
  &quot;check_results&quot;: null
}</code></pre>
<p>테라폼 콘솔에서도 sensitive value는 보이지 않는다.</p>
<pre><code class="language-bash">echo &quot;random_password.mypw&quot; | terraform console
{
  &quot;bcrypt_hash&quot; = (sensitive value)
  &quot;id&quot; = &quot;none&quot;
  &quot;keepers&quot; = tomap(null) /* of string */
  &quot;length&quot; = 16
  ...
  &quot;override_special&quot; = &quot;!#$%&quot;
  &quot;result&quot; = (sensitive value)
  &quot;special&quot; = true
  &quot;upper&quot; = true
}</code></pre>
<h3 id="tfstate">.tfstate</h3>
<p>Terraform의 <strong><code>.tfstate</code></strong> 파일 내의 <strong><code>serial</code></strong> 값은 상태 파일의 버전을 나타내며, 동시성 제어와 데이터 무결성 확인에도 중요한 역할을 합니다. 이 값은 Terraform 명령이 실행될 때마다 자동으로 증가하여, 상태 파일의 최신성과 일관성을 유지합니다. 그렇기에 backup 파일이 현재 state 파일보다 serial 번호가 낮다.</p>
<table>
<thead>
<tr>
<th>유형</th>
<th>구성 리소스 정의</th>
<th>State 구성 데이터</th>
<th>실제 리소스</th>
<th>기본 예상 동작</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>있음</td>
<td></td>
<td></td>
<td>리소스 생성</td>
</tr>
<tr>
<td>2</td>
<td>있음</td>
<td>있음</td>
<td></td>
<td>리소스 생성</td>
</tr>
<tr>
<td>3</td>
<td>있음</td>
<td>있음</td>
<td>있음</td>
<td>동작 없음</td>
</tr>
<tr>
<td>4</td>
<td></td>
<td>있음</td>
<td>있음</td>
<td>리소스 삭제</td>
</tr>
<tr>
<td>5</td>
<td></td>
<td></td>
<td>있음</td>
<td>동작 없음</td>
</tr>
<tr>
<td>6</td>
<td>있음</td>
<td></td>
<td>있음</td>
<td></td>
</tr>
<tr>
<td>- <code>-refresh=false</code> 옵션을 사용하면, 현재의 state파일과 테라폼코드를 비교하여 그대로 적용한다. 원격 리소스의 실제 상태는 확인하지 않음. 그렇기에 만약 원격리소스가 제거되었어도 다시 생성하지 않는다.</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p><strong>유형6번 실습진행</strong></p>
<ul>
<li>IAM user를 추가하는 리소스 확인</li>
</ul>
<pre><code class="language-go">locals {
  name = &quot;mytest&quot;
}

resource &quot;aws_iam_user&quot; &quot;myiamuser1&quot; {
  name = &quot;${local.name}1&quot;
}

resource &quot;aws_iam_user&quot; &quot;myiamuser2&quot; {
  name = &quot;${local.name}2&quot;
}</code></pre>
<ul>
<li>배포진행</li>
</ul>
<pre><code class="language-bash">terraform apply -auto-approve</code></pre>
<ul>
<li>배포 상태 확인</li>
</ul>
<pre><code class="language-bash">aws iam list-users | jq &#39;.Users[] | .UserName&#39;
&quot;admin&quot;
&quot;mytest1&quot;
&quot;mytest2&quot;</code></pre>
<ul>
<li>tfstate 파일 삭제</li>
</ul>
<pre><code class="language-bash">rm -rf terraform.tfstate*
❯ ls terraform.tfstate*
zsh: no matches found: terraform.tfstate*</code></pre>
<ul>
<li>Plan 명령을 실행하면, 아래와 같이 이미 존재하는 리소스를 파악하지 못하고 새롭게 생성하려고 함.</li>
</ul>
<pre><code class="language-bash">$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_iam_user.myiamuser1 will be created
  + resource &quot;aws_iam_user&quot; &quot;myiamuser1&quot; {
      + arn           = (known after apply)
      + force_destroy = false
      + id            = (known after apply)
      + name          = &quot;mytest1&quot;
      + path          = &quot;/&quot;
      + tags_all      = (known after apply)
      + unique_id     = (known after apply)
    }</code></pre>
<ul>
<li>apply 명령어를 실행하면, <strong>EntityAlreadyExists</strong> 에러 발생</li>
</ul>
<pre><code class="language-bash">$ terraform apply
...
Plan: 2 to add, 0 to change, 0 to destroy.
aws_iam_user.myiamuser2: Creating...
aws_iam_user.myiamuser1: Creating...
╷
│ Error: creating IAM User (mytest1): **EntityAlreadyExists: User with name mytest1 already exists.**
│       status code: 409, request id: e32ae858-e9eb-4c3a-a6ab-d7dba9f8bbd8
│ 
│   with aws_iam_user.myiamuser1,
│   on main.tf line 5, in resource &quot;aws_iam_user&quot; &quot;myiamuser1&quot;:
│    5: resource &quot;aws_iam_user&quot; &quot;myiamuser1&quot; {
│ 
╵</code></pre>
<ul>
<li>이럴때는 import 명령어를 통해 해결할 수 있다, IAM user의 ID는 유저의 이름이므로 아래의 명령어를 실행한다.<ul>
<li><code>terraform import [options] ADDRESS ID</code></li>
</ul>
</li>
</ul>
<pre><code class="language-bash">terraform import aws_iam_user.myiamuser1 mytest1                     
aws_iam_user.myiamuser1: Importing from ID &quot;mytest1&quot;...
aws_iam_user.myiamuser1: Import prepared!
  Prepared aws_iam_user for import
aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.</code></pre>
<ul>
<li>tfstate 확인, myiamuser1의 상태파일이 추가되었다.</li>
</ul>
<pre><code class="language-bash">{
  &quot;version&quot;: 4,
  &quot;terraform_version&quot;: &quot;1.5.6&quot;,
  &quot;serial&quot;: 4,
  &quot;lineage&quot;: &quot;0e52f56e-fe1e-d6e7-acb3-f521a3c2f365&quot;,
  &quot;outputs&quot;: {},
  &quot;resources&quot;: [
    {
      &quot;mode&quot;: &quot;managed&quot;,
      &quot;type&quot;: &quot;aws_iam_user&quot;,
      &quot;name&quot;: &quot;myiamuser1&quot;,
      &quot;provider&quot;: &quot;provider[\&quot;registry.terraform.io/hashicorp/aws\&quot;]&quot;,
      &quot;instances&quot;: [
        {
          &quot;schema_version&quot;: 0,
          &quot;attributes&quot;: {
                        ...
            &quot;id&quot;: &quot;mytest1&quot;,
            &quot;name&quot;: &quot;mytest1&quot;,

          },
          &quot;sensitive_attributes&quot;: [],
...
}</code></pre>
<h3 id="워크스페이스">워크스페이스</h3>
<p>State를 관리하는 논리적인 가상 공간을 워크스페이스라고한다.</p>
<p>개발용 환경, 스테이징 환경, 운영환경은 대부분 가지고 있다. 거의 유사한 환경을 구축한다고 하면 여러 개의 프로젝트를 통해 운영할 수 있지만 동일한 환경을 구성한다면 이는 일관성 유지에 좋지 않다. 테라폼에서는 이를 위해 workspace를 지원한다. 하나의 코드를 기반으로 여러 개의 환경을 구성할 수 있으며  <code>terraform.workspace</code> 변수를 통해 환경마다 리소스를 조정할 수 있다. </p>
<p><strong>vs 아래는 워크스페이스가 아닌 여러 개의 프로젝트로 구성한 모습이다.</strong> </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/c982c2ac-14dc-4fff-a019-805643728fc2/image.png" alt=""></p>
<aside>
💡 설명해주신 워크스페이스의 장단점

<ul>
<li><p><strong>장점</strong></p>
<ul>
<li>하나의 <strong>루트 모듈</strong>에서 다른 환경을 위한 리소스를 동일한 <strong>테라폼 구성</strong>으로 프로비저닝하고 관리</li>
<li>기존 프로비저닝된 환경에 영향을 주지 않고 변경 사항 실험 가능</li>
<li><strong>깃의 브랜치 전략</strong>처럼 동일한 구성에서 서로 다른 리소스 결과 관리 - [참고 : <a href="https://blog.hwahae.co.kr/all/tech/9507">화해 - Git 브랜치 전략 수립을 위한 전문가의 조언들</a>]</li>
</ul>
</li>
<li><p><strong>단점</strong></p>
<ul>
<li><p>State가 동일한 저장소(로컬 또는 백엔드)에 저장되어 State 접근 권한 관리가 불가능(어려움)</p>
</li>
<li><p>모든 환경이 동일한 리소스를 요구하지 않을 수 있으므로 테라폼 구성에 분기 처리가 다수 발생 가능</p>
</li>
<li><p>프로비저닝 대상에 대한 <strong>인증 요소를 완벽히 분리하기 어려움</strong></p>
<p>  → 가장 <strong>큰 단점은 완벽한 격리가 불가능</strong></p>
<p>  ⇒ 해결방안 1. 해결하기 위해 루트 모듈을 별도로 구성하는 디렉터리 기반의 레이아웃을 사용할 수 있다.
  ⇒ 해결방안 2. <strong>Terraform Cloud 환경의 워크스페이스를 활용</strong></p>
</li>
</ul>
</li>
</ul>
</aside>

<h2 id="module">Module</h2>
<p>모듈은 대부분의 프로그래밍 언어에서 쓰이는 라이브러리나 패키지와 역할이 비슷하다.</p>
<p>중복되거나, 자주쓰는 코드를 모듈화해서 편하게 재사용할 수 있다. </p>
<p>모듈 디렉터리 형식은 <code>terraform-&lt;프로바이더 이름&gt;-&lt;모듈 이름&gt;</code> 형식을 제안한다.</p>
<blockquote>
<p>이 형식은 Terraform Cloud, Terraform Enterprise에서도 사용되는 방식으로 </p>
</blockquote>
<p>1) 디렉터리 또는 레지스트리 이름이 테라폼을 위한 것이고, 2) 어떤 프로바이더의 리소스를 포함하고 있으며, 3) 부여된 이름이 무엇인지 판별할 수 있도록 한다.</p>
<blockquote>
</blockquote>
<h3 id="구조">구조</h3>
<p>아래와 같이 루트 모듈에서 자식 모듈을 참조한다. 자식모듈이 라이브러리 역할을 수행하고, 루트 모듈이 main 함수이다. 자식 모듈을 호출할 때, 변수도 맞게 대입한다.</p>
<p align="center">
  <img src="https://velog.velcdn.com/images/han-0315/post/e40ad0fb-7354-493b-ae62-975fe6ef5018/image.png" alt="text" width="3![](https://velog.velcdn.com/images/han-0315/post/b1252151-2465-4ea2-b288-edae797005e6/image.png)
00" />
    출처: https://jloudon.com/cloud/Azure-Policy-as-Code-with-Terraform-Part-1/
</p>

<p>간단한 예시를 확인해보면, 아래의 자식 모듈을 사용한다고 가정해보자. isDB라는 변수의 값을 대입해줘야하고, id와 pw를 출력할 수 있다.</p>
<pre><code class="language-go"># main.tf
resource &quot;random_pet&quot; &quot;name&quot; {
  keepers = {
    ami_id = timestamp()
  }
}

# DB일 경우 Password 생성 규칙을 다르게 반영 
resource &quot;random_password&quot; &quot;password&quot; {
  length           = var.isDB ? 16 : 10
  special          = var.isDB ? true : false
  override_special = &quot;!#$%*?&quot;
}
# variable.tf
variable &quot;isDB&quot; {
  type        = bool
  default     = false
  description = &quot;패스워드 대상의 DB 여부&quot;
}
# output.tf
output &quot;id&quot; {
  value = random_pet.name.id
}

output &quot;pw&quot; {
  value = nonsensitive(random_password.password.result) 
}</code></pre>
<p><strong>루트 모듈</strong></p>
<p>&quot;mypw1&quot;의 경우는 변수를 대입하지 않았으니, <code>isDB</code>의 기본값이 들어간다. 다음과 같이 모듈을 통해 코드를 구조화하고 재사용할 수 있다.</p>
<pre><code class="language-go">module &quot;mypw1&quot; {
  source = &quot;../modules/terraform-random-pwgen&quot;
}

module &quot;mypw2&quot; {
  source = &quot;../modules/terraform-random-pwgen&quot;
  isDB   = true
}

output &quot;mypw1&quot; {
  value  = module.mypw1
}

output &quot;mypw2&quot; {
  value  = module.mypw2
}</code></pre>
<h3 id="프로바이더-정의">프로바이더 정의</h3>
<p>루트모듈에서 프로바이더를 정의하는 것이 좋다. 만약, 자식모듈에서 프로바이더를 정의하면 루트 모듈에 버전이 다르면 오류가 발생하고 모듈에 반목문을 쓸 수 없다.</p>
<p>아래의 module “example”과 같이 루트 모듈에 프로바이더를 선언한다.</p>
<pre><code class="language-go"># The default &quot;aws&quot; configuration is used for AWS resources in the root
# module where no explicit provider instance is selected.
provider &quot;aws&quot; {
  region = &quot;us-west-1&quot;
}

# An alternate configuration is also defined for a different
# region, using the alias &quot;usw2&quot;.
provider &quot;aws&quot; {
  alias  = &quot;usw2&quot;
  region = &quot;us-west-2&quot;
}

# An example child module is instantiated with the alternate configuration,
# so any AWS resources it defines will use the us-west-2 region.
module &quot;example&quot; {
  source    = &quot;./example&quot;
  providers = {
    aws = aws.usw2
  }
}</code></pre>
<p>모듈은 위에서 진행하듯이 로컬파일로 가능하며 테라폼 레지스트리, 깃허브 등에서 가져와서 쓸 수 있다.</p>
<p>ex) Terraform registry에서 가져오기</p>
<pre><code class="language-go">module &quot;consul&quot; {
  source = &quot;hashicorp/consul/aws&quot;
  version = &quot;0.1.0&quot;
}</code></pre>
<h2 id="협업">협업</h2>
<p>S3를 통해, 백엔드를 구성하는 것은 1주차때 진행했다. 여기서는 Terraform Cloud를 이용하여 TFC 백엔드를 구성해본다. 당연히 Lock 기능과 버전관리도 지원한다.</p>
<h3 id="terraform-cloud">Terraform Cloud</h3>
<p>state 관리를 진행하는 TFC는 무상이라고 한다. </p>
<aside>
💡 TFC

<ul>
<li>제공 기능 : 기본 기능 무료, State 히스토리 관리, State lock 기본 제공, State 변경에 대한 비교 기능</li>
<li>Free Plan 업데이트 : 사용자 5명 → 리소스 500개, 보안 기능(SSO, Sentinel/OPA로 Policy 사용) - <a href="https://dev.classmethod.jp/articles/update-terraform-cloud-pricing-plan/">링크</a></aside>


</li>
</ul>
<p><strong>워크스페이스 생성</strong></p>
<ul>
<li><a href="https://app.terraform.io/">https://app.terraform.io/</a> 링크 접속 후 계정 생성</li>
<li>workflow 선택 화면에선 <strong>Create a new organization</strong> 선택</li>
<li>Connect</li>
<li>GitHub와 같은 버전관리 시스템에 연결할거면 VCS를 선택한다.</li>
<li>CLI-driven 선택</li>
</ul>
<p><strong>terraform login을 진행한다.</strong></p>
<pre><code class="language-bash">$ terraform login
[토큰 입력]</code></pre>
<p>토큰 확인</p>
<pre><code class="language-bash">cat ~/.terraform.d/credentials.tfrc.json | jq
{
  &quot;credentials&quot;: {
    &quot;app.terraform.io&quot;: {
      &quot;token&quot;: &quot;YMgr4VM...EWGuw&quot;
    }
  }
}</code></pre>
<p>provider.tf에서 테라폼 클라우드를 정의한다.</p>
<pre><code class="language-bash">terraform {
  cloud {
    organization = &quot;kane-org&quot;         # 생성한 ORG 이름 지정
    hostname     = &quot;app.terraform.io&quot; # default

    workspaces {
      name = &quot;terraform-stduy&quot; # 없으면 생성됨
    }
  }
}</code></pre>
<p>이후, terraform init 명령어를 실행하면 <code>.terraform</code> 디렉터리가 생성되고 안에 상태파일이 생성된다. 아래는 상태파일 세부내용이다.</p>
<pre><code class="language-bash">{
    &quot;version&quot;: 3,
    &quot;serial&quot;: 1,
    ...
    &quot;backend&quot;: {
        &quot;type&quot;: &quot;cloud&quot;,
        &quot;config&quot;: {
            &quot;hostname&quot;: &quot;app.terraform.io&quot;,
            &quot;organization&quot;: &quot;kane-org&quot;,
            &quot;token&quot;: null,
            &quot;workspaces&quot;: {
                &quot;name&quot;: &quot;terraform-stduy&quot;,
                &quot;tags&quot;: null
            }
        },
</code></pre>
<p>이제 init &amp;&amp; plan 명령어를 실행하면, 다음과 같이 클라우드에서 동작한다. [terraform cloud local 설정x]</p>
<blockquote>
<p>설정을 통해, terraform 작업을 모두 로컬에서 돌리고 state 파일만 업로드할 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/han-0315/post/072748bd-aa1d-4a96-9dcb-444d032ecc16/image.png" alt=""></p>
<p>Plan이 모두 동작하면, 아래와 같이 UI로 배포될 리소스를 알려준다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/8baf8844-b60a-4804-bcab-063b4418a147/image.png" alt=""></p>
<p>확실히 GUI로 보니 깔끔한 것 같다. 특히 리소스가 많아졌을 때 보기편할 것 같다.</p>
<h3 id="도전과제3">도전과제3</h3>
<blockquote>
<p>각자 사용하기 편리한 <strong>리소스를 모듈화</strong> 해보고, 해당 모듈을 활용해서 <strong>반복 리소스</strong>들 배포해보세요!</p>
</blockquote>
<p>VPC, Subnet 등 EC2에 필요한 리소스를 모듈화해봤다. 보안그룹의 포트는 SSH, HTTP를 열어놨다.</p>
<ul>
<li><p>module/main.tf</p>
<pre><code class="language-go">  locals {
    additional_tags = {
      Name = var.namespace
    }
  }

  resource &quot;aws_vpc&quot; &quot;vpc&quot; {
    cidr_block = &quot;192.169.0.0/16&quot;
    tags       = local.additional_tags
  }

  data &quot;aws_availability_zones&quot; &quot;available&quot; {
    state = &quot;available&quot;
  }

  resource &quot;aws_subnet&quot; &quot;public_subnet&quot; {
    vpc_id                  = aws_vpc.vpc.id
    cidr_block              = &quot;192.169.1.0/24&quot;
    availability_zone       = data.aws_availability_zones.available.names[0]
    map_public_ip_on_launch = true
    tags                    = local.additional_tags
  }

  resource &quot;aws_internet_gateway&quot; &quot;igw&quot; {
    vpc_id = aws_vpc.vpc.id
    tags   = local.additional_tags
  }
  resource &quot;aws_route_table&quot; &quot;public_route_table&quot; {
    vpc_id = aws_vpc.vpc.id
    route {
      cidr_block = &quot;0.0.0.0/0&quot;
      gateway_id = aws_internet_gateway.igw.id
    }
    tags = local.additional_tags
  }

  resource &quot;aws_route_table_association&quot; &quot;public_rtb_assoc&quot; {

    subnet_id      = aws_subnet.public_subnet.id
    route_table_id = aws_route_table.public_route_table.id
  }

  resource &quot;aws_security_group&quot; &quot;web_sg&quot; {
    name   = var.namespace
    vpc_id = aws_vpc.vpc.id

    ingress {
      from_port   = var.ssh_port
      to_port     = var.ssh_port
      protocol    = &quot;tcp&quot;
      cidr_blocks = [&quot;0.0.0.0/0&quot;]
    }

    ingress {
      from_port   = var.http_port
      to_port     = var.http_port
      protocol    = &quot;tcp&quot;
      cidr_blocks = [&quot;0.0.0.0/0&quot;]
    }

    egress {
      from_port   = 0
      to_port     = 0
      protocol    = &quot;-1&quot;
      cidr_blocks = [&quot;0.0.0.0/0&quot;]
    }

  }
  data &quot;aws_ami&quot; &quot;default&quot; {
    most_recent = true
    owners      = [&quot;amazon&quot;]

    filter {
      name   = &quot;owner-alias&quot;
      values = [&quot;amazon&quot;]
    }

    filter {
      name   = &quot;name&quot;
      values = [&quot;amzn2-ami-hvm*&quot;]
    }
  }

  resource &quot;aws_instance&quot; &quot;app&quot; {
    ami                    = data.aws_ami.default.id
    instance_type          = var.ec2_instance_type
    key_name               = var.key_name
    vpc_security_group_ids = [aws_security_group.web_sg.id]
    subnet_id              = aws_subnet.public_subnet.id
    tags                   = local.additional_tags
  }</code></pre>
</li>
<li><p>module/output.tf</p>
<pre><code class="language-go">  output &quot;instance_public_ip&quot; {
    value       = aws_instance.app.public_ip
    description = &quot;The public IP address of the App instance&quot;
  }</code></pre>
</li>
<li><p>module/variable.tf</p>
<pre><code class="language-go">  variable &quot;ssh_port&quot; {
    default     = 22
    type        = number
    description = &quot;SSH port&quot;
  }
  variable &quot;http_port&quot; {
    default     = 80
    type        = number
    description = &quot;HTTP port&quot;
  }
  variable &quot;ec2_instance_type&quot; {
    type        = string
    description = &quot;The type of EC2 instance to launch&quot;
  }
  variable &quot;key_name&quot; {
    type        = string
    description = &quot;The key name to use for an EC2 instance&quot;
  }
  variable &quot;namespace&quot; {
    type        = string
    description = &quot;env namespace&quot;
  }</code></pre>
</li>
</ul>
<ul>
<li><p>root/main.tf</p>
<pre><code class="language-go">  locals {
    env = {
      dev = {
        instance_type = &quot;t3.micro&quot;
        key_name      = &quot;m1&quot;
        namespace     = &quot;dev&quot;
      }
      prod = {
        instance_type = &quot;t3.medium&quot;
        key_name      = &quot;m1&quot;
        namespace     = &quot;prod&quot;
      }
    }
  }

  provider &quot;aws&quot; {
    region = &quot;ap-northeast-2&quot;
  }

  module &quot;ec2_aws_amazone&quot; {
    for_each          = local.env
    source            = &quot;../../module/ec2&quot;
    key_name          = each.value.key_name
    ec2_instance_type = each.value.instance_type
    namespace         = each.value.namespace
  }

  # output.tf
  output &quot;module_output_instance_public_ip&quot; {
    value = [
      for k in module.ec2_aws_amazone : k.instance_public_ip
    ]
  }</code></pre>
</li>
</ul>
<p>이제, 테라폼 명령어를 실행하여 리소스를 배포한다.</p>
<pre><code class="language-bash">$ tf apply -auto-approve
module.ec2_aws_amazone[&quot;dev&quot;].data.aws_availability_zones.available: Reading...
module.ec2_aws_amazone[&quot;prod&quot;].data.aws_availability_zones.available: Reading...
module.ec2_aws_amazone[&quot;prod&quot;].data.aws_ami.default: Reading...
module.ec2_aws_amazone[&quot;dev&quot;].data.aws_ami.default: Reading...
module.ec2_aws_amazone[&quot;dev&quot;].data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2_aws_amazone[&quot;prod&quot;].data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2_aws_amazone[&quot;dev&quot;].data.aws_ami.default: Read complete after 1s [id=ami-0ec77cfb1037681eb]
module.ec2_aws_amazone[&quot;prod&quot;].data.aws_ami.default: Read complete after 1s [id=ami-0ec77cfb1037681eb]
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.

Outputs:

module_output_instance_public_ip = [
  &quot;3.35.235.54&quot;,
  &quot;13.124.205.208&quot;,
]</code></pre>
<p><strong>AWS 콘솔에서 배포된 목록을 확인한다.</strong></p>
<ul>
<li>vpc
  <img src="https://velog.velcdn.com/images/han-0315/post/e156e759-8f17-4602-acdf-51fb7c2402f6/image.png" alt=""></li>
</ul>
<ul>
<li>EC2
  <img src="https://velog.velcdn.com/images/han-0315/post/f601c557-299a-495c-8733-3037a7b08abc/image.png" alt=""></li>
</ul>
<p>환경 별로, 배포가 잘 된 모습을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform Study #3]]></title>
            <link>https://velog.io/@han-0315/Terraform-Study-3</link>
            <guid>https://velog.io/@han-0315/Terraform-Study-3</guid>
            <pubDate>Wed, 13 Sep 2023 12:41:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/han-0315/post/40639ca5-d709-4456-8d54-b8c72a5d13a8/image.png" alt=""></p>
<h1 id="3주차">3주차</h1>
<p>💡 ‘테라폼으로 시작하는 IaC’ 책으로 진행하는 Terraform 스터디[T101] 3주차 정리내용입니다.</p>
<p>이번시간은 테라폼 기본사용 마지막 단계(3/3)이다. 이번주차에서는 조건문, 함수, 프로비저너, data block에 대해 배운 뒤 프로바이더를 경험해보고 마무리된다. 개인적으로 null_resource에 대해 잘몰랐다. 많이 사용한 플러그인 중 하나라고 한다..! 이번 기회에 잘 배워두면 좋을 것 같다. (지금은 terraform_data가 같은 기능을 수행한다.)</p>
<h2 id="conditional조건문">Conditional(조건문)</h2>
<p>조건 문의 경우 C언어의 삼항연산자와 유사하다. 그 외에는 지원하지 않는 모양이다.</p>
<p>형식:  <code>condition ? true_val : false_val</code> </p>
<p><strong>실습</strong></p>
<ul>
<li>main.tf</li>
</ul>
<pre><code class="language-go">variable &quot;enable_file&quot; {
  default = true
}

resource &quot;local_file&quot; &quot;foo&quot; {
  count    = var.enable_file ? 1 : 0
  content  = &quot;foo!&quot;
  filename = &quot;${path.module}/foo.bar&quot;
}

output &quot;content&quot; {
  value = var.enable_file ? local_file.foo[0].content : &quot;&quot;
}</code></pre>
<p>위의 코드의 내용은 var.enable_file의 값을 입력하지 않거나, true로 설정하면 foo.bar라는 local file을 생성한다. 반대의 경우라면 리소스를 생성하지 않는다.</p>
<ul>
<li><code>false</code>를 지정한 경우</li>
</ul>
<pre><code class="language-bash">$ export TF_VAR_enable_file**=false**
$ export | grep TF_VAR_enable_file
TF_VAR_enable_file=false
$ terraform init &amp;&amp; terraform plan &amp;&amp; terraform apply -auto-approve
...
Changes to Outputs:
  + content = &quot;&quot;

You can apply this plan to save these new output values to the Terraform state, without changing any real
infrastructure.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

content = &quot;&quot;</code></pre>
<ul>
<li><code>true</code>를 지정한경우(=아무것도 입력하지 않음)</li>
</ul>
<pre><code class="language-bash"># 아무것도 입력하지 않았을 떄
$ terraform init &amp;&amp; terraform plan &amp;&amp; terraform apply -auto-approve
...
+ content = &quot;foo!&quot;
local_file.foo[0]: Creating...
local_file.foo[0]: Creation complete after 0s [id=4bf3e335199107182c6f7638efaad377acc7f452]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:
content = &quot;foo!&quot;

$ terraform state list
local_file.foo[0]

$ echo &quot;local_file.foo[0].content&quot; | terraform console
╷
│ Warning: Value for undeclared variable
│ 
│ The root module does not declare a variable named &quot;ec2_instance_type&quot; but a value was found in file
│ &quot;terraform.tfvars&quot;. If you meant to use this value, add a &quot;variable&quot; block to the configuration.
│ 
│ To silence these warnings, use TF_VAR_... environment variables to provide certain &quot;global&quot; settings to
│ all configurations in your organization. To reduce the verbosity of these warnings, use the
│ -compact-warnings option.
╵
&quot;foo!&quot;
</code></pre>
<p>위의 예시처럼, 조건문이 아주 잘 적용된다.</p>
<h3 id="함수">함수</h3>
<p>함수는 내장함수만 사용가능하다. 사용자 정의함수와 같이 직접 만들 수 없다. <a href="https://developer.hashicorp.com/terraform/language/functions">**공식문서</a>** 에서 확인하면서 확인한 함수를 간단하게 정리해봤다.</p>
<ul>
<li><p><code>toset</code> : 해당 함수는 집합과 같이 중복된 원소를 제거하고, 정렬시킨다.</p>
<ul>
<li><code>toset([&quot;b&quot;, &quot;a&quot;,&quot;b&quot;]) =[”a”, “b”]</code></li>
</ul>
</li>
<li><p><code>Slice</code> :  목록 내에서 일부 연속 요소(elements)를 추출합니다. 시작 인덱스(<code>startindex</code>
)는 포함되지만 끝 인덱스(<code>endindex</code>)는 제외</p>
</li>
<li><p><code>[**length](https://developer.hashicorp.com/terraform/language/functions/length)</code> :** list, map 또는 string의 길이를 계산합니다. 리스트 또는 맵이면 컬렉션(collection)의 요소 수, 문자열이면 문자 수를 반환합니다.</p>
</li>
<li><p>숫자 관련 함수</p>
<ul>
<li><code>min, max, ceil, floor</code> 함수도 존재한다.</li>
<li>min(-1,2,var.temp) = -1, ceil(10.1) = 11</li>
</ul>
</li>
<li><p>문자열 관련 함수</p>
<ul>
<li>split(&quot;,&quot;, &quot;ami-xyz,AMI-ABC,ami-efg&quot;) = <code>[ &quot;ami-xyz&quot;,&quot;AMI-ABC&quot;,&quot;ami-efg&quot; ]</code></li>
<li>lower, upper : lower(var.ami)= <code>[ &quot;ami-xyz&quot;,&quot;ami-abc&quot;,&quot;ami-efg&quot; ]</code></li>
<li>substr(var.ami.0,7) = ami-xyz</li>
<li>join(”,” , [ &quot;ami-xyz&quot;,&quot;AMI-ABC&quot;,&quot;ami-efg&quot; ]) : <code>&quot;ami-xyz,AMI-ABC,ami-efg&quot;</code></li>
</ul>
</li>
<li><p>Collection 함수</p>
<ul>
<li>length(var.ami) = 3</li>
<li>index(var.ami, “AMI-ABC”) = 1</li>
<li>element(var.ami,2) = ami-efg</li>
<li>contains(var.ami, “AMI-ABC”) = true (요소가 있는 지 없는 지)</li>
</ul>
</li>
<li><p>MAP 관련 함수 → map 함수는 지원하지 않고, <code>tomap</code> 함수를 지원</p>
<pre><code class="language-go">  variable &quot;ami&quot; {
    type = map
    default = { 
          &quot;us-east-1&quot; = &quot;ami-xyz&quot;,
      &quot;ca-central-1&quot; = &quot;ami-efg&quot;,
      &quot;ap-south-1&quot; = &quot;ami-ABC&quot;
      }
      description = &quot;A map of AMI ID&#39;s for specific regions&quot; 
  }</code></pre>
<ul>
<li>lookup (var.ami, &quot;us-east-1&quot;) : ami-xyz</li>
</ul>
</li>
</ul>
<h3 id="프로비저너">프로비저너</h3>
<p>프로비저너는 프로바이더로 실행되지 않는 커맨드와 파일 복사 같은 역할을 수행한다. </p>
<blockquote>
<p><strong>프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화되지 않으므로 프로비저닝에 대한 결과가 항상 같다고 보장할 수 없다</strong> ⇒ <strong>선언적 보장 안됨</strong></p>
</blockquote>
<p>그렇기에, 프로비저너보단 userdata 등을 사용하는 것이 좋다.</p>
<p>프로비저너는 생성할 때만 실행되고 추후 작업은 없다. 그래서 <code>provisioner</code>가 실패하면 리소스가 잘못되었다고 판단하고 다음 <code>terraform apply</code> 할 때 제거하거나 다시 생성한다. <code>provisioner</code>에서 <code>when = &quot;destroy&quot;</code>를 지정하면 해당 프로비저너는 리소스를 제거하기 전에 실행되고 프로비저너가 실패한다면 다음 <code>terraform apply</code> 할 때 다시 실행하게 된다. 문서에 따르면 이 때문에 제거 프로비저너는 여러 번 실행해도 괜찮도록 작성해야 한다고 한다. </p>
<hr>
<p><strong>참고 자료</strong></p>
<p>앤서블과 연동해서 쓸거면, 아래의 링크로 진행하면 된다.</p>
<p><a href="https://github.com/ansible/terraform-provider-ansible/tree/main/examples">https://github.com/ansible/terraform-provider-ansible/tree/main/examples</a></p>
<hr>
<p>아래와 같이 원격에 내용을 전달할 수 있지만, 가급적 user_data를 사용하는 것이 좋다.</p>
<p><code>user_data = base64encode(templatefile(&quot;${path.module}/ubuntu_docker.tftpl&quot;, {}))</code> </p>
<p><strong>connection</strong>: remote-exec와 file 프로비저너를 사용하려면, 원격에 연결할 정보를 명시해야 한다. 주로 SSH/WinRM만 존재한다.</p>
<pre><code class="language-go">resource &quot;aws_instance&quot; &quot;web&quot; {
    ...
  connection {
    type     = &quot;ssh&quot;
    user     = &quot;root&quot;
    password = var.root_password
    host     = self.public_ip
  }

  provisioner &quot;file&quot; {
    source      = &quot;script.sh&quot;
    destination = &quot;/tmp/script.sh&quot;
  }

  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;chmod +x /tmp/script.sh&quot;,
      &quot;/tmp/script.sh args&quot;,
    ]
  }
}</code></pre>
<h3 id="null-resource">null resource</h3>
<p>아무작업도 수행하지 않는 리소스이다.</p>
<blockquote>
<p>이런 리소스가 필요한 이유는 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생하여, 프로바이더가 제공하는 리소스 수명주기 관리만으로는 이를 해결하기 어렵기 때문이다.</p>
</blockquote>
<p><strong>주로 사용되는 시나리오</strong></p>
<ul>
<li>프로비저닝 수행 과정에서 명령어 실행</li>
<li>프로비저너와 함께 사용</li>
<li>모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용</li>
<li>출력을 위한 데이터 가공</li>
</ul>
<p><strong>예시 상황</strong></p>
<p>EC2의 인스턴스로 웹서비스를 실행한다. 웹서비스 설정에 고정된 IP(EIP)가 필요하다. </p>
<p>아래와 같이 순환참조 에러가 발생하는 상황에서, null_resource를 추가해 해결할 수 있음</p>
<pre><code class="language-go">provider &quot;aws&quot; {
  region = &quot;ap-northeast-2&quot;
}

resource &quot;aws_security_group&quot; &quot;instance&quot; {
  name = &quot;t101sg&quot;

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = &quot;tcp&quot;
    cidr_blocks = [&quot;0.0.0.0/0&quot;]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = &quot;tcp&quot;
    cidr_blocks = [&quot;0.0.0.0/0&quot;]
  }

}

resource &quot;aws_instance&quot; &quot;example&quot; {
  ami                    = &quot;ami-0c9c942bd7bf113a2&quot;
  instance_type          = &quot;t2.micro&quot;
  subnet_id              = &quot;subnet-dbc571b0&quot; 
  private_ip             = &quot;172.31.1.100&quot;
  vpc_security_group_ids = [aws_security_group.instance.id]

  user_data = &lt;&lt;-EOF
              #!/bin/bash
              echo &quot;Hello, T101 Study&quot; &gt; index.html
              nohup busybox httpd -f -p 80 &amp;
              EOF

  tags = {
    Name = &quot;Single-WebSrv&quot;
  }
    # (1) 여기에서 eip 리소스에 대한 접근을 하면 순환참조로 에러가 발생함. 
  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;echo ${aws_eip.myeip.public_ip}&quot;
     ]
  }
}
# (1)번의 내용을 대체할 수 있는 Null_resource
resource &quot;null_resource&quot; &quot;echomyeip&quot; {
  provisioner &quot;remote-exec&quot; {
    connection {
      host = aws_eip.myeip.public_ip
      type = &quot;ssh&quot;
      user = &quot;ubuntu&quot;
      private_key =  file(&quot;/home/kaje/kp-kaje.pem&quot;) # 각자 자신의 EC2 SSH Keypair 파일 위치 지정
      #password = &quot;qwe123&quot;
    }
    inline = [
      &quot;echo ${aws_eip.myeip.public_ip}&quot;
      ]
  }
}

resource &quot;aws_eip&quot; &quot;myeip&quot; {
  #vpc = true
  instance = aws_instance.example.id
  associate_with_private_ip = &quot;172.31.1.100&quot;
}

output &quot;public_ip&quot; {
  value       = aws_instance.example.public_ip
  description = &quot;The public IP of the Instance&quot;
}</code></pre>
<h3 id="terraform_data">terraform_data</h3>
<p>이 리소스 또한, null_resource와 동일한 역할을 하나, 테라폼 자체에 포함된 <strong>기본 수명주기 관리자</strong>가 제공된다.</p>
<p><strong>triggers_replace:</strong> 인스턴스의 상태를 저장하며, 상태가 변경되면 아래의 명령어를 수행한다.</p>
<p>아래의 예시를 통해 확인하면, </p>
<pre><code class="language-go">resource &quot;terraform_data&quot; &quot;foo&quot; {
  triggers_replace = [
    local_file.foo
  ]
  provisioner &quot;local-exec&quot; {
    command = &quot;echo &#39;terraform_data test&#39;&quot;
  }
}
output &quot;terraform_data_output&quot; {
  value = terraform_data.foo.output # 출력 결과는 &quot;world&quot;
}

variable &quot;enable_file&quot; {
  default = true
}

resource &quot;local_file&quot; &quot;foo&quot; {
  count    = var.enable_file ? 1 : 0
  content  = &quot;foo!&quot;
  filename = &quot;${path.module}/foo.bar&quot;
}

output &quot;content&quot; {
  value = var.enable_file ? local_file.foo[0].content : &quot;&quot;
}</code></pre>
<ol>
<li>terraform apply 이후, foo.bar의 내용을 수정했을 때</li>
</ol>
<pre><code class="language-go">$ terraform apply -auto-approve
local_file.foo[0]: Refreshing state... [id=4bf3e335199107182c6f7638efaad377acc7f452]
terraform_data.foo: Refreshing state... [id=bdfbfd37-fccc-4f02-6542-08f1bbb3d2a1]
...
terraform_data.foo: Destroying... [id=bdfbfd37-fccc-4f02-6542-08f1bbb3d2a1]
terraform_data.foo: Destruction complete after 0s
local_file.foo[0]: Creating...
local_file.foo[0]: Creation complete after 0s [id=4bf3e335199107182c6f7638efaad377acc7f452]
terraform_data.foo: Creating...
terraform_data.foo: Provisioning with &#39;local-exec&#39;...
terraform_data.foo (local-exec): Executing: [&quot;/bin/sh&quot; &quot;-c&quot; &quot;echo &#39;terraform_data test&#39;&quot;]
terraform_data.foo (local-exec): terraform_data test
terraform_data.foo: Creation complete after 0s [id=4301eef8-bbc6-58f0-6948-a6f6ac176b6c]

Apply complete! Resources: 2 added, 0 changed, 1 destroyed.

Outputs:

content = &quot;foo!&quot;</code></pre>
<ol>
<li>(triggers_replace를 주석으로 제거한 뒤)terraform apply 이후, foo.bar의 내용을 수정했을 때</li>
</ol>
<pre><code class="language-bash">$ terraform apply
terraform_data.foo: Refreshing state... [id=3396b6e8-ffca-dac3-f0d5-c41455864c42]
local_file.foo[0]: Refreshing state... [id=4bf3e335199107182c6f7638efaad377acc7f452]

Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.foo[0] will be created
  + resource &quot;local_file&quot; &quot;foo&quot; {
      + content              = &quot;foo!&quot;
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = &quot;0777&quot;
      + file_permission      = &quot;0777&quot;
      + filename             = &quot;./foo.bar&quot;
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only &#39;yes&#39; will be accepted to approve.

  Enter a value: yes

local_file.foo[0]: Creating...
local_file.foo[0]: Creation complete after 0s [id=4bf3e335199107182c6f7638efaad377acc7f452]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

content = &quot;foo!&quot;</code></pre>
<h3 id="moved">moved</h3>
<p>state에 기록되는 리소스의 이름이 변경되면 기존 리소스 삭제 후 재생성한다. 이름은 변경하지만, 인프라를 유지하고 싶을 때 moved block을 사용한다.</p>
<ul>
<li>원본</li>
</ul>
<pre><code class="language-bash">resource &quot;local_file&quot; &quot;a&quot; {
  content  = &quot;foo!&quot;
  filename = &quot;${path.module}/foo.bar&quot;
}

output &quot;file_content&quot; {
  value = local_file.a.content
}</code></pre>
<ul>
<li>이름 수정 후</li>
</ul>
<pre><code class="language-bash">resource &quot;local_file&quot; &quot;b&quot; {
  content  = &quot;foo!&quot;
  filename = &quot;${path.module}/foo.bar&quot;
}

moved {
  from = local_file.a
  to   = local_file.b
}

output &quot;file_content&quot; {
  value = local_file.b.content
}</code></pre>
<p>이와 같이 moved block을 사용하면, 인프라를 유지할 수 있다.</p>
<h3 id="프로바이더">프로바이더</h3>
<p>프로바이더란 인프라 리소스를 제공하는 업체라고 생각하면 된다. Terraform은 플러그인을 사용하여 프로바이더라고 불리는 클라우드, SaaS, 다른 API와 상호작용한다.</p>
<p>Terraform 이 어떤 공급자와 사용할 지 표현하기 위해, <code>provider.tf</code> 에 별도로 정의한다.</p>
<p>프로바이더는 <code>terraform init</code> 명령어를 통해, 필요한 플러그인을 검색 및 다운로드하며 lock.hcl 파일에 프로바이더를 명시하여 앞으로의 코드 수행에서 사용되는 플러그인을 제한한다. (예상하지 못한, 동작을 방지하는 역할을 한다.) <code>terraform init</code> 명령어는 백엔드 설정 혹은 프로젝트 시작시 수행하기에 여러 작업이 일어난다. 프로바이더만 업그레이드하고 싶으면, <code>terraform init -upgrade</code> 를 수행한다. </p>
<p>아래의 그림으로 한번에 이해할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/1bfc8411-bfc9-490a-91e5-91305e17397b/image.png" alt="">
출처:<a href="https://malwareanalysis.tistory.com/619">https://malwareanalysis.tistory.com/619</a></p>
<p>아래와 같이, 파트너사 혹은 플러그인을 제공하는 업체라면 테라폼을 통해 리소스를 정의할 수 있다.
Terraform과 파트너 목록은 아래의 이미지 참고
<img src="https://velog.velcdn.com/images/han-0315/post/3c9477df-6c67-4fa7-9ada-e4ba7d4c39ae/image.png" alt=""></p>
<ul>
<li><p>kubernetes 환경 인프라 구축하기</p>
<ul>
<li><p>provider.tf</p>
<pre><code class="language-bash">  terraform {
    required_providers {
      kubernetes = {
        source = &quot;hashicorp/kubernetes&quot;
      }
    }
  }

  provider &quot;kubernetes&quot; {
    config_path    = &quot;~/.kube/config&quot;
  }</code></pre>
</li>
<li><p>kubernetes.tf</p>
<pre><code class="language-bash">  resource &quot;kubernetes_deployment&quot; &quot;nginx&quot; {
    metadata {
      name = &quot;nginx-example&quot;
      labels = {
        App = &quot;t101-nginx&quot;
      }
    }
    spec {
      replicas = 2
      selector {
        match_labels = {
          App = &quot;t101-nginx&quot;
        }
      }
      template {
        metadata {
          labels = {
            App = &quot;t101-nginx&quot;
          }
        }
        spec {
          container {
            image = &quot;nginx:1.7.8&quot;
            name  = &quot;example&quot;

            port {
              container_port = 80
            }
          }
        }
      }
    }
  }

  resource &quot;kubernetes_service&quot; &quot;nginx&quot; {
    metadata {
      name = &quot;nginx-example&quot;
    }
    spec {
      selector = {
        App = kubernetes_deployment.nginx.spec.0.template.0.metadata[0].labels.App
      }
      port {
        node_port   = 30080
        port        = 80
        target_port = 80
      }

      type = &quot;NodePort&quot;
    }
  }</code></pre>
</li>
</ul>
</li>
<li><p>실행결과(미니큐브로 테스트)</p>
</li>
</ul>
<pre><code class="language-bash">$ terraform init &amp;&amp; terraform plan &amp;&amp; terraform apply -auto-approve
...
Plan: 2 to add, 0 to change, 0 to destroy.
kubernetes_deployment.nginx: Creating...
kubernetes_deployment.nginx: Still creating... [10s elapsed]
kubernetes_deployment.nginx: Creation complete after 16s [id=default/nginx-example]
kubernetes_service.nginx: Creating...
kubernetes_service.nginx: Creation complete after 0s [id=default/nginx-example]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
kubernetes_deployment.nginx
kubernetes_service.nginx</code></pre>
<pre><code class="language-bash">Every 1.0s: kubectl get pods,svc                                 MacBook-Pro.local: Wed Sep 13 21:30:54 2023

NAME                                 READY   STATUS    RESTARTS   AGE
pod/nginx-example-868fbd6dcc-8r9bv   1/1     Running   0          89s
pod/nginx-example-868fbd6dcc-xp4rg   1/1     Running   0          89s

NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/kubernetes      ClusterIP   10.96.0.1        &lt;none&gt;        443/TCP        114d
service/nginx-example   NodePort    10.103.116.226   &lt;none&gt;        80:30080/TCP   74s</code></pre>
<p>이처럼 정상적으로 테스트된 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform Study #2]]></title>
            <link>https://velog.io/@han-0315/Terraform-Study-2</link>
            <guid>https://velog.io/@han-0315/Terraform-Study-2</guid>
            <pubDate>Sun, 03 Sep 2023 16:21:31 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/han-0315/post/7c8e9d03-dde9-42ce-b2b7-0bcdc96aa578/image.png" alt=""></p>
<aside>
💡  ‘테라폼으로 시작하는 IaC’ 책으로 진행하는 Terraform 스터디[T101] 2주차 정리내용입니다.

</aside>

<h3 id="데이터-소스">데이터 소스</h3>
<p>데이터 소스(<code>data</code>)는 외부의 리소스 혹은 저장된 정보를 내부로 가져올 때 사용한다.</p>
<p>기본 사용법은 2기 스터디원 Ssoon님이 <a href="https://kschoi728.tistory.com/124">블로그</a>에 잘 정리해주셨다. </p>
<p>아래와 같이 AMI나 AZ를 조회할 때 유용하다.</p>
<ul>
<li>ubuntu AMI 조회</li>
</ul>
<pre><code class="language-go">data &quot;aws_ami&quot; &quot;ubuntu&quot; {
  most_recent = true

  owners = [&quot;099720109477&quot;] 

  filter {
    name   = &quot;name&quot;
    values = [&quot;ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*&quot;]
  }

  filter {
    name   = &quot;architecture&quot;
    values = [&quot;x86_64&quot;]
  }

  filter {
    name   = &quot;root-device-type&quot;
    values = [&quot;ebs&quot;]
  }

  filter {
    name   = &quot;state&quot;
    values = [&quot;available&quot;]
  }

  filter {
    name   = &quot;virtualization-type&quot;
    values = [&quot;hvm&quot;]
  }
}</code></pre>
<ul>
<li>AZ 검색</li>
</ul>
<pre><code class="language-go">data &quot;aws_availability_zones&quot; &quot;available&quot; {
    group_names = [
        &quot;ap-northeast-2&quot;,
    ]
    id          = &quot;ap-northeast-2&quot;
    names       = [
        &quot;ap-northeast-2a&quot;,
        &quot;ap-northeast-2b&quot;,
        &quot;ap-northeast-2c&quot;,
        &quot;ap-northeast-2d&quot;,
    ]
    state       = &quot;available&quot;
    zone_ids    = [
        &quot;apne2-az1&quot;,
        &quot;apne2-az2&quot;,
        &quot;apne2-az3&quot;,
        &quot;apne2-az4&quot;,
    ]
}</code></pre>
<h3 id="입력-변수">입력 변수</h3>
<p>변수는 Terrraform 코드를 동적으로 구성할 수 있게 한다. 테라폼에서는 이것을 <strong>입력 변수</strong> Input Variables 로 정의한다.</p>
<ul>
<li><p>선언 예시</p>
<pre><code class="language-go">  variable &quot;&lt;이름&gt;&quot; {
   &lt;인수&gt; = &lt;값&gt;
  }

  variable &quot;image_id&quot; {
   type = string
  }</code></pre>
</li>
</ul>
<p>위와 같이 변수를 정의할 때 다양한 메타인수를 넣을 수 있다. 관련 정보는 아래와 같다.</p>
<aside>
💡 변수 정의 시 사용 가능한 메타인수

<ul>
<li><p><strong>default</strong> : 변수 값을 전달하는 여러 가지 방법을 지정하지 않으면 기본값이 전달됨, 기본값이 없으면 대화식으로 사용자에게 변수에 대한 정보를 물어봄</p>
</li>
<li><p><strong>type</strong> : 변수에 허용되는 값 유형 정의, string number bool list map set object tuple 와 유형을 지정하지 않으면 any 유형으로 간주</p>
</li>
<li><p><strong>description</strong> : 입력 변수의 설명</p>
</li>
<li><p><strong>validation</strong> : 변수 선언의 제약조건을 추가해 유효성 검사 규칙을 정의 - <a href="https://honglab.tistory.com/217">링크</a></p>
</li>
<li><p><strong>sensitive</strong> : 민감한 변수 값임을 알리고 테라폼의 출력문에서 값 노출을 제한 (암호 등 민감 데이터의 경우) - <a href="https://daaa0555.tistory.com/371">링크</a></p>
</li>
<li><p><strong>nullable</strong> : 변수에 값이 없어도 됨을 지정</p>
</aside>
</li>
<li><p>우선순위</p>
</li>
</ul>
<p>1번 부터 변수를 대입하며, 후 순위가 전 순위를 덮어쓰기 합니다. 결론적으로 아래에 있는 옵션이 우선순위가 높습니다. </p>
<table>
<thead>
<tr>
<th>Order</th>
<th>Option</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Environment Variables</td>
</tr>
<tr>
<td>2</td>
<td>terraform.tfvars</td>
</tr>
<tr>
<td>3</td>
<td>terraform.tfvars.json</td>
</tr>
<tr>
<td>4</td>
<td>*.auto.tfvars (alphabetical order)</td>
</tr>
<tr>
<td>5</td>
<td>-var or –var-file (command-line flags)</td>
</tr>
</tbody></table>
<h3 id="local">Local</h3>
<p>local은 외부에서 입력되지 않고, 코드 내에서만 가공되어 동작하는 값이다. 외부에서 입력되진 않지만 Local 선언 자체에 일반 변수를 넣을 수 있다. (아래의 예시 참고)</p>
<p>local은 회사내의 클라우드 서비스를 이용할 때, 리소스에 태그를 걸어야한다. ex) Owner, Purpose 등</p>
<p>이 때 Local 변수를 사용하면 아래와 같이 편하게 리소스에 태그를 걸 수 있다.</p>
<pre><code class="language-go">locals {
  additional_tags = {
    Purpose     = var.purpose
    Owner       = var.owner
  }
}
...
resource &quot;aws_instance&quot; &quot;app&quot; {
...
  tags = merge(
    {
      Name = &quot;web-app&quot;
    },
    local.additional_tags
  )

}</code></pre>
<h3 id="실습">실습</h3>
<p><code>도전과제2</code> : 위 3개 코드 파일 내용에 <strong>리소스</strong>의 이름(<strong>myvpc</strong>, <strong>mysubnet1</strong> 등)을 반드시! 꼭! 자신의 <strong>닉네임</strong>으로 변경해서 배포 실습해보세요!</p>
<ul>
<li>VPC DNS 옵션 활성화</li>
</ul>
<pre><code class="language-go">resource &quot;aws_vpc&quot; &quot;myvpc&quot; {
  cidr_block           = &quot;10.10.0.0/16&quot;
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = &quot;t101-study&quot;
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/han-0315/post/9687844c-e387-4839-81ad-bc66cbf4fb1c/image.png" alt=""></p>
<ul>
<li><code>[도전과제1]</code> 리전 내에서 사용 가능한 <strong>가용영역 목록 가져오기</strong>를 사용한 VPC 리소스 생성 실습 진행</li>
<li>아래와 같이, data 소스를 이용하여 AZ를 가져온다.</li>
</ul>
<pre><code class="language-go">resource &quot;aws_subnet&quot; &quot;mysubnet1&quot; {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = &quot;10.10.1.0/24&quot;

  availability_zone = data.aws_availability_zones.available.names[2]

  tags = {
    Name = &quot;t101-subnet1&quot;
  }
}

resource &quot;aws_subnet&quot; &quot;mysubnet2&quot; {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = &quot;10.10.2.0/24&quot;

  availability_zone = &quot;ap-northeast-2c&quot;

  tags = {
    Name = &quot;t101-subnet2&quot;
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/han-0315/post/c7c5bd11-e370-48df-af0a-f6869fdcdfd8/image.png" alt=""></p>
<ul>
<li>ec2 생성 콘솔에서 확인</li>
</ul>
<p><img src="https://velog.velcdn.com/images/han-0315/post/7875e838-0973-4bcf-a3c4-fef6620a761b/image.png" alt=""></p>
<ul>
<li>Graph</li>
</ul>
<p>Vscode 에서 추출한 그림인데, 리소스가 많아 보기 조금 불편하다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/f1fdc61e-ee24-4db2-adfb-a0e47e06dcc3/image.png" alt=""></p>
<ul>
<li>EC2 접속하기</li>
</ul>
<pre><code class="language-bash">$ MYIP=$(terraform output -raw kane_ec2_public_ip)
$ echo $MYIP                   
3.35.173.67
$ while true; do curl --connect-timeout 1  http://$MYIP/ ; echo &quot;------------------------------&quot;; date; sleep 1; done
&lt;h1&gt;RegionAz(apne2-az1) : Instance ID(i-0ca40805a20604dbe) : Private IP(10.10.1.34) : Web Server&lt;/h1&gt;
------------------------------
Mon Sep  4 00:50:56 KST 2023
&lt;h1&gt;RegionAz(apne2-az1) : Instance ID(i-0ca40805a20604dbe) : Private IP(10.10.1.34) : Web Server&lt;/h1&gt;
------------------------------
Mon Sep  4 00:50:57 KST 2023
&lt;h1&gt;RegionAz(apne2-az1) : Instance ID(i-0ca40805a20604dbe) : Private IP(10.10.1.34) : Web Server&lt;/h1&gt;
------------------------------
Mon Sep  4 00:50:58 KST 2023</code></pre>
<h3 id="output">Output</h3>
<p>terraform apply 이후 파일에 적힌 출력값을 콘솔에 출력해준다. 주로 Ec2의 퍼블릭 ip같이 꼭 확인해야 하는 것들을 주로 출력한다. 생성 후의 정보를 출력하기에 당연한 이야기지만 <strong>오로지, apply를 적용할 때만 출력한다. 또한 이런 값들은 추후 파이프라인 구성, shell script 혹은 <code>ansible</code> 에 사용할 수도 있다.</strong></p>
<p><strong>기본 예시</strong></p>
<pre><code class="language-go">output &quot;instance_ip_addr&quot; {
  value       = aws_instance.server.private_ip
  description = &quot;The private IP address of the main server instance.&quot;
}</code></pre>
<p><strong>조건 검사 진행</strong></p>
<pre><code class="language-go">output &quot;api_base_url&quot; {
  value = &quot;https://${aws_instance.example.private_dns}:8433/&quot;

  # The EC2 instance must have an encrypted root volume.
  precondition {
    condition     = data.aws_ebs_volume.example.encrypted
    error_message = &quot;The server&#39;s root volume is not encrypted.&quot;
  }
}</code></pre>
<ul>
<li>Option<ul>
<li><code>sensitive</code> : CLI 에서 출력되지 않게 할 수 있다.</li>
<li><strong>**<code>depends_on</code> : 선수관계를 정할 수 있다.(먼저, 출력되는 것을 결정할 수 있다.)</strong></li>
</ul>
</li>
</ul>
<pre><code class="language-go">output &quot;instance_ip_addr&quot; {
  value       = aws_instance.server.private_ip
  description = &quot;The private IP address of the main server instance.&quot;

  depends_on = [
    # Security group rule must be created before this IP address could
    # actually be used, otherwise the services will be unreachable.
    aws_security_group_rule.local_access,
  ]
}</code></pre>
<h3 id="반복문">반복문</h3>
<ul>
<li><p><strong>count :</strong> 반복문, 정수 값만큼 리소스나 모듈을 생성함. 인스턴스가 거의 동일한 경우 Count가 적절(For each 보다), <code>count, count.index</code> 로 접근</p>
<pre><code class="language-go">  variable &quot;subnet_ids&quot; {
    type = list(string)
  }

  resource &quot;aws_instance&quot; &quot;server&quot; {
    # Create one instance for each subnet
    count = length(var.subnet_ids)
      ...
    subnet_id     = var.subnet_ids[count.index]

    tags = {
      Name = &quot;Server ${count.index}&quot;
    }
  }</code></pre>
</li>
<li><p><strong>for_each :</strong> 반복문, 선언된 key 값 개수만큼 리소스를 생성</p>
<pre><code class="language-go">  resource &quot;aws_instance&quot; &quot;example&quot; {
    # One VPC for each element of var.vpcs
    for_each = var.instances

    # each.value here is a value from var.vpcs
    name = each.key
      ami = each.value.ami
  }</code></pre>
</li>
<li><p><strong>for</strong></p>
<p>  만약 [ ]으로 되어있으면 tuple 형식으로 컨테이너를 반환하고, {}이면 오브젝트로 반환하는 반복문이다.</p>
<p>  또한 <code>for</code> 뒤에 <code>If</code> 를 통해 필터링 기능도 가능하다.(if 인 값만 사용)</p>
<pre><code class="language-go">  [for s in var.list : upper(s) if s != &quot;&quot;]
  [for i, v in var.list : &quot;${i} is ${v}&quot;]
  # object 형식일때
  [for k, v in var.map : length(k) + length(v)]</code></pre>
</li>
</ul>
<ul>
<li><strong>Dynamic Block</strong></li>
</ul>
<p>특수한 목적의 Dynamic Block을 통해 동적으로 만들어지는 변수에 대해 반복 가능한 블럭을 만들 수 있다. 기존의 for_each, count 등 반복문은 리소스 block 등 자신의 바깥 블럭을 반복해서 찍어내는 것에 비해 dynamic block은 block자체를 정의하며 반복적으로 찍어낸다. (resource와 같은 단일블락이 아닌 내부 블락으로만 사용된다.) 사용방법은 Argument를 확인하면 된다.</p>
<ul>
<li>찾아보니, 다음과 같은 안내사항도 있었다.<ul>
<li>과도한 사용을 피한다. (동적 블록을 과도하게 사용하면 구성을 읽고 유지하기 어려울 수 있다.)</li>
<li>재사용 가능한 모듈을 위한 깨끗한 사용자 인터페이스를 구축하기 위해 세부 정보를 숨겨야 할 때 사용합니다</li>
<li>가능한 경우 항상 중첩된 블록을 문자 그대로 써라.</li>
</ul>
</li>
</ul>
<pre><code class="language-go">resource &quot;aws_security_group&quot; &quot;backend-sg&quot; {
  name        = &quot;backend-sg&quot;
  vpc_id      = aws_vpc.backend-vpc.id
    dynamic &quot;ingress&quot; {
        for_each = var.ingress_ports
        content {
          from_port = ingress.value
                to_port = ingress.value
                protocol = &quot;tcp&quot;
                cidr_blocks = [&quot;0.0.0.0/0&quot;]
        }
    }
}
# 아래와 같이 하기 싫어서 위처럼 진행
resource &quot;aws_security_group&quot; &quot;backend-sg&quot; {
  name        = &quot;backend-sg&quot;
  vpc_id      = aws_vpc.backend-vpc.id
    ingress {
          from_port = 22
                to_port = 22
                protocol = &quot;tcp&quot;
                cidr_blocks = [&quot;0.0.0.0/0&quot;]
    }
    ingress {
          from_port = 8080
                to_port = 8080
                protocol = &quot;tcp&quot;
                cidr_blocks = [&quot;0.0.0.0/0&quot;]
    }
}

</code></pre>
<h3 id="도전과제3">도전과제3</h3>
<blockquote>
<p><code>도전과제3</code> : <strong>입력변수</strong>를 활용해서 리소스(어떤 리소스든지 상관없음)를 배포해보고, 해당 코드를 정리해주세요!</p>
</blockquote>
<p>위에서 진행한 EC2 배포 코드를 이용한다. 변수를 통해 인스턴스의 타입을 동적으로 구성한다.</p>
<ul>
<li>EC2 구성</li>
</ul>
<pre><code class="language-go">resource &quot;aws_instance&quot; &quot;kane_ec2&quot; {

  depends_on = [
    aws_internet_gateway.kane_igw
  ]

  ami                         = data.aws_ami.amazonlinux2.id
  associate_public_ip_address = true
    // 아래의 내용을 수정!
  instance_type               = var.ec2_instance_type
  vpc_security_group_ids      = [&quot;${aws_security_group.kane_sg.id}&quot;]
  subnet_id                   = aws_subnet.kane_subnet1.id
...</code></pre>
<ul>
<li>variable.tf 파일을 생성한 뒤, 아래의 내용 추가</li>
</ul>
<pre><code class="language-go">variable &quot;ec2_instance_type&quot; {
  type        = string
  description = &quot;The type of EC2 instance to launch&quot;
}</code></pre>
<ul>
<li>terraform.tfvars 파일을 생성한 뒤 아래의 내용을 추가한다.<ul>
<li>해당 파일이 존재하면, 테라폼은 자동으로 변수의 값을 가져간다. 우선순위에 따라 덮어써질 수 있긴 하다. 하지만 여기선 변수 입력을 해당 파일로만 하니 상관없다.</li>
</ul>
</li>
</ul>
<pre><code class="language-go">ec2_instance_type = &quot;t2.small&quot;</code></pre>
<p>이제 <code>Terraform apply</code> 명령어를 통해 인프라를 구축한다.</p>
<p>기존과는 다르게 <code>t2.micro</code> 가 아닌 <code>t2.small</code> 이 생성된 것을 확인할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/81a554a6-f603-49ca-b473-95b46c532e15/image.png" alt=""></p>
<h3 id="도전과제4">도전과제4</h3>
<blockquote>
<p><code>도전과제4</code> : <strong>local</strong>를 활용해서 리소스(어떤 리소스든지 상관없음)를 배포해보고, 해당 코드를 정리해주세요!</p>
</blockquote>
<p>local을 통해, EC2에 태깅 작업을 진행한다.</p>
<ul>
<li>local 선언</li>
</ul>
<pre><code class="language-go">locals {
  additional_tags = {
    Environment = &quot;Dev&quot;
    Purpose     = &quot;Test&quot;
    Owner       = &quot;Kane&quot;
  }
}</code></pre>
<ul>
<li>EC2에 추가</li>
</ul>
<pre><code class="language-go">resource &quot;aws_instance&quot; &quot;kane_ec2&quot; {

  depends_on = [
    aws_internet_gateway.kane_igw
  ]

    ...

  tags = merge({
    Name = &quot;t101-kane_ec2&quot;
    }
  , local.additional_tags)
}</code></pre>
<p><code>terraform apply</code>를 실행한다.</p>
<p>이제 AWS 콘솔에 들어가 <strong>EC2 &gt; Tags</strong> 페이지를 확인하면 다음과 같이 태깅이 올바르게 된 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/c4ee119f-ed52-4d97-ba77-70bdd10e8bb4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform Study #1]]></title>
            <link>https://velog.io/@han-0315/Terraform-Study-1</link>
            <guid>https://velog.io/@han-0315/Terraform-Study-1</guid>
            <pubDate>Mon, 28 Aug 2023 10:15:18 GMT</pubDate>
            <description><![CDATA[<h1 id="1주차">1주차</h1>
<aside>
💡  ‘테라폼으로 시작하는 IaC’ 책으로 진행하는 Terraform 스터디[T101] 1주차 정리내용입니다.
</aside>


<p>평소에 테라폼에 관심이 많아, 자격증도 취득하고 공부를 했다. 공부를 했지만, 아직 이것저것 헷갈리는 게 많다. 이를 구체화시키고, 실무에 대한 조언을 들을 겸 스터디에 참가하게 되었다. 나중에는 Golang 연습할 겸 코드도 뜯어보고 싶다. 스터디는 <a href="https://www.notion.so/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863?pvs=21">CloudNet</a>에서 주관하고 유형욱님과 윤서율님이 진행해주신다. </p>
<p>1주차에서는 테라폼에 대해 알아보고, 실행 환경을 세팅한다. 이후 EC2를 배포해보면서 기본 문법과 명령어에 대해 학습한다. </p>
<h3 id="테라폼-제공유형">테라폼 제공유형</h3>
<ol>
<li><p><strong>On-premise</strong> : Terraform이라 불리는 형태로, 사용자의 컴퓨팅 환경에 오픈소스 바이너리툴인 테라폼을 통해 사용</p>
<blockquote>
<p>라이선스를 변경되어서, <strong>오픈소스 → 커뮤니티 에디션</strong>으로 변경된다.</p>
</blockquote>
</li>
<li><p><strong>Hosted SaaS</strong> : Terraform Cloud로 불리는 <strong>SaaS</strong>로 제공되는 구성 환경으로 하시코프가 관리하는 서버 환경이 제공</p>
</li>
<li><p><strong>Private Install</strong> : Terraform Enterprise로 불리는 서버 설치형 구성 환경으로, 기업의 사내 정책에 따라 프로비저닝 관리가 외부 네트워크와 격리 - <a href="https://www.hashicorp.com/blog/announcing-hashicorp-private-terraform-enterprise">링크</a></p>
</li>
</ol>
<p>2,3 번은 기본적으로 GUI가 제공되며 Terraform Cloud는 Free 티어가 있다. </p>
<p><strong>테라폼 클라우드 가격정책 비교</strong></p>
<ul>
<li>Free : 리소스 500개 까지 무료 → 커뮤니티 버전</li>
<li>Standard : Free + 워크플로우 기능 추가 + 동시실행(Concurrency 개수 3개)</li>
</ul>
<h3 id="aws-옵션실습-환경-구성">AWS 옵션(실습 환경 구성)</h3>
<p><strong>AWS_PAGER 옵션 제거</strong></p>
<ul>
<li>하나의 페이지처럼, 작동함 → 나갈려면 :q 옵션을 입력해야 하고, 기타 옵션도 쓸 수 있는 듯?</li>
</ul>
<p>페이저를 비활성화하는 이유는 여러 가지가 있을 수 있습니다.</p>
<ol>
<li><strong>Scripting and Automation</strong>: AWS CLI 명령어의 출력을 스크립트나 다른 프로그램에서 파싱해야 할 경우, 페이저가 불필요한 중간 단계를 추가할 수 있습니다.</li>
<li><strong>Non-Interactive Environments</strong>: CI/CD 파이프라인이나 배치 작업과 같은 비대화형(non-interactive) 환경에서는 페이저가 문제를 일으킬 수 있습니다.</li>
</ol>
<p><code>export AWS_PAGER=&quot;&quot;</code></p>
<p>적용하면, 페이저가 없이 값만 출력된다.</p>
<p><strong>AWS 계정 선택</strong></p>
<p>여러 AWS 계정을 쓰는 경우, Profile을 환경변수로 선택할 수 있다. </p>
<p><code>export AWS_PROFILE=&quot;study&quot;</code></p>
<p>Terraform에서도 별도의 provider block에서 세팅해줘야한다.</p>
<pre><code class="language-bash">provider &quot;aws&quot; {
  profile = &quot;eks&quot;
  region  = &quot;ap-northeast-2&quot;
}</code></pre>
<p>추가) <strong>vscode aws toolkit 설정으로, 현재의 profile을 확인할 수 있다. 여러 AWS 계정을 쓸 경우, 편리하다.</strong></p>
<h3 id="hcl">HCL</h3>
<p>HCL은 JSON을 본따 만든 언어이며, JSON보다 사람 친화적인 언어이다. </p>
<ul>
<li>인프라가 코드로 표현되고, 이 코드는 곧 인프라이기 때문에 <strong>선언적(declarative)</strong> 특성을 갖게 되고 튜링 완전한 Turing-complete <strong>언어적</strong> 특성을 갖는다. [참고: <a href="http://wiki.hash.kr/index.php/%ED%8A%9C%EB%A7%81%EC%99%84%EC%A0%84">튜링완전</a>]</li>
<li>즉, 일반적인 프로그래밍 언어의 <strong>조건문</strong> 처리 같은 동작이 가능하다. 자동화와 더불어, 쉽게 <strong>버저닝해 히스토리</strong>를 관리하고 <strong>함께 작업</strong> 할 수 있는 기반을 제공. → 확실한 차이점</li>
</ul>
<h3 id="terraform-command-옵션">Terraform Command 옵션</h3>
<ul>
<li><code>validate</code><ul>
<li><strong><code>-no-color</code></strong> : 대부분의 명령과 함께 사용 가능, 로컬이 아닌 외부 실행 환경(젠킨스, Terraform Cloud, Github Action 등)을 사용하는 경우, 색상 표기 문자 <strong>←[0m←[1m</strong> 가 표기 될 수 있다. 이 경우 -no-color 옵션으로 <strong>색상 표기 문자 없이 출력</strong>함. [<a href="https://developer.hashicorp.com/terraform/cli/commands/plan#no-color">참고</a>]</li>
</ul>
</li>
<li><code>plan</code><ul>
<li><code>-detailed-exitcode</code> : <strong>plan 추가 옵션</strong>으로, 파이프라인 설계에서 활용 가능, exitcode가 환경 변수로 구성됨</li>
</ul>
</li>
<li><code>apply</code> or <code>destroy</code><ul>
<li><code>-auto-approve</code>: 자동 승인 기능 부여 옵션</li>
</ul>
</li>
</ul>
<h3 id="ec2-배포">EC2 배포</h3>
<p>우선, 해당 실습에서는 default VPC를 사용한다. 만약 사용하는 리전의 default VPC가 없다면 아래의 명령어를 실행하여 생성한다. </p>
<pre><code class="language-bash">$ aws ec2 create-default-vpc
{
    &quot;Vpc&quot;: {
        &quot;CidrBlock&quot;: &quot;172.31.0.0/16&quot;,
        ...
        ],
        &quot;IsDefault&quot;: true,
        &quot;Tags&quot;: []
    }
}</code></pre>
<p>EC2를 프로비저닝하는 기본적인 코드이다. 해당 코드를 실행하면, EC2를 생성할 수 있으며 접속할 public IP를 받을 수 있다.</p>
<pre><code class="language-go">provider &quot;aws&quot; {
  region = &quot;ap-northeast-2&quot;
}

resource &quot;aws_instance&quot; &quot;example&quot; {
  ami                    = &quot;*ami-0c9c942bd7bf113a2*&quot;
  instance_type          = &quot;t2.micro&quot;
  **vpc_security_group_ids = [aws_security_group.instance.id]**

  user_data = &lt;&lt;-EOF
              #!/bin/bash
              echo &quot;Hello, T101 Study&quot; &gt; index.html
              nohup busybox httpd -f -p **8080** &amp;
              EOF

  tags = {
    Name = &quot;Single-WebSrv&quot;
  }
}

resource &quot;**aws_security_group&quot;** &quot;instance&quot; {
  name = **var**.security_group_name

  **ingress** {
    from_port   = 8080
    to_port     = 8080
    protocol    = &quot;tcp&quot;
    cidr_blocks = [&quot;0.0.0.0/0&quot;]
  }
}

**variable** &quot;security_group_name&quot; {
  description = &quot;The name of the security group&quot;
  type        = string
  default     = &quot;terraform-example-instance&quot;
}

**output** &quot;public_ip&quot; {
  value       = aws_instance.example.public_ip
  description = &quot;The public IP of the Instance&quot;
}</code></pre>
<pre><code class="language-bash">$ tf apply
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

public_ip = &quot;54.180.106.217&quot;</code></pre>
<aside>
💡 만약 "빈번하게 서버의 포트가 변경되면" 어떻게 해야 될까요? 이런 "불편함을 해결"하려면 어떻게 해야 될까요?
</aside>

<p>→ <code>user_data_replace_on_change = false</code> 옵션을 추가한다!</p>
<p>코드를 아래와 같이 변경한다.</p>
<pre><code class="language-go">resource &quot;aws_instance&quot; &quot;example&quot; {
  ami                    = &quot;ami-0c9c942bd7bf113a2&quot;
    ...

  user_data_replace_on_change = false
  tags = {
    Name = &quot;Single-WebSrv&quot;
  }
}
...
resource &quot;aws_security_group&quot; &quot;instance&quot; {
  name = var.security_group_name
    ...
    # lifecycle을 추가하면 다운타임을 줄일 수 있다.
    lifecycle {
    create_before_destroy = true
  }
}</code></pre>
<p>변경하기 이전과 이후에 생성과정을 보면 다른 것을 알 수 있다. 이전처럼 파괴하고 재생성이 아닌, 변경된 값을 업데이트한다.</p>
<pre><code class="language-bash"># 이전
aws_security_group.instance: Destroying... [id=sg-03c90b3d559abb123]
aws_security_group.instance: Destruction complete after 1s
aws_security_group.instance: Creating...
aws_security_group.instance: Creation complete after 1s [id=sg-0de633908986b76ad]
# 라이프사이클 적용 후
...
aws_security_group.instance: Modifying... [id=sg-0de633908986b76ad]
aws_security_group.instance: Modifications complete after 1s [id=sg-0de633908986b76ad]</code></pre>
<aside>
💡 만약  "user_data_replace_on_change = false" 설정 상태에서 userdata 를 변경 후 apply 하면 어떻게 될까요?

</aside>

<p>이 설정은 <strong><code>user_data</code></strong>가 변경될 때 EC2 인스턴스를 새로 생성할 것인지, 아니면 기존 인스턴스를 유지할 것인지를 결정한다. <code>false</code>는 인스턴스를 유지하는 옵션이며, 인스턴스를 유지한다.</p>
<pre><code class="language-bash"># 적용 전
aws_instance.example: Destroying... [id=i-0794bd9bb343a948b]
...
aws_instance.example: Creating...

# &quot;user_data_replace_on_change = false&quot; 적용 후
aws_instance.example: Modifying... [id=i-0568341d533d4ca58]
aws_instance.example: Still modifying... [id=i-0568341d533d4ca58, 10s elapsed]
aws_instance.example: Still modifying... [id=i-0568341d533d4ca58, 20s elapsed]</code></pre>
<h3 id="테라폼-문법-설명">테라폼 문법 설명</h3>
<p>Terraform 블록, 아래와 같이 내용을 잘 정리해주셨다.</p>
<ul>
<li><strong><em>오늘 실행하던, 3년 후에 실행하던 동일한 결과를 얻을 수 있어야 한다! (Desired State + Immutable)</em></strong></li>
</ul>
<pre><code class="language-go">terraform {
  required_version = &quot;~&gt; 1.3.0&quot; # 테라폼 버전

  required_providers { # 프로바이더 버전을 나열
    random = {
      version = &quot;&gt;= 3.0.0, &lt; 3.1.0&quot;
    }
    aws = {
      version = &quot;4.2.0&quot;
    }
  }

  cloud { # Cloud/Enterprise 같은 원격 실행을 위한 정보 [참고: Docs]
    organization = &quot;&lt;MY_ORG_NAME&gt;&quot;
    workspaces {
      name = &quot;my-first-workspace&quot;
    }
  }

  backend &quot;local&quot; { # state를 보관하는 위치를 지정 [참고: Docs, local, remote, s3]
    path = &quot;relative/path/to/terraform.tfstate&quot;
  }
}</code></pre>
<ul>
<li>테라폼 0.13 버전 이전에는 provider 블록에 함께 버전을 명시했지만 해당 버전 이후 프로바이더 버전은 terraform 블록에서 <code>required_providers</code>에 정의</li>
</ul>
<pre><code class="language-go">terraform {
  cloud {
    hostname = &quot;[app.terraform.io](http://app.terraform.io/)&quot;
    organization = &quot;my-org&quot;
    workspades = {
       name = &quot;my-app-prod&quot;
    }
  }
}</code></pre>
<h3 id="backend">Backend</h3>
<p>협업을 위해서는 s3 등 원격으로 저장해서 관리함, 기본적으로 lock을 지원한다. 로컬에서 간단하게 테스트할 수 있는 데, 로컬에서 <code>apply</code> 명령어를 실행하고, 승인을 기다릴 때  ls -al 명령어로 작업 디렉터리의 파일을 확인하면 <code>.terraform.tfstate.lock.info</code> 파일이 생성된 것을 확인할 수 있다. </p>
<pre><code class="language-bash">cat .terraform.tfstate.lock.info | jq .
{
  &quot;ID&quot;: &quot;b4dbfee6-a28f-04da-d235-5591414dbcbc&quot;,
  &quot;Operation&quot;: &quot;OperationTypeApply&quot;,
  &quot;Info&quot;: &quot;&quot;,
  &quot;Who&quot;: &quot;kane@kanes-MacBook-Pro.local&quot;,
  &quot;Version&quot;: &quot;1.5.6&quot;,
  &quot;Created&quot;: &quot;2023-08-27T14:07:14.110318Z&quot;,
  &quot;Path&quot;: &quot;terraform.tfstate&quot;
}</code></pre>
<ul>
<li>추가 옵션1 (<strong>이전 구성 유지</strong>) : <strong><code>-migrate-state</code></strong>는 terraform.tfstate의 이전 구성에서 최신의 state 스냅샷을 읽고 기록된 정보를 새 구성으로 전환한다.</li>
<li>추가 옵션2 (<strong>새로 초기화</strong>) : <code>-reconfigure</code>는 init을 실행하기 전에 terraform.tfstate 파일을 삭제해 테라폼을 처음 사용할 때처럼 이 작업 공간(디렉터리)을 초기화 하는 동작이다.</li>
</ul>
<h3 id="tfstate">.tfstate</h3>
<p>Terraform의 <strong><code>.tfstate</code></strong> 파일 내의 <strong><code>serial</code></strong> 값은 상태 파일의 버전을 나타내며, 동시성 제어와 데이터 무결성 확인에도 중요한 역할을 합니다. 이 값은 Terraform 명령이 실행될 때마다 자동으로 증가하여, 상태 파일의 최신성과 일관성을 유지합니다. 그렇기에 backup 파일이 현재 state 파일보다 serial 번호가 낮다.</p>
<h3 id="도전과제1-ec2-웹-서버-배포">도전과제1 (<strong>EC2 웹 서버 배포<em>)</em></strong></h3>
<p>위의 EC2 실습에서 user_data 부분만 변경했다. </p>
<pre><code class="language-go">user_data = &lt;&lt;-EOF
              #!/bin/bash
              echo &quot;T101 Study Kane&quot; &gt; index.html
              nohup busybox httpd -f -p 8080 &amp;
              EOF</code></pre>
<p><img src="https://velog.velcdn.com/images/han-0315/post/c5f02ee5-8f33-4217-ae07-5fa3d7b7fbc1/image.png" alt=""></p>
<h3 id="도전과제2-backend">도전과제2 (Backend)</h3>
<p><strong>AWS S3/DynamoDB 백엔드</strong> </p>
<p>아래는 관련 코드이다.</p>
<pre><code class="language-go">provider &quot;aws&quot; {
  profile = &quot;eks&quot;
  region  = &quot;ap-northeast-2&quot;
}

resource &quot;aws_s3_bucket&quot; &quot;mys3bucket&quot; {
  bucket = &quot;kane-t101study-tfstate&quot;
}

# Enable versioning so you can see the full revision history of your state files
resource &quot;aws_s3_bucket_versioning&quot; &quot;mys3bucket_versioning&quot; {
  bucket = aws_s3_bucket.mys3bucket.id
  versioning_configuration {
    status = &quot;Enabled&quot;
  }
}

resource &quot;aws_dynamodb_table&quot; &quot;mydynamodbtable&quot; {
  name         = &quot;terraform-locks&quot;
  billing_mode = &quot;PAY_PER_REQUEST&quot;
  hash_key     = &quot;LockID&quot;

  attribute {
    name = &quot;LockID&quot;
    type = &quot;S&quot;
  }
}

output &quot;s3_bucket_arn&quot; {
  value       = aws_s3_bucket.mys3bucket.arn
  description = &quot;The ARN of the S3 bucket&quot;
}

output &quot;dynamodb_table_name&quot; {
  value       = aws_dynamodb_table.mydynamodbtable.name
  description = &quot;The name of the DynamoDB table&quot;
}
</code></pre>
<p>EC2를 배포하는 코드에 아래와 같은 원격 backend를 설정한다.</p>
<pre><code class="language-go">terraform {
  backend &quot;s3&quot; {
    bucket = &quot;kane-t101study-tfstate&quot;
    key    = &quot;dev/terraform.tfstate&quot;
    region = &quot;ap-northeast-2&quot;
    dynamodb_table = &quot;terraform-locks&quot;
    # encrypt        = true
  }
}</code></pre>
<p>배포한 후, AWS 콘솔에 접속하면 아래와 같이 table을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/21b1bc61-2539-4c5e-9a64-02486d9d4355/image.png" alt=""></p>
<p>이제, s3를 모니터링하며 Terraform을 배포하여 state 파일이 정상적으로 변경되는 지 확인한다.</p>
<pre><code class="language-bash">while true; do aws s3 ls s3://$NICKNAME-t101study-tfstate --recursive --human-readable --summarize ; echo &quot;------------------------------&quot;; date; sleep 1; done

Total Objects: 0
   Total Size: 0 Bytes
------------------------------
Mon Aug 28 00:50:18 KST 2023

Total Objects: 0
   Total Size: 0 Bytes
------------------------------
...
# 리소스 생성
------------------------------
Mon Aug 28 00:53:16 KST 2023
2023-08-28 00:50:56   21.1 KiB dev/terraform.tfstate

Total Objects: 1
   Total Size: 21.1 KiB
------------------------------
Mon Aug 28 00:53:17 KST 2023
2023-08-28 00:53:18   22.4 KiB dev/terraform.tfstate
------------------------------
...
# 리소스 업데이트
------------------------------
Total Objects: 1
   Total Size: 22.4 KiB
------------------------------
# 리소스 삭제
...
------------------------------
Mon Aug 28 00:56:04 KST 2023
2023-08-28 00:56:03  180 Bytes dev/terraform.tfstate

Total Objects: 1
   Total Size: 180 Bytes
------------------------------
Mon Aug 28 00:56:05 KST 2023</code></pre>
<p>아래와 같이 콘솔에서도 확인할 수 있다. 생성 순서는 아래에서부터 위 방향이다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/3c80559c-3c40-4be1-8600-ab45e84f5281/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EKS 스터디 7주차]]></title>
            <link>https://velog.io/@han-0315/EKS-7%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@han-0315/EKS-7%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 08 Jun 2023 14:57:58 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<p>먼저, 이번 과제를 마지막으로 EKS 스터디는 끝이났다. 스터디 완주와 귀여운 Go 캐릭터들이 모여 기쁘다. 스터디를 하면서 많은 일들이 있었다. (실수로 AWS 토큰을 노출해서, 메일도 날라오고..) EKS 실무 관련 경험이 없었는 데, 정말 많은 이론을 배우고, 따라하며 익힐 수 있었다. 앞으로 복습을 하며 추후 예정으로 미뤄뒀던 실습과 이론을 진행하면서 내용을 세부화해서 포스팅할 예정이다.</p>
<p><strong>그동안 스터디를 준비해주신 가시다님과 CloudNet 팀 덕분에 많이 배울 수 있었다. 정말 감사합니다 ㅎㅎ!!</strong> 
스터디에 관심있으신 분은 <a href="https://gasidaseo.notion.site/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">CloudNet</a>에서 확인할 수 있습니다!</p>
<p>이번 주차의 주제는 Automation이다. ACK와 Flux에 대해 실습을 진행하고, 아주 간단히 ArgoCD를 진행한다. </p>
<h3 id="aws-controller-for-kubernetes-ack">AWS Controller for Kubernetes (ACK)</h3>
<p>AWS에서 제작한 오픈소스 툴이며, 클라우드를 잘 모르는 개발자?에게 좋을 듯하다. 하지만 아직 정식으로 오픈한 서비스가 많아보이진 않는다. 
자세한 내용은 <a href="https://github.com/aws-controllers-k8s/community">공식문서</a> 참고!</p>
<blockquote>
<p><code>ACK</code> : <strong>aws</strong> 서비스 리소스를 <strong>k8s</strong> 에서 직접 <strong>정의</strong>하고 <strong>사</strong>용 할 수 있음</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/han-0315/post/8ae3cdfe-0c69-40a7-aa58-f9177187419c/image.png" alt=""></p>
<ul>
<li><strong>S3 실습 진행</strong></li>
</ul>
<p>우선, ACK S3 controller를 받는다.</p>
<pre><code class="language-bash">
$export SERVICE=s3
$export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep &#39;&quot;tag_name&quot;:&#39; | cut -d&#39;&quot;&#39; -f4 | cut -c 2-)
$helm pull oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION
Pulled: public.ecr.aws/aws-controllers-k8s/s3-chart:1.0.4
Digest: sha256:9cd8574c78c7f226a2520a423a447afd02366a3ec87b5d1ba910992da3e264b8
$tar xzvf $SERVICE-chart-$RELEASE_VERSION.tgz
s3-chart/Chart.yaml
s3-chart/values.yaml
s3-chart/values.schema.json
s3-chart/templates/NOTES.txt
s3-chart/templates/_helpers.tpl
s3-chart/templates/cluster-role-binding.yaml
s3-chart/templates/cluster-role-controller.yaml
s3-chart/templates/deployment.yaml
s3-chart/templates/metrics-service.yaml
s3-chart/templates/role-reader.yaml
s3-chart/templates/role-writer.yaml
s3-chart/templates/service-account.yaml
s3-chart/crds/s3.services.k8s.aws_buckets.yaml
s3-chart/crds/services.k8s.aws_adoptedresources.yaml
s3-chart/crds/services.k8s.aws_fieldexports.yaml

$tree ~/$SERVICE-chart
/root/s3-chart
├── Chart.yaml
├── crds
│   ├── s3.services.k8s.aws_buckets.yaml
│   ├── services.k8s.aws_adoptedresources.yaml
│   └── services.k8s.aws_fieldexports.yaml
├── templates
│   ├── cluster-role-binding.yaml
│   ├── cluster-role-controller.yaml
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── metrics-service.yaml
│   ├── NOTES.txt
│   ├── role-reader.yaml
│   ├── role-writer.yaml
│   └── service-account.yaml
├── values.schema.json
└── values.yaml

2 directories, 15 files</code></pre>
<p><strong>받은 helm 차트를 배포!</strong></p>
<pre><code class="language-bash">$export ACK_SYSTEM_NAMESPACE=ack-system
$export AWS_REGION=ap-northeast-2
$
$helm install --create-namespace -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller --set aws.region=&quot;$AWS_REGION&quot; ~/$SERVICE-chart
NAME: ack-s3-controller
LAST DEPLOYED: Tue Jun  6 15:41:29 2023
NAMESPACE: ack-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
s3-chart has been installed.
This chart deploys &quot;public.ecr.aws/aws-controllers-k8s/s3-controller:1.0.4&quot;.

Check its status by running:
  kubectl --namespace ack-system get pods -l &quot;app.kubernetes.io/instance=ack-s3-controller&quot;

You are now able to create Amazon Simple Storage Service (S3) resources!

The controller is running in &quot;cluster&quot; mode.
The controller is configured to manage AWS resources in region: &quot;ap-northeast-2&quot;

Visit https://aws-controllers-k8s.github.io/community/reference/ for an API
reference of all the resources that can be created using this controller.

For more information on the AWS Controllers for Kubernetes (ACK) project, visit:
https://aws-controllers-k8s.github.io/community/
</code></pre>
<p>이제, 배포를 확인한다.</p>
<pre><code class="language-bash">$helm list --namespace $ACK_SYSTEM_NAMESPACE
NAME                 NAMESPACE     REVISION    UPDATED                                    STATUS      CHART             APP VERSION
ack-s3-controller    ack-system    1           2023-06-06 15:41:29.434398105 +0900 KST    deployed    s3-chart-1.0.4    1.0.4
$kubectl -n ack-system get pods
NAME                                          READY   STATUS              RESTARTS   AGE
ack-s3-controller-s3-chart-7c55c6657d-2dl4x   0/1     ContainerCreating   0          8s

$kubectl get crd | grep $SERVICE
buckets.s3.services.k8s.aws                  2023-06-06T06:41:27Z

$kubectl get all -n ack-system
NAME                                              READY   STATUS    RESTARTS   AGE
pod/ack-s3-controller-s3-chart-7c55c6657d-2dl4x   1/1     Running   0          15s

NAME                                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ack-s3-controller-s3-chart   1/1     1            1           15s

NAME                                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/ack-s3-controller-s3-chart-7c55c6657d   1         1         1       15s
$kubectl describe sa -n ack-system ack-s3-controller
Name:                ack-s3-controller
Namespace:           ack-system
Labels:              app.kubernetes.io/instance=ack-s3-controller
                     app.kubernetes.io/managed-by=Helm
                     app.kubernetes.io/name=s3-chart
                     app.kubernetes.io/version=1.0.4
                     helm.sh/chart=s3-chart-1.0.4
                     k8s-app=s3-chart
Annotations:         meta.helm.sh/release-name: ack-s3-controller
                     meta.helm.sh/release-namespace: ack-system
Image pull secrets:  &lt;none&gt;
Mountable secrets:   &lt;none&gt;
Tokens:              &lt;none&gt;
Events:              &lt;none&gt;
$echo $ACK_SYSTEM_NAMESPACE
ack-system
$echo $RELEASE_VERSION
1.0.4</code></pre>
<p><strong>IRSA 설정 - S3 Full Access</strong></p>
<p>파드에 권한이 없다면, AWS S3에 접근할 수 없다. eksctl를 통해 ack-s3-controller 서비스 어카운트에 정책을 붙인다.</p>
<pre><code class="language-bash">$eksctl create iamserviceaccount \
&gt;   --name ack-$SERVICE-controller \
&gt;   --namespace ack-system \
&gt;   --cluster $CLUSTER_NAME \
&gt;   --attach-policy-arn $(aws iam list-policies --query &#39;Policies[?PolicyName==`AmazonS3FullAccess`].Arn&#39; --output text) \
&gt;   --override-existing-serviceaccounts --approve

2023-06-06 15:42:36 [ℹ]  1 existing iamserviceaccount(s) (kube-system/aws-load-balancer-controller) will be excluded
2023-06-06 15:42:36 [ℹ]  1 iamserviceaccount (ack-system/ack-s3-controller) was included (based on the include/exclude rules)
2023-06-06 15:42:36 [!]  metadata of serviceaccounts that exist in Kubernetes will be updated, as --override-existing-serviceaccounts was set
2023-06-06 15:42:36 [ℹ]  1 task: {
    2 sequential sub-tasks: {
        create IAM role for serviceaccount &quot;ack-system/ack-s3-controller&quot;,
        create serviceaccount &quot;ack-system/ack-s3-controller&quot;,
    } }2023-06-06 15:42:36 [ℹ]  building iamserviceaccount stack &quot;eksctl-myeks-addon-iamserviceaccount-ack-system-ack-s3-controller&quot;
2023-06-06 15:42:36 [ℹ]  deploying stack &quot;eksctl-myeks-addon-iamserviceaccount-ack-system-ack-s3-controller&quot;
2023-06-06 15:42:36 [ℹ]  waiting for CloudFormation stack &quot;eksctl-myeks-addon-iamserviceaccount-ack-system-ack-s3-controller&quot;
2023-06-06 15:43:06 [ℹ]  waiting for CloudFormation stack &quot;eksctl-myeks-addon-iamserviceaccount-ack-system-ack-s3-controller&quot;
2023-06-06 15:43:59 [ℹ]  waiting for CloudFormation stack &quot;eksctl-myeks-addon-iamserviceaccount-ack-system-ack-s3-controller&quot;
2023-06-06 15:43:59 [ℹ]  serviceaccount &quot;ack-system/ack-s3-controller&quot; already exists
2023-06-06 15:43:59 [ℹ]  updated serviceaccount &quot;ack-system/ack-s3-controller&quot;</code></pre>
<p>웹 콘솔에서도 IAM 서비스어카운트가 생성된 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/894eaa53-2f5c-426f-b4b6-fb101673469b/image.png" alt=""></p>
<p>터미널에서 자세한 내용 확인</p>
<pre><code class="language-bash"># 생성 확인
$eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE    NAME                ROLE ARN
ack-system    ack-s3-controller        arn:aws:iam::011116120544:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-CGT73XW0JNSS
kube-system    aws-load-balancer-controller    arn:aws:iam::011116120544:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-1OBUCIYN8PY2F

$k get sa -n ack-system
NAME                SECRETS   AGE
ack-s3-controller   0         3m39s
default             0         3m39s

$eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE    NAME                ROLE ARN
ack-system    ack-s3-controller        arn:aws:iam::011116120544:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-CGT73XW0JNSS
kube-system    aws-load-balancer-controller    arn:aws:iam::011116120544:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-1OBUCIYN8PY2F
$kubectl get sa -n ack-system

NAME                SECRETS   AGE
ack-s3-controller   0         4m12s
default             0         4m12s
$
$kubectl describe sa ack-$SERVICE-controller -n ack-system
Name:                ack-s3-controller
Namespace:           ack-system
Labels:              app.kubernetes.io/instance=ack-s3-controller
                     app.kubernetes.io/managed-by=eksctl
                     app.kubernetes.io/name=s3-chart
                     app.kubernetes.io/version=1.0.4
                     helm.sh/chart=s3-chart-1.0.4
                     k8s-app=s3-chart
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::011116120544:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-CGT73XW0JNSS
                     meta.helm.sh/release-name: ack-s3-controller
                     meta.helm.sh/release-namespace: ack-system
Image pull secrets:  &lt;none&gt;
Mountable secrets:   &lt;none&gt;
Tokens:              &lt;none&gt;
Events:              &lt;none&gt;
$kubectl -n ack-system rollout restart deploy ack-$SERVICE-controller-$SERVICE-chart
deployment.apps/ack-s3-controller-s3-chart restarted
$kubectl describe pod -n ack-system -l k8s-app=$SERVICE-chart
Name:             ack-s3-controller-s3-chart-5d5bd5d57c-sfbb5
Namespace:        ack-system
Priority:         0
Service Account:  ack-s3-controller
Node:             ip-192-168-2-99.ap-northeast-2.compute.internal/192.168.2.99
Start Time:       Tue, 06 Jun 2023 15:46:23 +0900
Labels:           app.kubernetes.io/instance=ack-s3-controller
                  app.kubernetes.io/managed-by=Helm
                  app.kubernetes.io/name=s3-chart
                  k8s-app=s3-chart
                  pod-template-hash=5d5bd5d57c
Annotations:      kubectl.kubernetes.io/restartedAt: 2023-06-06T15:46:23+09:00
                  kubernetes.io/psp: eks.privileged
                  seccomp.security.alpha.kubernetes.io/pod: runtime/default
Status:           Running
SeccompProfile:   RuntimeDefault
IP:               192.168.2.89
IPs:
  IP:           192.168.2.89
Controlled By:  ReplicaSet/ack-s3-controller-s3-chart-5d5bd5d57c
Containers:
  controller:
    Container ID:  containerd://1af33fc46028ce439b7b7dc809267b3bbf84f22ba2e7681f6929ddc0b68063b8
    Image:         public.ecr.aws/aws-controllers-k8s/s3-controller:1.0.4
    Image ID:      public.ecr.aws/aws-controllers-k8s/s3-controller@sha256:c103185184be38ec4d113d99c06889d4facd4025cd5238f141ebbcc0bad8b155
    Port:          8080/TCP
    Host Port:     0/TCP
    Command:
      ./bin/controller
    Args:
      --aws-region
      $(AWS_REGION)
      --aws-endpoint-url
      $(AWS_ENDPOINT_URL)
      --enable-development-logging
      $(ACK_ENABLE_DEVELOPMENT_LOGGING)
      --log-level
      $(ACK_LOG_LEVEL)
      --resource-tags
      $(ACK_RESOURCE_TAGS)
      --watch-namespace
      $(ACK_WATCH_NAMESPACE)
      --deletion-policy
      $(DELETION_POLICY)
    State:          Running
      Started:      Tue, 06 Jun 2023 15:46:28 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     100m
      memory:  128Mi
    Requests:
      cpu:     50m
      memory:  64Mi
    Environment:
      ACK_SYSTEM_NAMESPACE:            ack-system (v1:metadata.namespace)
      AWS_REGION:                      ap-northeast-2
      AWS_ENDPOINT_URL:
      ACK_WATCH_NAMESPACE:
      DELETION_POLICY:                 delete
      ACK_ENABLE_DEVELOPMENT_LOGGING:  false
      ACK_LOG_LEVEL:                   info
      ACK_RESOURCE_TAGS:               services.k8s.aws/controller-version=%CONTROLLER_SERVICE%-%CONTROLLER_VERSION%,services.k8s.aws/namespace=%K8S_NAMESPACE%
      AWS_STS_REGIONAL_ENDPOINTS:      regional
      AWS_ROLE_ARN:                    arn:aws:iam::011116120544:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-CGT73XW0JNSS
      AWS_WEB_IDENTITY_TOKEN_FILE:     /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    Mounts:
      /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-kpfz4 (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  aws-iam-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  86400
  kube-api-access-kpfz4:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       &lt;nil&gt;
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              kubernetes.io/os=linux
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  8s    default-scheduler  Successfully assigned ack-system/ack-s3-controller-s3-chart-5d5bd5d57c-sfbb5 to ip-192-168-2-99.ap-northeast-2.compute.internal
  Normal  Pulled     5s    kubelet            Container image &quot;public.ecr.aws/aws-controllers-k8s/s3-controller:1.0.4&quot; already present on machine
  Normal  Created    5s    kubelet            Created container controller
  Normal  Started    3s    kubelet            Started container controller
</code></pre>
<p><strong>이제, S3 관련 테스트를 진행한다.</strong> </p>
<ol>
<li>Bucket 생성 </li>
</ol>
<pre><code class="language-bash">$export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query &quot;Account&quot; --output text)
$export BUCKET_NAME=my-ack-s3-bucket-$AWS_ACCOUNT_ID
$read -r -d &#39;&#39; BUCKET_MANIFEST &lt;&lt;EOF
&gt; apiVersion: s3.services.k8s.aws/v1alpha1
&gt; kind: Bucket
&gt; metadata:
&gt;   name: $BUCKET_NAME
&gt; spec:
&gt;   name: $BUCKET_NAME
&gt; EOF
$echo &quot;${BUCKET_MANIFEST}&quot; &gt; bucket.yaml
$cat bucket.yaml | yh
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
  name: my-ack-s3-bucket-011116120544
spec:
  name: my-ack-s3-bucket-011116120544

$aws s3 ls

$kubectl create -f bucket.yaml
bucket.s3.services.k8s.aws/my-ack-s3-bucket-011116120544 created

# 생성 확인
$aws s3 ls
2023-06-06 15:48:02 my-ack-s3-bucket-011116120544
$kubectl get buckets
NAME                            AGE
my-ack-s3-bucket-011116120544   10s
$kubectl describe bucket/$BUCKET_NAME | head -6
Name:         my-ack-s3-bucket-011116120544
Namespace:    default
Labels:       &lt;none&gt;
Annotations:  &lt;none&gt;
API Version:  s3.services.k8s.aws/v1alpha1
Kind:         Bucket

$aws s3 ls | grep $BUCKET_NAME
2023-06-06 15:48:02 my-ack-s3-bucket-011116120544
</code></pre>
<ol>
<li><strong>S3 버킷 업데이트 : 태그 정보 입력</strong></li>
</ol>
<pre><code class="language-bash">$read -r -d &#39;&#39; BUCKET_MANIFEST &lt;&lt;EOF
&gt; apiVersion: s3.services.k8s.aws/v1alpha1
&gt; kind: Bucket
&gt; metadata:
&gt;   name: $BUCKET_NAME
&gt; spec:
&gt;   name: $BUCKET_NAME
&gt;   tagging:
&gt;     tagSet:
&gt;     - key: myTagKey
&gt;       value: myTagValue
&gt; EOF
$echo &quot;${BUCKET_MANIFEST}&quot; &gt; bucket.yaml

#S3 버킷 설정 업데이트 실행 : 필요 주석 자동 업뎃 내용이니 무시해도됨!
$kubectl apply -f bucket.yaml
Warning: resource buckets/my-ack-s3-bucket-011116120544 is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
bucket.s3.services.k8s.aws/my-ack-s3-bucket-011116120544 configured
</code></pre>
<p>S3 버킷 업데이트 결과를 확인한다. </p>
<pre><code class="language-bash">$kubectl describe bucket/$BUCKET_NAME | grep Spec: -A5
Spec:
  Name:  my-ack-s3-bucket-011116120544
  Tagging:
    Tag Set:
      Key:    myTagKey
      Value:  myTagValue

$kubectl describe bucket/$BUCKET_NAME | grep Spec: -A5
Spec:
  Name:  my-ack-s3-bucket-011116120544
  Tagging:
    Tag Set:
      Key:    myTagKey
      Value:  myTagValue
</code></pre>
<p>콘솔에서도 값 변경 확인</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/bf8071e4-0f5d-4f1d-ace5-862d2cda893d/image.png" alt=""></p>
<p>이제 실습을 종료하고, 자원을 삭제한다.</p>
<pre><code class="language-bash">$kubectl delete -f bucket.yaml
bucket.s3.services.k8s.aws &quot;my-ack-s3-bucket-011116120544&quot; deleted
$kubectl get bucket/$BUCKET_NAME
Error from server (NotFound): buckets.s3.services.k8s.aws &quot;my-ack-s3-bucket-011116120544&quot; not found
$export SERVICE=s3
$helm uninstall -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller
release &quot;ack-s3-controller&quot; uninstalled
$kubectl delete -f ~/$SERVICE-chart/crds
customresourcedefinition.apiextensions.k8s.io &quot;buckets.s3.services.k8s.aws&quot; deleted
customresourcedefinition.apiextensions.k8s.io &quot;adoptedresources.services.k8s.aws&quot; deleted
customresourcedefinition.apiextensions.k8s.io &quot;fieldexports.services.k8s.aws&quot; deleted
$eksctl delete iamserviceaccount --cluster myeks --name ack-$SERVICE-controller --namespace ack-system
2023-06-06 15:50:31 [ℹ]  1 iamserviceaccount (ack-system/ack-s3-controller) was included (based on the include/exclude rules)
2023-06-06 15:50:31 [ℹ]  1 task: {
    2 sequential sub-tasks: {
        delete IAM role for serviceaccount &quot;ack-system/ack-s3-controller&quot; [async],
        delete serviceaccount &quot;ack-system/ack-s3-controller&quot;,
    } }2023-06-06 15:50:31 [ℹ]  will delete stack &quot;eksctl-myeks-addon-iamserviceaccount-ack-system-ack-s3-controller&quot;
2023-06-06 15:50:31 [ℹ]  serviceaccount &quot;ack-system/ack-s3-controller&quot; was already deleted
$</code></pre>
<h3 id="flux">Flux</h3>
<p>argocd와 유사한 gitops 툴이다. <strong>kustomize에 특화된 도구이며, 테라폼 코드를 실행하는 기능이 있다고 한다.</strong> </p>
<p>하지만, helm or kustomize에 의존적이다.</p>
<blockquote>
<p>자세한 내용은 악분님 블로그 참고 <a href="https://malwareanalysis.tistory.com/612">Blog</a></p>
</blockquote>
<p>Flux 설치한다.</p>
<pre><code class="language-bash">$curl -s https://fluxcd.io/install.sh | sudo bash
[INFO]  Downloading metadata https://api.github.com/repos/fluxcd/flux2/releases/latest
[INFO]  Using 2.0.0-rc.5 as release
[INFO]  Downloading hash https://github.com/fluxcd/flux2/releases/download/v2.0.0-rc.5/flux_2.0.0-rc.5_checksums.txt
[INFO]  Downloading binary https://github.com/fluxcd/flux2/releases/download/v2.0.0-rc.5/flux_2.0.0-rc.5_linux_amd64.tar.gz
[INFO]  Verifying binary download
which: no shasum in (/sbin:/bin:/usr/sbin:/usr/bin)
[INFO]  Installing flux to /usr/local/bin/flux
$. &lt;(flux completion bash)

# 설치 확인
$flux --version
flux version 2.0.0-rc.5
</code></pre>
<p>이제 깃허브와 연동한다. 깃허브 토큰을 발급받아서 진행한다. </p>
<pre><code class="language-bash"># 깃허브 토큰 등록 
$export GITHUB_TOKEN=ghp_mawAkDy...
$export GITHUB_USER=han-03..

# flux 연결
$flux bootstrap github \
&gt;   --owner=$GITHUB_USER \
&gt;   --repository=fleet-infra \
&gt;   --branch=main \
&gt;   --path=./clusters/my-cluster \
&gt;   --personal
► connecting to github.com
✔ repository &quot;https://github.com/han/fleet-infra&quot; created
► cloning branch &quot;main&quot; from Git repository &quot;https://github.com/han/fleet-infra.git&quot;
✔ cloned repository
► generating component manifests
# Warning: &#39;patchesJson6902&#39; is deprecated. Please use &#39;patches&#39; instead. Run &#39;kustomize edit fix&#39; to update your Kustomization automatically.
✔ generated component manifests
✔ committed sync manifests to &quot;main&quot; (&quot;061daf49d8f729dba1dd4bc38023e891df92d225&quot;)
► pushing component manifests to &quot;https://github.com/han/fleet-infra.git&quot;
► installing components in &quot;flux-system&quot; namespace
✔ installed components
✔ reconciled components
► determining if source secret &quot;flux-system/flux-system&quot; exists
► generating source secret
✔ public key: ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzOD...
✔ configured deploy key &quot;flux-system-main-flux-system-./clusters/my-cluster&quot; for &quot;https://github.com/han/fleet-infra&quot;
► applying source secret &quot;flux-system/flux-system&quot;
✔ reconciled source secret
► generating sync manifests
✔ generated sync manifests
✔ committed sync manifests to &quot;main&quot; (&quot;cb4137dd69c7891da32982d5991deb9ebd901278&quot;)
► pushing sync manifests to &quot;https://github.com/han/fleet-infra.git&quot;
► applying sync manifests
✔ reconciled sync configuration
◎ waiting for Kustomization &quot;flux-system/flux-system&quot; to be reconciled
✔ Kustomization reconciled successfully
► confirming components are healthy
✔ helm-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ notification-controller: deployment ready
✔ source-controller: deployment ready
✔ all components are healthy

#설치 확인
$kubectl get pods -n flux-system
NAME                                       READY   STATUS    RESTARTS   AGE
helm-controller-fbdd59577-dgm6p            1/1     Running   0          24s
kustomize-controller-6b67b54cf8-xvjz4      1/1     Running   0          24s
notification-controller-78f4869c94-8dc52   1/1     Running   0          24s
source-controller-75db64d9f7-jhxmk         1/1     Running   0          24s

$kubectl get-all -n flux-system
NAME                                                               NAMESPACE    AGE
configmap/kube-root-ca.crt                                         flux-system  29s
endpoints/notification-controller                                  flux-system  26s
endpoints/source-controller                                        flux-system  26s
endpoints/webhook-receiver                                         flux-system  26s
...
$kubectl get crd | grep fluxc
alerts.notification.toolkit.fluxcd.io        2023-06-06T06:58:28Z
buckets.source.toolkit.fluxcd.io             2023-06-06T06:58:28Z
gitrepositories.source.toolkit.fluxcd.io     2023-06-06T06:58:28Z
helmcharts.source.toolkit.fluxcd.io          2023-06-06T06:58:28Z
helmreleases.helm.toolkit.fluxcd.io          2023-06-06T06:58:28Z
helmrepositories.source.toolkit.fluxcd.io    2023-06-06T06:58:28Z
kustomizations.kustomize.toolkit.fluxcd.io   2023-06-06T06:58:28Z
ocirepositories.source.toolkit.fluxcd.io     2023-06-06T06:58:29Z
providers.notification.toolkit.fluxcd.io     2023-06-06T06:58:29Z
receivers.notification.toolkit.fluxcd.io     2023-06-06T06:58:29Z

# 리포지토리 확인
$kubectl get gitrepository -n flux-system
NAME          URL                                         AGE   READY   STATUS
flux-system   ssh://git@github.com/han/fleet-infra   25s   True    stored artifact for revision &#39;main@sha1:cb4137dd69c7891da32982d5991deb9ebd901278&#39;
</code></pre>
<p>Gitops 도구를 설치한다.</p>
<pre><code class="language-bash">$curl --silent --location &quot;https://github.com/weaveworks/weave-gitops/releases/download/v0.24.0/gitops-$(uname)-$(uname -m).tar.gz&quot; | tar xz -C /tmp
$sudo mv /tmp/gitops /usr/local/bin

$gitops version
To improve our product, we would like to collect analytics data. You can read more about what data we collect here: https://docs.gitops.weave.works/docs/feedback-and-telemetry/
Would you like to turn on analytics to help us improve our product: Y
Current Version: 0.24.0
GitCommit: cc1d0e680c55e0aaf5bfa0592a0a454fb2064bc1
BuildTime: 2023-05-24T16:29:14Z
Branch: releases/v0.24.0
$PASSWORD=&quot;password&quot;

$gitops create dashboard ww-gitops --password=$PASSWORD
✚ Generating GitOps Dashboard manifests ...
► Creating GitOps Dashboard objects ...
✚ Generating GitOps Dashboard manifests ...
✔ Generated GitOps Dashboard manifests
► Checking for a cluster in the kube config ...
► Checking if Flux is already installed ...
► Getting Flux version ...
✔ Flux &amp;{v2.0.0-rc.5  flux-system} is already installed
► Applying GitOps Dashboard manifests
► Installing the GitOps Dashboard ...
✔ GitOps Dashboard has been installed
► Request reconciliation of dashboard (timeout 3m0s) ...
◎ Waiting for GitOps Dashboard reconciliation
✔ GitOps Dashboard ww-gitops is ready
✔ Installed GitOps Dashboard

$flux -n flux-system get helmrelease
NAME         REVISION    SUSPENDED    READY    MESSAGE
ww-gitops    4.0.22      False        True     Release reconciliation succeeded

$kubectl -n flux-system get pod,svc
NAME                                           READY   STATUS    RESTARTS   AGE
pod/helm-controller-fbdd59577-dgm6p            1/1     Running   0          2m15s
pod/kustomize-controller-6b67b54cf8-xvjz4      1/1     Running   0          2m15s
pod/notification-controller-78f4869c94-8dc52   1/1     Running   0          2m15s
pod/source-controller-75db64d9f7-jhxmk         1/1     Running   0          2m15s
pod/ww-gitops-weave-gitops-66dc44989f-wmmd9    1/1     Running   0          46s

NAME                              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/notification-controller   ClusterIP   10.100.0.14      &lt;none&gt;        80/TCP     2m15s
service/source-controller         ClusterIP   10.100.20.46     &lt;none&gt;        80/TCP     2m15s
service/webhook-receiver          ClusterIP   10.100.76.241    &lt;none&gt;        80/TCP     2m15s
service/ww-gitops-weave-gitops    ClusterIP   10.100.101.129   &lt;none&gt;        9001/TCP   46s

# External DNS 연결을 위한 ingress 설정
$CERT_ARN=`aws acm list-certificates --query &#39;CertificateSummaryList[].CertificateArn[]&#39; --output text`
$echo $CERT_ARN
arn:aws:acm:ap-northeast-2:011116120544:certificate/836b6dfa-0955-4401-a721-ecd8689b6025

$cat &lt;&lt;EOT &gt; gitops-ingress.yaml
&gt; apiVersion: networking.k8s.io/v1
&gt; kind: Ingress
&gt; metadata:
&gt;   name: gitops-ingress
&gt;   annotations:
&gt;     alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
&gt;     alb.ingress.kubernetes.io/group.name: study
&gt;     alb.ingress.kubernetes.io/listen-ports: &#39;[{&quot;HTTPS&quot;:443}, {&quot;HTTP&quot;:80}]&#39;
&gt;     alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
&gt;     alb.ingress.kubernetes.io/scheme: internet-facing
&gt;     alb.ingress.kubernetes.io/ssl-redirect: &quot;443&quot;
&gt;     alb.ingress.kubernetes.io/success-codes: 200-399
&gt;     alb.ingress.kubernetes.io/target-type: ip
&gt; spec:
&gt;   ingressClassName: alb
&gt;   rules:
&gt;   - host: gitops.$MyDomain
&gt;     http:
&gt;       paths:
&gt;       - backend:
&gt;           service:
&gt;             name: ww-gitops-weave-gitops
&gt;             port:
&gt;               number: 9001
&gt;         path: /
&gt;         pathType: Prefix
&gt; EOT

$kubectl apply -f gitops-ingress.yaml -n flux-system
ingress.networking.k8s.io/gitops-ingress created

$kubectl get ingress -n flux-system
NAME             CLASS   HOSTS                 ADDRESS                                                         PORTS   AGE
gitops-ingress   alb     gitops.dongmin.link   myeks-ingress-alb-1372943946.ap-northeast-2.elb.amazonaws.com   80      3s

#GitOps 접속 정보 확인 &gt;&gt; 웹 접속 후 정보 확인
$echo -e &quot;GitOps Web https://gitops.$MyDomain&quot;
GitOps Web https://gitops.dongmin.link
</code></pre>
<p>웹 UI 모습</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/0a222184-36a6-4eeb-8a07-9be1189f797a/image.png" alt=""></p>
<p>소스 생성 방식</p>
<blockquote>
<p>소스 생성 : 유형 - git, helm, oci, bucket
<code>flux create source {소스 유형}</code></p>
</blockquote>
<p>테스트 진행</p>
<pre><code class="language-bash">#악분(최성욱)님이 준비한 repo로 git 소스 생성
$GITURL=&quot;https://github.com/sungwook-practice/fluxcd-test.git&quot;
$flux create source git nginx-example1 --url=$GITURL --branch=main --interval=30s
✚ generating GitRepository source
► applying GitRepository source
✔ GitRepository source created
◎ waiting for GitRepository source reconciliation
✔ GitRepository source reconciliation completed
✔ fetched revision: main@sha1:4478b54cb7a8eaf1ee2665e2b3dd5bcfd55e9da9

$flux get sources git
NAME              REVISION              SUSPENDED    READY    MESSAGE
flux-system       main@sha1:cb4137dd    False        True     stored artifact for revision &#39;main@sha1:cb4137dd&#39;
nginx-example1    main@sha1:4478b54c    False        True     stored artifact for revision &#39;main@sha1:4478b54c&#39;

# 악분님 repo와 연결된 것을 확인할 수 있음
$kubectl -n flux-system get gitrepositories
NAME             URL                                                    AGE   READY   STATUS
flux-system      ssh://git@github.com/han../fleet-infra              40m   True    stored artifact for revision &#39;main@sha1:cb4137dd69c7891da32982d5991deb9ebd901278&#39;
nginx-example1   https://github.com/sungwook-practice/fluxcd-test.git   37s   True    stored artifact for revision &#39;main@sha1:4478b54cb7a8eaf1ee2665e2b3dd5bcfd55e9da9&#39;

# 배포
$flux create kustomization nginx-example1 --target-namespace=default --interval=1m --source=nginx-example1 --path=&quot;./nginx&quot; --health-check-timeout=2m
✚ generating Kustomization
► applying Kustomization
✔ Kustomization created
◎ waiting for Kustomization reconciliation
✔ Kustomization nginx-example1 is ready
✔ applied revision main@sha1:4478b54cb7a8eaf1ee2665e2b3dd5bcfd55e9da9

# 배포 확인
$flux get kustomizations
NAME              REVISION              SUSPENDED    READY    MESSAGE
flux-system       main@sha1:cb4137dd    False        True     Applied revision: main@sha1:cb4137dd
nginx-example1    main@sha1:4478b54c    False        True     Applied revision: main@sha1:4478b54c
</code></pre>
<p>이제 Repo를 삭제한다. 처음 애플리케이션을 생성할 때 <code>prune</code> 옵션에 따라 리소스 삭제 유무가 달라진다. </p>
<pre><code class="language-bash">$flux delete kustomization nginx-example1
Are you sure you want to delete this kustomization: y
► deleting kustomization nginx-example1 in flux-system namespace
✔ kustomization deleted

# flux kustomizations만 삭제되고, EKS 리소스가 삭제가 되지 않음
$flux get kustomizations
NAME           REVISION              SUSPENDED    READY    MESSAGE
flux-system    main@sha1:cb4137dd    False        True     Applied revision: main@sha1:cb4137dd

$kubectl get pod,svc nginx-example1
NAME                 READY   STATUS    RESTARTS   AGE
pod/nginx-example1   1/1     Running   0          98s
NAME                     TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/nginx-example1   ClusterIP   10.100.61.6   &lt;none&gt;        80/TCP    98s

# 옵션변경
# flux 애플리케이션 다시 생성 :  --prune 옵션 true
$flux create kustomization nginx-example1 \
&gt;   --target-namespace=default \
&gt;   --prune=true \
&gt;   --interval=1m \
&gt;   --source=nginx-example1 \
&gt;   --path=&quot;./nginx&quot; \
&gt;   --health-check-timeout=2m
✚ generating Kustomization
► applying Kustomization
✔ Kustomization created
◎ waiting for Kustomization reconciliation
✔ Kustomization nginx-example1 is ready
✔ applied revision main@sha1:4478b54cb7a8eaf1ee2665e2b3dd5bcfd55e9da9

$flux get kustomizations
NAME              REVISION              SUSPENDED    READY    MESSAGE
flux-system       main@sha1:cb4137dd    False        True     Applied revision: main@sha1:cb4137dd
nginx-example1    main@sha1:4478b54c    False        True     Applied revision: main@sha1:4478b54c

# 리소스까지 삭제되는 모습
$flux delete kustomization nginx-example1
Are you sure you want to delete this kustomization: y
► deleting kustomization nginx-example1 in flux-system namespace
✔ kustomization deleted

$flux get kustomizations
NAME           REVISION              SUSPENDED    READY    MESSAGE
flux-system    main@sha1:cb4137dd    False        True     Applied revision: main@sha1:cb4137dd
$kubectl get pod,svc nginx-example1
Error from server (NotFound): pods &quot;nginx-example1&quot; not found
Error from server (NotFound): services &quot;nginx-example1&quot; not found
</code></pre>
<p>테스트를 마무리 하고 자원을 삭제한다. </p>
<pre><code class="language-bash">$flux delete source git nginx-example1
Are you sure you want to delete this source git: y
? Are you sure you want to delete this source git? [y/N] y█
✔ source git deleted
$flux get sources git
NAME           REVISION              SUSPENDED    READY    MESSAGE
flux-system    main@sha1:cb4137dd    False        True     stored artifact for revision &#39;main@sha1:cb4137dd&#39;
$
$kubectl -n flux-system get gitrepositories
NAME          URL                                         AGE   READY   STATUS
flux-system   ssh://git@github.com/han/fleet-infra   44m   True    stored artifact for revision &#39;main@sha1:cb4137dd69c7891da32982d5991deb9ebd901278&#39;</code></pre>
<h3 id="argocd">ArgoCD</h3>
<p>쿠버네티스 GitOps 환경에서 지속적인 배포를 위한 오픈소스 도구이다. CNCF 프로젝트 중 하나이다. </p>
<p>자세한 내용은 <a href="https://argoproj.github.io/cd/">GitHub</a> 참고</p>
<p>여기서는 간단하게 ArgoCD를 배포하고, 로그인만 진행합니다.</p>
<pre><code class="language-bash"># argo CD 생성
$kubectl create namespace argocd
$kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

$curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
$sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
$rm argocd-linux-amd64

$kubectl patch svc argocd-server -n argocd -p &#39;{&quot;spec&quot;: {&quot;type&quot;: &quot;LoadBalancer&quot;}}&#39;

$kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=&quot;{.data.password}&quot; | base64 -d; echo
4IVTqkP2MhiTdIZo

$kubectl get svc argocd-server -n argocd
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP                                                                   PORT(S)                      AGE
argocd-server   LoadBalancer   10.100.98.251   a677d8882cdab494ebfee894df436abc-367513095.ap-northeast-2.elb.amazonaws.com   80:31331/TCP,443:30926/TCP   29m
</code></pre>
<p><img src="https://velog.velcdn.com/images/han-0315/post/cedb45bd-ed25-4216-a7f1-f9177f3faa23/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EKS 스터디 6주차]]></title>
            <link>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-6%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-6%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 03 Jun 2023 09:15:38 GMT</pubDate>
            <description><![CDATA[<p>이번 주차에는 EKS 보안에 대해 배웠다. 먼저, 쿠버네티스의 인증 인가 체계에 대해 배우고, EKS는 어떻게 다른 지 학습한다. 이후 IRSA에 대한 실습을 마지막으로 이번주차가 종료된다. </p>
<h2 id="환경세팅">환경세팅</h2>
<p>이번의 EKS 배포환경은 이전 주차와 크게 다르지 않다. EKS 인증/인가 테스트를 위해 작업용 EC2가 하나 추가되었다. 가시다님이 제공해주신 <a href="https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick5.yaml">CloudFormation</a>을 통해 배포를 실시한다.</p>
<h2 id="k8s-인증인가">K8S 인증/인가</h2>
<p>쿠버네티스의 인증 인가 체계를 살펴보면 아래의 그림과 같다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/c7092618-1efd-45b2-8d4d-681baa6d449e/image.png" alt=""></p>
<p>인증 단계를 거친다. 인증이 완료되면 인가 단계를 거친다. 인가 단계를 통해 명령이 리소스에 대한 권한이 있는 지 확인한 후, Admission control 을 통해서 etcd에 접근한다. </p>
<p>실습환경은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/917dcc82-8239-41f0-9534-6455be7fca41/image.png" alt=""></p>
<p>2개의 네임 스페이스가 존재하고 각 네임스페이스에는 서비스어카운트와 파드를 둔다. 서비스어카운트에 롤을 바인딩해보며 인증인가 체계를 확인한다.</p>
<ol>
<li><strong>kubeconfig 파일 확인</strong></li>
</ol>
<p>우선 kubeconfig 파일을 확인하여 현재의 쿠버네티스 접속 상태를 확인한다.</p>
<blockquote>
<p>clusters : kubectl 이 사용할 쿠버네티스 API 서버의 접속 정보 목록. 원격의 쿠버네티스 API 서버의 주소를 추가해 사용 가능
users : 쿠버네티스의 API 서버에 접속하기 위한 사용자 인증 정보 목록. (서비스 어카운트의 토큰, 혹은 인증서의 데이터 등)
contexts : cluster 항목과 users 항목에 정의된 값을 조합해 최종적으로 사용할 쿠버네티스 클러스터의 정보(컨텍스트)를 설정.
예를 들어 clusters 항목에 클러스터 A,B 가 정의돼 있고, users 항목에 사용자 a,b 가 정의돼 있다면 cluster A + user a 를 조합해,
&#39;cluster A 에 user a 로 인증해 쿠버네티스를 사용한다&#39; 라는 새로운 컨텍스트를 정의할 수 있습니다.</p>
</blockquote>
<pre><code class="language-bash">$cat .kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJ..
    server: https://BF69CC8DDDFB36E86FE01E52B6F5641B.gr7.ap-northeast-2.eks.amazonaws.com
  name: myeks.ap-northeast-2.eksctl.io
contexts:
- context:
    cluster: myeks.ap-northeast-2.eksctl.io
    user: EKS-study@myeks.ap-northeast-2.eksctl.io
  name: kane
current-context: kane
kind: Config
preferences: {}
users:
- name: EKS-study@myeks.ap-northeast-2.eksctl.io
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - eks
      - get-token
      - --output
      - json
      - --cluster-name
      - myeks
      - --region
      - ap-northeast-2
      command: aws
      env:
      - name: AWS_STS_REGIONAL_ENDPOINTS
        value: regional
      interactiveMode: IfAvailable
      provideClusterInfo: false</code></pre>
<ol>
<li>위의 실습 환경과 같이 네임스페이스와 파드, 서비스 어카운트를 생성합니다.</li>
</ol>
<pre><code class="language-bash">$kubectl create namespace dev-team
namespace/dev-team created
$k create ns infra-team
namespace/infra-team created
# 네임스페이스 생성 확인
$k get ns
NAME              STATUS   AGE
default           Active   34m
dev-team          Active   12s #&lt;--
infra-team        Active   3s  #&lt;--
kube-node-lease   Active   34m
kube-public       Active   34m
kube-system       Active   34m
monitoring        Active   7m55s
$k create sa dev-k8s -n dev-team
serviceaccount/dev-k8s created
$k create sa infra-k8s -n infra-team
serviceaccount/infra-k8s created
$k get sa -n infra-team
NAME        SECRETS   AGE
default     0         37s
infra-k8s   0         7s
</code></pre>
<ul>
<li>아래는 서비스 어카운트의 정보를 확인한다. 토큰 값이 없는 것을 확인할 수 있다.</li>
</ul>
<blockquote>
<p>1.24 버전으로 업데이트 되며, 서비스 계정을 생성하면 토큰이 자동으로 생성되는 방식에서 수동으 로 생성해야 하는 방식으로 변경됨.</p>
</blockquote>
<pre><code class="language-bash"># 서비스 어카운트 정보확인
# 
$k get sa -n infra-team infra-k8s -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: &quot;2023-05-31T11:28:28Z&quot;
  name: infra-k8s
  namespace: infra-team
  resourceVersion: &quot;7932&quot;
  uid: f5371b71-860f-48b2-9927-0b3d4e60052a
</code></pre>
<ul>
<li><strong>“dev-k8s 서비스어카운트의 토큰 정보 확인” 부분은 추후 수동으로 토큰을 생성한 뒤 진행</strong></li>
</ul>
<ol start="3">
<li><strong>서비스 어카운트를 지정하여 파드 생성 후 권한 테스트</strong></li>
</ol>
<pre><code class="language-bash">
$cat &lt;&lt;EOF | kubectl create -f -
&gt; apiVersion: v1
&gt; kind: Pod
&gt; metadata:
&gt;   name: dev-kubectl
&gt;   namespace: dev-team
&gt; spec:
&gt;   serviceAccountName: dev-k8s
&gt;   containers:
&gt;   - name: kubectl-pod
&gt;     image: bitnami/kubectl:1.24.10
&gt;     command: [&quot;tail&quot;]
&gt;     args: [&quot;-f&quot;, &quot;/dev/null&quot;]
&gt;   terminationGracePeriodSeconds: 0
&gt; EOF
pod/dev-kubectl created

$cat &lt;&lt;EOF | kubectl create -f -
&gt; apiVersion: v1
&gt; kind: Pod
&gt; metadata:
&gt;   name: infra-kubectl
&gt;   namespace: infra-team
&gt; spec:
&gt;   serviceAccountName: infra-k8s
&gt;   containers:
&gt;   - name: kubectl-pod
&gt;     image: bitnami/kubectl:1.24.10
&gt;     command: [&quot;tail&quot;]
&gt;     args: [&quot;-f&quot;, &quot;/dev/null&quot;]
&gt;   terminationGracePeriodSeconds: 0
&gt; EOF
pod/infra-kubectl created

#확인
$kubectl get pod -A
NAMESPACE     NAME                                                        READY   STATUS              RESTARTS   AGE
dev-team      dev-kubectl                                                 0/1     ContainerCreating   0          12s
infra-team    infra-kubectl                                               0/1     ContainerCreating   0          10s
kube-system   aws-load-balancer-controller-5f99d5f58f-lqj8f               1/1     Running             0          19m
kube-system   aws-load-balancer-controller-5f99d5f58f-mpmtr               1/1     Running             0          19m
...
monitoring    kube-prometheus-stack-prometheus-node-exporter-zv249        1/1     Running             0          18m
monitoring    prometheus-kube-prometheus-stack-prometheus-0               2/2     Running             0          18m

# 서비스 어카운트와 파드 확인
$kubectl get pod -o dev-kubectl -n dev-team -o yaml
apiVersion: v1
...
    securityContext: {}
    serviceAccount: dev-k8s
    serviceAccountName: dev-k8s
    terminationGracePeriodSeconds: 0
...
$kubectl get pod -o infra-kubectl -n infra-team -o yaml
apiVersion: v1
...
    serviceAccount: infra-k8s
    serviceAccountName: infra-k8s
...

# 서비스 어카운트 정보 확인
$kubectl exec -it dev-kubectl -n dev-team -- ls /run/secrets/kubernetes.io/serviceaccount
ca.crt    namespace  token
$kubectl exec -it dev-kubectl -n dev-team -- cat /run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6ImMwMTM4ZDQ5OGUyYjk0OGE3MzA5M2VkOTI3ZGFiODNjNTE2NGUzZjgifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIl0sImV4cCI6M...

alias k1=&#39;kubectl exec -it dev-kubectl -n dev-team -- kubectl&#39;
alias k2=&#39;kubectl exec -it infra-kubectl -n infra-team -- kubectl&#39;

# 권한 테스트
$k1 get pods 
Error from server (Forbidden): pods is forbidden: User &quot;system:serviceaccount:dev-team:dev-k8s&quot; cannot list resource &quot;pods&quot; in API group &quot;&quot; in the namespace &quot;dev-team&quot;
command terminated with exit code 1
$k1 run nginx --image nginx:1.20-alpine
Error from server (Forbidden): pods is forbidden: User &quot;system:serviceaccount:dev-team:dev-k8s&quot; cannot create resource &quot;pods&quot; in API group &quot;&quot; in the namespace &quot;dev-team&quot;
command terminated with exit code 1
$k2 get pods # kubectl exec -it infra-kubectl -n infra-team -- kubectl get pods 와 동일한 실행 명령이다!
Error from server (Forbidden): pods is forbidden: User &quot;system:serviceaccount:infra-team:infra-k8s&quot; cannot list resource &quot;pods&quot; in API group &quot;&quot; in the namespace &quot;infra-team&quot;
command terminated with exit code 1
$k2 run nginx --image nginx:1.20-alpine
Error from server (Forbidden): pods is forbidden: User &quot;system:serviceaccount:infra-team:infra-k8s&quot; cannot create resource &quot;pods&quot; in API group &quot;&quot; in the namespace &quot;infra-team&quot;
command terminated with exit code 1

# 권한이 없는 것을 확인할 수 있음.
$k1 auth can-i get pods
no
command terminated with exit code 1
$k2 get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User &quot;system:serviceaccount:infra-team:infra-k8s&quot; cannot list resource &quot;pods&quot; in API group &quot;&quot; in the namespace &quot;kube-system&quot;
command terminated with exit code 1
</code></pre>
<ol>
<li>Role을 부여한 뒤 서비스 어카운트에 바인딩 진행한다. </li>
</ol>
<pre><code class="language-bash">#각각 네임스페이스에 롤(Role)를 생성 후 서비스 어카운트 바인딩
# 모든 권한 부여(*)
$cat &lt;&lt;EOF | kubectl create -f -
&gt; apiVersion: rbac.authorization.k8s.io/v1
&gt; kind: Role
&gt; metadata:
&gt;   name: role-dev-team
&gt;   namespace: dev-team
&gt; rules:
&gt; - apiGroups: [&quot;*&quot;]
&gt;   resources: [&quot;*&quot;]
&gt;   verbs: [&quot;*&quot;]
&gt; EOF
role.rbac.authorization.k8s.io/role-dev-team created

$cat &lt;&lt;EOF | kubectl create -f -
&gt; apiVersion: rbac.authorization.k8s.io/v1
&gt; kind: Role
&gt; metadata:
&gt;   name: role-infra-team
&gt;   namespace: infra-team
&gt; rules:
&gt; - apiGroups: [&quot;*&quot;]
&gt;   resources: [&quot;*&quot;]
&gt;   verbs: [&quot;*&quot;]
&gt; EOF
role.rbac.authorization.k8s.io/role-infra-team created

$kubectl get roles -n dev-team
NAME            CREATED AT
role-dev-team   2023-05-31T11:40:56Z
$kubectl get roles -n infra-team
NAME              CREATED AT
role-infra-team   2023-05-31T11:41:11Z
$kubectl get roles -n dev-team -o yaml
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
  kind: Role
  metadata:
    creationTimestamp: &quot;2023-05-31T11:40:56Z&quot;
    name: role-dev-team
    namespace: dev-team
    resourceVersion: &quot;11069&quot;
    uid: 02cc4672-f543-4fb6-a770-4352d37f7a7e
  rules:
  - apiGroups:
    - &#39;*&#39;
    resources:
    - &#39;*&#39;
    verbs:
    - &#39;*&#39;
kind: List
metadata:
  resourceVersion: &quot;&quot;
$kubectl describe roles role-dev-team -n dev-team
Name:         role-dev-team
Labels:       &lt;none&gt;
Annotations:  &lt;none&gt;
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  *.*        []                 []              [*]

# 롤 바인딩
$cat &lt;&lt;EOF | kubectl create -f -
&gt; apiVersion: rbac.authorization.k8s.io/v1
&gt; kind: RoleBinding
&gt; metadata:
&gt;   name: roleB-dev-team
&gt;   namespace: dev-team
&gt; roleRef:
&gt;   apiGroup: rbac.authorization.k8s.io
&gt;   kind: Role
&gt;   name: role-dev-team
&gt; subjects:
&gt; - kind: ServiceAccount
&gt;   name: dev-k8s
&gt;   namespace: dev-team
&gt; EOF
rolebinding.rbac.authorization.k8s.io/roleB-dev-team created

$cat &lt;&lt;EOF | kubectl create -f -
&gt; apiVersion: rbac.authorization.k8s.io/v1
&gt; kind: RoleBinding
&gt; metadata:
&gt;   name: roleB-infra-team
&gt;   namespace: infra-team
&gt; roleRef:
&gt;   apiGroup: rbac.authorization.k8s.io
&gt;   kind: Role
&gt;   name: role-infra-team
&gt; subjects:
&gt; - kind: ServiceAccount
&gt;   name: infra-k8s
&gt;   namespace: infra-team
&gt; EOF
rolebinding.rbac.authorization.k8s.io/roleB-infra-team created
$kubectl get rolebindings -n dev-team
NAME             ROLE                 AGE
roleB-dev-team   Role/role-dev-team   7s
$kubectl get rolebindings -n infra-team
NAME               ROLE                   AGE
roleB-infra-team   Role/role-infra-team   7s
$kubectl get rolebindings -n dev-team -o yaml
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
  kind: RoleBinding
  metadata:
    creationTimestamp: &quot;2023-05-31T11:41:34Z&quot;
    name: roleB-dev-team
    namespace: dev-team
    resourceVersion: &quot;11233&quot;
    uid: b6b8aa7c-637d-4ec9-a95b-1d7af10fb427
  roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: Role
    name: role-dev-team
  subjects:
  - kind: ServiceAccount
    name: dev-k8s
    namespace: dev-team
kind: List
metadata:
  resourceVersion: &quot;&quot;
$kubectl describe rolebindings roleB-dev-team -n dev-team
Name:         roleB-dev-team
Labels:       &lt;none&gt;
Annotations:  &lt;none&gt;
Role:
  Kind:  Role
  Name:  role-dev-team
Subjects:
  Kind            Name     Namespace
  ----            ----     ---------
  ServiceAccount  dev-k8s  dev-team

# 테스트 진행!
alias k1=&#39;kubectl exec -it dev-kubectl -n dev-team -- kubectl&#39;
alias k2=&#39;kubectl exec -it infra-kubectl -n infra-team -- kubectl&#39;

# (옵션) kubectl auth can-i 로 kubectl 실행 사용자가 특정 권한을 가졌는지 확인
k1 auth can-i get podsNAME          READY   STATUS    RESTARTS   AGE
dev-kubectl   1/1     Running   0          3m24s

$k1 get pods
NAME          READY   STATUS    RESTARTS   AGE
dev-kubectl   1/1     Running   0          3m39s
$k1 run nginx --image nginx:1.20-alpine
pod/nginx created
$k1 get pods
NAME          READY   STATUS              RESTARTS   AGE
dev-kubectl   1/1     Running             0          3m46s
nginx         0/1     ContainerCreating   0          3s
$k1 delete pods nginx
pod &quot;nginx&quot; deleted
$k1 get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User &quot;system:serviceaccount:dev-team:dev-k8s&quot; cannot list resource &quot;pods&quot; in API group &quot;&quot; in the namespace &quot;kube-system&quot;
command terminated with exit code 1
$k1 get nodes
Error from server (Forbidden): nodes is forbidden: User &quot;system:serviceaccount:dev-team:dev-k8s&quot; cannot list resource &quot;nodes&quot; in API group &quot;&quot; at the cluster scope
command terminated with exit code 1
$k2 get pods
NAME            READY   STATUS    RESTARTS   AGE
infra-kubectl   1/1     Running   0          4m2s
$k2 run nginx --image nginx:1.20-alpine
pod/nginx created
$k2 get pods
NAME            READY   STATUS    RESTARTS   AGE
infra-kubectl   1/1     Running   0          4m8s
nginx           1/1     Running   0          2s
$k2 delete pods nginx
pod &quot;nginx&quot; deleted
$k2 get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User &quot;system:serviceaccount:infra-team:infra-k8s&quot; cannot list resource &quot;pods&quot; in API group &quot;&quot; in the namespace &quot;kube-system&quot;
command terminated with exit code 1
$k2 get nodes
Error from server (Forbidden): nodes is forbidden: User &quot;system:serviceaccount:infra-team:infra-k8s&quot; cannot list resource &quot;nodes&quot; in API group &quot;&quot; at the cluster scope
command terminated with exit code 1

# 파드에 대한 권한은 있지만, 노드에 대한 권한은 없는 모습
# 노드는 클러스터 롤 범위에 있기 때문이다.!
$k1 auth can-i get pods
yes
$k1 auth can-i get nodes
Warning: resource &#39;nodes&#39; is not namespace scoped
yes
$k1 auth can-i get no
Warning: resource &#39;nodes&#39; is not namespace scoped
yes

# 리소스 삭제
$kubectl delete ns dev-team infra-team
namespace &quot;dev-team&quot; deleted
namespace &quot;infra-team&quot; deleted</code></pre>
<h2 id="eks-인증인가">EKS 인증/인가</h2>
<h3 id="이론">이론</h3>
<p>이제, EKS 의 인증/인가 단계에 대해 실습합니다. 아래의 그림과 설명은 유튜브 영상에서 확인할 수  있습니다.</p>
<p><a href="https://youtu.be/bksogA-WXv8?t=669">https://youtu.be/bksogA-WXv8?t=669</a></p>
<blockquote>
<p>EKS는 Webhook, OIDC, Service Account을 지원한다.</p>
</blockquote>
<p>아래는 RBAC에 대한 설명이다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/e283534b-1249-4de9-8aae-8abf9aa857a0/image.png" alt=""></p>
<p>이제 EKS의 인증 인가 체계에 대해 알아본다. </p>
<p>핵심은 인증 인가 단계를 AWS IAM 을 통해 진행한다.! 어떻게 진행할 수 있는 지 확인해보면 다음과 같다. </p>
<p>먼저, EKS의 작업용 PC에서 쿠버네티스 명령을 날리면, 자동으로 <code>.kubeconfig</code> 에 입력되어 있는 <code>eks get-token</code> 명령을 실행된다. . </p>
<pre><code class="language-bash">#!/bin/bash
# AWS에서 제공해준 EKS config 파일이다. 
read -r -d &#39;&#39; KUBECONFIG &lt;&lt;EOF
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: $certificate_data
    server: $cluster_endpoint
  name: arn:aws:eks:$region_code:$account_id:cluster/$cluster_name
contexts:
- context:
    cluster: arn:aws:eks:$region_code:$account_id:cluster/$cluster_name
    user: arn:aws:eks:$region_code:$account_id:cluster/$cluster_name
  name: arn:aws:eks:$region_code:$account_id:cluster/$cluster_name
current-context: arn:aws:eks:$region_code:$account_id:cluster/$cluster_name
kind: Config
preferences: {}
users:
- name: arn:aws:eks:$region_code:$account_id:cluster/$cluster_name
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      command: aws
      args:
        - --region
        - $region_code
        - eks
        - get-token
        - --cluster-name
        - $cluster_name
        # - --role
        # - &quot;arn:aws:iam::$account_id:role/my-role&quot;
      # env:
        # - name: &quot;AWS_PROFILE&quot;
        #   value: &quot;aws-profile&quot;
EOF
echo &quot;${KUBECONFIG}&quot; &gt; ~/.kube/config</code></pre>
<p>해당 명령은 EKS service endpoint로 간다. 요청에 대한 응답으로 토큰값이 전달된다. </p>
<p>토큰값을 디코딩해보면, <code>aws sts get-caller-identity</code> 를 호출하는 pre-signed URL이다</p>
<p>이제 URL을 가지고 아래와 같은 구조로 인증 인가 단계가 진행된다.</p>
<p>쿠버네티스는 CA, bearer token, authenticating proxy 방법을 통해 API 요청을 허용한다. 여기서 EKS는 Bearer Token을 사용하는 것이다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/b83bfc42-8b7b-4aa9-b86e-29b47446caab/image.png" alt=""></p>
<p>이후 토큰을 통해 아까 살펴봤던 쿠버네티스 인증단계의 webhook 인증을 선택한다. URL은 <code>sts get-caller-identity</code> 를 호출하고, 이는 AWS IAM에게 인증을 받아 아래와 같은 유저 아이디 혹은 Role에 대한 ARN을 반환받는다.   </p>
<pre><code class="language-json">{
    &quot;UserId&quot;: &quot;AIDASAMPLEUSERID&quot;,
    &quot;Account&quot;: &quot;123456789012&quot;,
    &quot;Arn&quot;: &quot;arn:aws:iam::123456789012:role/k8s-admin&quot;
}</code></pre>
<p>위의 정보를 <code>aws-auth</code> 를 보낸다. <code>aws-auth</code> 는 쿠<strong>버네티스 내의 User, Group으로 맵핑하는 개체로 맵핑된 쿠버네티스 개체를 반환한다.</strong> 쿠버네티스 개체를 통해 RBAC 인가 단계를 거친 후 etcd에 접근한다.</p>
<ul>
<li>요약 (박준환님이 정리해주셨다.)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/han-0315/post/2b92bc94-006d-4b65-9204-8671c2448800/image.png" alt=""></p>
<ul>
<li><strong>핵심</strong> : 인증은 AWS IAM, 인가는 K8S RBAC에서 처리</li>
</ul>
<p><img src="https://velog.velcdn.com/images/han-0315/post/2844ad2f-451d-4212-b019-d6a2dc968c15/image.png" alt=""></p>
<hr>
<h3 id="실습">실습</h3>
<p>먼저, RBAC 관련 krew 플러그인을 설치합니다.</p>
<pre><code class="language-bash">$**kubectl krew install access-matrix rbac-tool rbac-view rolesum**</code></pre>
<p><strong>플러그인 확인하기</strong></p>
<pre><code class="language-bash">$kubectl rbac-tool lookup system:masters

W0531 20:47:56.354831    9284 warnings.go:67] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
  SUBJECT        | SUBJECT TYPE | SCOPE       | NAMESPACE | ROLE
+----------------+--------------+-------------+-----------+---------------+
  system:masters | Group        | ClusterRole |           | cluster-admin

$kubectl rbac-tool lookup system:nodes # eks:node-bootstrapper
W0531 20:47:58.478713    9379 warnings.go:67] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
  SUBJECT      | SUBJECT TYPE | SCOPE       | NAMESPACE | ROLE
+--------------+--------------+-------------+-----------+-----------------------+
  system:nodes | Group        | ClusterRole |           | eks:node-bootstrapper
$kubectl rbac-tool lookup system:bootstrappers # eks:node-bootstrapper
W0531 20:48:02.171737    9432 warnings.go:67] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
  SUBJECT              | SUBJECT TYPE | SCOPE       | NAMESPACE | ROLE
+----------------------+--------------+-------------+-----------+-----------------------+
  system:bootstrappers | Group        | ClusterRole |           | eks:node-bootstrapper
$kubectl describe ClusterRole eks:node-bootstrapper
Name:         eks:node-bootstrapper
Labels:       eks.amazonaws.com/component=node
Annotations:  &lt;none&gt;
PolicyRule:
  Resources                                                      Non-Resource URLs  Resource Names  Verbs
  ---------                                                      -----------------  --------------  -----
  certificatesigningrequests.certificates.k8s.io/selfnodeserver  []                 []              [create]
$kubectl rbac-tool whoami
{Username: &quot;kubernetes-admin&quot;,
 UID:      &quot;aws-iam-authenticator:871103481195:AIDA4VUOQIVV5CHOU2JOK&quot;,
 Groups:   [&quot;system:masters&quot;,
            &quot;system:authenticated&quot;],
 Extra:    {accessKeyId:  [&quot;AKIA4VUOQIVV2CPMGKLE&quot;],
            arn:          [&quot;arn:aws:iam::871103481195:user/EKS-study&quot;],
            canonicalArn: [&quot;arn:aws:iam::871103481195:user/EKS-study&quot;],
            principalId:  [&quot;AIDA4VUOQIVV5CHOU2JOK&quot;],
            sessionName:  [&quot;&quot;]}}
$kubectl rolesum aws-node -n kube-system

ServiceAccount: kube-system/aws-node
Secrets:

Policies:

• [CRB] */aws-node ⟶  [CR] */aws-node
  Resource                          Name  Exclude  Verbs  G L W C U P D DC
  *.extensions                      [*]     [-]     [-]   ✖ ✔ ✔ ✖ ✖ ✖ ✖ ✖
  eniconfigs.crd.k8s.amazonaws.com  [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖
  events.[,events.k8s.io]           [*]     [-]     [-]   ✖ ✔ ✖ ✔ ✖ ✔ ✖ ✖
  namespaces                        [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖
  nodes                             [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✔ ✖ ✖ ✖
  pods                              [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖

$kubectl rolesum -k User system:kube-proxy
User: system:kube-proxy

Policies:
• [CRB] */system:node-proxier ⟶  [CR] */system:node-proxier
  Resource                         Name  Exclude  Verbs  G L W C U P D DC
  endpoints                        [*]     [-]     [-]   ✖ ✔ ✔ ✖ ✖ ✖ ✖ ✖
  endpointslices.discovery.k8s.io  [*]     [-]     [-]   ✖ ✔ ✔ ✖ ✖ ✖ ✖ ✖
  events.[,events.k8s.io]          [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✔ ✔ ✖ ✖
  nodes                            [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖
  services                         [*]     [-]     [-]   ✖ ✔ ✔ ✖ ✖ ✖ ✖ ✖

$kubectl rolesum -k Group system:masters
Group: system:masters

Policies:
• [CRB] */cluster-admin ⟶  [CR] */cluster-admin
  Resource  Name  Exclude  Verbs  G L W C U P D DC
  *.*       [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔
</code></pre>
<p><strong>rbac-view 실행</strong></p>
<pre><code class="language-bash">$kubectl rbac-view
...
INFO[0060] Built Matrix for ClusterRoles
INFO[0064] Built Matrix for Roles
INFO[0064] Matrix for json built
$echo -e &quot;RBAC View Web http://$(curl -s ipinfo.io/ip):8800&quot;
RBAC View Web http://3.36.103.16:8800</code></pre>
<p>위의 출력된 URL에 접속하면 아래와 같이 UI를 통해 RBAC를 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/ab1c063a-30b5-404e-8abb-2555bc60228e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/ec12a823-b689-46e1-a63c-0c698fb99180/image.png" alt=""></p>
<p><strong>인증/인가 완벽 분석 해보기</strong></p>
<p>아래에서는 위에서 설명한 EKS의 인증 인가체계를 코드를 통해 상세하게 분석합니다.</p>
<blockquote>
<p>kubectl 명령 → aws eks get-token → EKS Service endpoint(STS)에 토큰 요청 ⇒ 응답값 디코드(Pre-Signed URL 이며 GetCallerIdentity..)</p>
</blockquote>
<pre><code class="language-bash">$aws sts get-caller-identity --query Arn
&quot;arn:aws:iam::871103481195:user/EKS-study&quot;
$cat ~/.kube/config | yh
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1EVXpN...
    server: https://BF69CC8DDDFB36E86FE01E52B6F5641B.gr7.ap-northeast-2.eks.amazonaws.com
  name: myeks.ap-northeast-2.eksctl.io
contexts:
- context:
    cluster: myeks.ap-northeast-2.eksctl.io
GET-TOKEN()                                                        GET-TOKEN()
    user: EKS-study@myeks.ap-northeast-2.eksctl.io
  name: kane
current-context: kane
kind: Config
preferences: {}
users:
- name: EKS-study@myeks.ap-northeast-2.eksctl.io
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - eks
      - get-token
      - --output
      - json
      - --cluster-name
      - myeks
      - --region
      - ap-northeast-2
      command: aws
      env:
      - name: AWS_STS_REGIONAL_ENDPOINTS
        value: regional
      interactiveMode: IfAvailable
      provideClusterInfo: false
$aws eks get-token --cluster-name $CLUSTER_NAME | jq
{
  &quot;kind&quot;: &quot;ExecCredential&quot;,
  &quot;apiVersion&quot;: &quot;client.authentication.k8s.io/v1beta1&quot;,
  &quot;spec&quot;: {},
  &quot;status&quot;: {
    &quot;expirationTimestamp&quot;: &quot;2023-05-31T12:04:36Z&quot;,
    &quot;token&quot;: &quot;k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhb...&quot;
  }
}
$aws eks get-token --cluster-name $CLUSTER_NAME | jq -r &#39;.status.token&#39;
k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDL...
</code></pre>
<p>토큰을 변환한 모습</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/c036e1d3-6cc9-428a-a093-b9c33489075b/image.png" alt=""></p>
<blockquote>
<p>EKS API는 <strong>Token Review</strong> 를 <strong>Webhook token authenticator</strong>에 요청 ⇒ (STS GetCallerIdentity 호출) AWS IAM 해당 <strong>호출 인증 완료</strong> 후 User/Role에 대한 <strong>ARN 반환</strong></p>
</blockquote>
<pre><code class="language-bash">$kubectl api-resources | grep authentication
tokenreviews                                   authentication.k8s.io/v1               false        TokenReview

$kubectl explain tokenreviews
KIND:     TokenReview
VERSION:  authentication.k8s.io/v1

DESCRIPTION:
     TokenReview attempts to authenticate a token to a known user. Note:
     TokenReview requests may be cached by the webhook token authenticator
     plugin in the kube-apiserver.

...
</code></pre>
<blockquote>
<p>이제 쿠버네티스 <strong>RBAC 인가</strong>를 처리합니다. 개인적인 생각이지만 플랫폼간 인증 이외에 인가까지 처리 통합은 쉽지 않은 것 같습니다</p>
</blockquote>
<pre><code class="language-bash">$kubectl api-resources | grep Webhook
mutatingwebhookconfigurations                  admissionregistration.k8s.io/v1        false        MutatingWebhookConfiguration
validatingwebhookconfigurations                admissionregistration.k8s.io/v1        false        ValidatingWebhookConfiguration
$kubectl get validatingwebhookconfigurations
NAME                                        WEBHOOKS   AGE
aws-load-balancer-webhook                   3          46m
eks-aws-auth-configmap-validation-webhook   1          72m
kube-prometheus-stack-admission             1          45m
vpc-resource-validating-webhook             2          72m
$kubectl get validatingwebhookconfigurations eks-aws-auth-configmap-validation-webhook -o yaml | kubectl neat | yh
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: eks-aws-auth-configmap-validation-webhook
webhooks:
- admissionReviewVersions:
  - v1
  clientConfig:
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1EVXpN..
    url: https://127.0.0.1:21375/validate
  failurePolicy: Ignore
  matchPolicy: Equivalent
  name: eks-aws-auth-configmap-validation-webhook.amazonaws.com
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: kube-system
  rules:
  - apiGroups:
    - &quot;&quot;
    apiVersions:
    - v1
    operations:
    - UPDATE
    resources:
    - configmaps
    scope: &#39;*&#39;
  sideEffects: None
  timeoutSeconds: 5
$kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS
      username: system:node:{{EC2PrivateDNSName}}
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
$kubectl rbac-tool whoami
{Username: &quot;kubernetes-admin&quot;,
 UID:      &quot;aws-iam-authenticator:871103481195:AIDA4VUOQIVV5CHOU2JOK&quot;,
 Groups:   [&quot;system:masters&quot;,
            &quot;system:authenticated&quot;],
 Extra:    {accessKeyId:  [&quot;AKIA4VUOQIVV2CPMGKLE&quot;],
            arn:          [&quot;arn:aws:iam::871103481195:user/EKS-study&quot;],
            canonicalArn: [&quot;arn:aws:iam::871103481195:user/EKS-study&quot;],
            principalId:  [&quot;AIDA4VUOQIVV5CHOU2JOK&quot;],
            sessionName:  [&quot;&quot;]}}
#system:masters , system:authenticated 그룹의 정보 확인
$kubectl rbac-tool lookup system:masters
W0531 21:06:13.318205   11330 warnings.go:67] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
  SUBJECT        | SUBJECT TYPE | SCOPE       | NAMESPACE | ROLE
+----------------+--------------+-------------+-----------+---------------+
  system:masters | Group        | ClusterRole |           | cluster-admin
$kubectl rbac-tool lookup system:authenticated
W0531 21:06:14.357002   11384 warnings.go:67] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
  SUBJECT              | SUBJECT TYPE | SCOPE       | NAMESPACE | ROLE
+----------------------+--------------+-------------+-----------+----------------------------------+
  system:authenticated | Group        | ClusterRole |           | eks:podsecuritypolicy:privileged
  system:authenticated | Group        | ClusterRole |           | system:discovery
  system:authenticated | Group        | ClusterRole |           | system:public-info-viewer
  system:authenticated | Group        | ClusterRole |           | system:basic-user
$kubectl rolesum -k Group system:masters
Group: system:masters

Policies:
• [CRB] */cluster-admin ⟶  [CR] */cluster-admin
  Resource  Name  Exclude  Verbs  G L W C U P D DC
  *.*       [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔

$kubectl rolesum -k Group system:authenticated
W0531 21:06:16.145547   11491 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
Group: system:authenticated

Policies:
• [CRB] */eks:podsecuritypolicy:authenticated ⟶  [CR] */eks:podsecuritypolicy:privileged

  Name            PRIV  RO-RootFS  Volumes  Caps  SELinux   RunAsUser  FSgroup   SUPgroup
  eks.privileged  True    False      [*]    [*]   RunAsAny  RunAsAny   RunAsAny  RunAsAny

• [CRB] */system:basic-user ⟶  [CR] */system:basic-user
  Resource                                       Name  Exclude  Verbs  G L W C U P D DC
  selfsubjectaccessreviews.authorization.k8s.io  [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
  selfsubjectrulesreviews.authorization.k8s.io   [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖

• [CRB] */system:discovery ⟶  [CR] */system:discovery

• [CRB] */system:public-info-viewer ⟶  [CR] */system:public-info-viewer

#system:masters 그룹이 사용 가능한 클러스터 롤 확인 : cluster-admin
$kubectl describe clusterrolebindings.rbac.authorization.k8s.io cluster-admin
Name:         cluster-admin
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
Role:
  Kind:  ClusterRole
  Name:  cluster-admin
Subjects:
  Kind   Name            Namespace
  ----   ----            ---------
  Group  system:masters
$kubectl describe clusterrole cluster-admin
Name:         cluster-admin
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  *.*        []                 []              [*]
             [*]                []              [*]
$kubectl describe ClusterRole system:discovery
Name:         system:discovery
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
             [/api/*]           []              [get]
             [/api]             []              [get]
             [/apis/*]          []              [get]
             [/apis]            []              [get]
             [/healthz]         []              [get]
             [/livez]           []              [get]
             [/openapi/*]       []              [get]
             [/openapi]         []              [get]
             [/readyz]          []              [get]
             [/version/]        []              [get]
             [/version]         []              [get]
$kubectl describe ClusterRole system:public-info-viewer
Name:         system:public-info-viewer
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
             [/healthz]         []              [get]
             [/livez]           []              [get]
             [/readyz]          []              [get]
             [/version/]        []              [get]
             [/version]         []              [get]
$kubectl describe ClusterRole system:basic-user
Name:         system:basic-user
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources                                      Non-Resource URLs  Resource Names  Verbs
  ---------                                      -----------------  --------------  -----
  selfsubjectaccessreviews.authorization.k8s.io  []                 []              [create]
  selfsubjectrulesreviews.authorization.k8s.io   []                 []              [create]
$kubectl describe ClusterRole eks:podsecuritypolicy:privileged
Name:         eks:podsecuritypolicy:privileged
Labels:       eks.amazonaws.com/component=pod-security-policy
              kubernetes.io/cluster-service=true
Annotations:  &lt;none&gt;
PolicyRule:
  Resources                   Non-Resource URLs  Resource Names    Verbs
  ---------                   -----------------  --------------    -----
  podsecuritypolicies.policy  []                 [eks.privileged]  [use]
</code></pre>
<h3 id="데브온스-신입-사원을-위한-myeks-bastion-2에-설정해보기"><strong>데브온스 신입 사원을 위한 myeks-bastion-2에 설정해보기</strong></h3>
<ul>
<li>[main bastion] IAM 사용자 생성</li>
</ul>
<pre><code class="language-bash">$aws iam create-user --user-name testuser
{
    &quot;User&quot;: {
        &quot;Path&quot;: &quot;/&quot;,
        &quot;UserName&quot;: &quot;testuser&quot;,
        &quot;UserId&quot;: &quot;AIDA4VUOQIVVX254SX7ZZ&quot;,
        &quot;Arn&quot;: &quot;arn:aws:iam::871103481195:user/testuser&quot;,
        &quot;CreateDate&quot;: &quot;2023-05-31T12:07:09+00:00&quot;
    }
}
#사용자에게 프로그래밍 방식 액세스 권한 부여
$aws iam create-access-key --user-name testuser
{
    &quot;AccessKey&quot;: {
        &quot;UserName&quot;: &quot;testuser&quot;,
        &quot;AccessKeyId&quot;: &quot;AKIA4VUOQIVV2DMRFWMX&quot;,
        &quot;Status&quot;: &quot;Active&quot;,
        &quot;SecretAccessKey&quot;: &quot;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&quot;,
        &quot;CreateDate&quot;: &quot;2023-05-31T12:07:16+00:00&quot;
    }
}
$aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --user-name testuser
$aws sts get-caller-identity --query Arn
&quot;arn:aws:iam::871103481195:user/EKS-study&quot;
$aws ec2 describe-instances --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key==&#39;Name&#39;]|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output table
-----------------------------------------------------------------------
|                          DescribeInstances                          |
+----------------------+----------------+------------------+----------+
|     InstanceName     | PrivateIPAdd   |   PublicIPAdd    | Status   |
+----------------------+----------------+------------------+----------+
|  myeks-ng1-Node      |  192.168.3.81  |  43.200.177.234  |  running |
|  myeks-ng1-Node      |  192.168.2.117 |  3.38.186.134    |  running |
|  myeks-bastion-EC2-2 |  192.168.1.200 |  3.38.105.241    |  running |
|  myeks-bastion-EC2   |  192.168.1.100 |  3.36.103.16     |  running |
|  myeks-ng1-Node      |  192.168.1.102 |  43.201.254.218  |  running |
+----------------------+----------------+------------------+----------+
</code></pre>
<ul>
<li>이제 bastion2 에 접속하여, 생성한 testuser AWS config 설정을 하고, 권한을 확인한다.</li>
</ul>
<pre><code class="language-bash">[root@myeks-bastion-2 ~]# bastion 2로 접속
$aws sts get-caller-identity --query Arn
Unable to locate credentials. You can configure credentials by running &quot;aws configure&quot;.
$aws configure
AWS Access Key ID [None]: AKIA4VUOQIVV2DMRFWMX
AWS Secret Access Key [None]: 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
Default region name [None]: ap-northeast-2
Default output format [None]: json
$aws sts get-caller-identity --query Arn
&quot;arn:aws:iam::871103481195:user/testuser&quot;

# 접속 실패
$kubectl get node -v6
I0531 21:11:22.552208    1798 round_trippers.go:553] GET http://localhost:8080/api?timeout=32s  in 0 milliseconds
E0531 21:11:22.552311    1798 memcache.go:265] couldn&#39;t get current server API group list: Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.552333    1798 cached_discovery.go:120] skipped caching discovery info due to Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.552554    1798 round_trippers.go:553] GET http://localhost:8080/api?timeout=32s  in 0 milliseconds
E0531 21:11:22.552594    1798 memcache.go:265] couldn&#39;t get current server API group list: Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.553487    1798 cached_discovery.go:120] skipped caching discovery info due to Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.553511    1798 shortcut.go:100] Error loading discovery information: Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.554197    1798 round_trippers.go:553] GET http://localhost:8080/api?timeout=32s  in 0 milliseconds
E0531 21:11:22.554252    1798 memcache.go:265] couldn&#39;t get current server API group list: Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.555354    1798 cached_discovery.go:120] skipped caching discovery info due to Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.555575    1798 round_trippers.go:553] GET http://localhost:8080/api?timeout=32s  in 0 milliseconds
E0531 21:11:22.555682    1798 memcache.go:265] couldn&#39;t get current server API group list: Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.556788    1798 cached_discovery.go:120] skipped caching discovery info due to Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.556994    1798 round_trippers.go:553] GET http://localhost:8080/api?timeout=32s  in 0 milliseconds
E0531 21:11:22.557035    1798 memcache.go:265] couldn&#39;t get current server API group list: Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.558150    1798 cached_discovery.go:120] skipped caching discovery info due to Get &quot;http://localhost:8080/api?timeout=32s&quot;: dial tcp 127.0.0.1:8080: connect: connection refused
I0531 21:11:22.558214    1798 helpers.go:264] Connection error: Get http://localhost:8080/api?timeout=32s: dial tcp 127.0.0.1:8080: connect: connection refused
The connection to the server localhost:8080 was refused - did you specify the right host or port?

# kube config 파일이 없음!
$ls ~/.kube
ls: cannot access /root/.kube: No such file or directory
</code></pre>
<ul>
<li>[myeks-bastion] testuser에 system:masters 그룹 부여로 EKS 관리자 수준 권한 설정</li>
</ul>
<pre><code class="language-bash"># 방안1 : eksctl 사용 &gt;&gt; iamidentitymapping 실행 시 aws-auth 컨피그맵 작성해줌
# tesk 유저 권한부여
## Creates a mapping from IAM role or user to Kubernetes user and groups

$eksctl create iamidentitymapping --cluster $CLUSTER_NAME --username testuser --group system:masters --arn arn:aws:iam::$ACCOUNT_ID:user/testuser
2023-05-31 21:12:06 [ℹ]  checking arn arn:aws:iam::871103481195:user/testuser against entries in the auth ConfigMap
2023-05-31 21:12:06 [ℹ]  adding identity &quot;arn:aws:iam::871103481195:user/testuser&quot; to auth ConfigMap
$kubectl get cm -n kube-system aws-auth -o yaml | kubectl neat | yh
apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS
      username: system:node:{{EC2PrivateDNSName}}
  mapUsers: |
    - groups:
      - system:masters
      userarn: arn:aws:iam::871103481195:user/testuser
      username: testuser
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
$eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN                                            USERNAME                GROUPS                    ACCOUNT
arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS    system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes
arn:aws:iam::871103481195:user/testuser                            testuser                system:masters
</code></pre>
<ul>
<li>이제 bastion2에 접속하여, eks를 업데이트하고 kubeconfig 파일을 비교 확인한다.</li>
</ul>
<pre><code class="language-bash">[root@myeks-bastion-2 ~]# 
# 업데이트
$aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias testuser
Added new context testuser to /root/.kube/config
# 첫번째 bastic ec2의 config와 비교해보자
$cat ~/.kube/config | yh
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SU
    server: https://BF69CC8DDDFB36E86FE01E52B6F5641B.gr7.ap-northeast-2.eks.amazonaws.com
  name: arn:aws:eks:ap-northeast-2:871103481195:cluster/myeks
contexts:
- context:
    cluster: arn:aws:eks:ap-northeast-2:871103481195:cluster/myeks
    user: testuser
  name: testuser
current-context: testuser
kind: Config
preferences: {}
users:
- name: testuser
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - --region
      - ap-northeast-2
      - eks
      - get-token
      - --cluster-name
      - myeks
      - --output
      - json
      command: aws</code></pre>
<p>아래는 main bastion 의 kube config 파일이다. 대부분 같은 것을 확인할 수 있다.</p>
<pre><code class="language-bash">#bation 2와 kubeconfig file 확인
$cat ~/.kube/config | yh
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS..
    server: https://BF69CC8DDDFB36E86FE01E52B6F5641B.gr7.ap-northeast-2.eks.amazonaws.com
  name: myeks.ap-northeast-2.eksctl.io
contexts:
- context:
    cluster: myeks.ap-northeast-2.eksctl.io
    user: EKS-study@myeks.ap-northeast-2.eksctl.io
  name: kane
current-context: kane
kind: Config
preferences: {}
users:
- name: EKS-study@myeks.ap-northeast-2.eksctl.io
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - eks
      - get-token
...

#bastion2와 비교
$kubectl rbac-tool whoami
{Username: &quot;kubernetes-admin&quot;,
 UID:      &quot;aws-iam-authenticator:871103481195:AIDA4VUOQIVV5CHOU2JOK&quot;,
 Groups:   [&quot;system:masters&quot;,
            &quot;system:authenticated&quot;],
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS
      username: system:node:{{EC2PrivateDNSName}}
  mapUsers: |
    - groups:
      - system:authenticated
      userarn: arn:aws:iam::871103481195:user/testuser
      username: testuser
...</code></pre>
<ul>
<li><p>[myeks-bastion] testuser 의 Group 변경(system:masters → system:authenticated)으로 RBAC 동작 확인</p>
</li>
<li><p>데브옵스 신입, config map 수정</p>
<pre><code class="language-json">  # Please edit the object below. Lines beginning with a &#39;#&#39; will be ignored,
  # and an empty file will abort the edit. If an error occurs while saving this file will be
  # reopened with the relevant failures.
  #
  apiVersion: v1
  data:
    mapRoles: |
      - groups:
        - system:bootstrappers
        - system:nodes
        rolearn: arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS
        username: system:node:{{EC2PrivateDNSName}}
    mapUsers: |
      - groups:
        - system:authenticated
        userarn: arn:aws:iam::871103481195:user/testuser
        username: testuser
  kind: ConfigMap
  metadata:
    creationTimestamp: &quot;2023-05-31T11:03:14Z&quot;
    name: aws-auth
    namespace: kube-system
    resourceVersion: &quot;19809&quot;
    uid: 7e786e3c-cf39-4ade-a86f-ff92a4bcbb39</code></pre>
</li>
</ul>
<pre><code class="language-bash">$kubectl edit cm -n kube-system aws-auth
Edit cancelled, no changes made.
$kubectl edit cm -n kube-system aws-auth
configmap/aws-auth edited
$eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN                                            USERNAME                GROUPS                    ACCOUNT
arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS    system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes
arn:aws:iam::871103481195:user/testuser                            testuser                system:authenticated
$eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN                                            USERNAME                GROUPS                    ACCOUNT
arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS    system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes
arn:aws:iam::871103481195:user/testuser                            testuser                system:authenticated
$kubectl edit cm -n kube-system aws-auth
Edit cancelled, no changes made.
$kubectl edit cm -n kube-system aws-auth
Edit cancelled, no changes made.
$kubectl get node -v6
I0531 21:18:20.686745   13429 loader.go:374] Config loaded from file:  /root/.kube/config
I0531 21:18:21.481898   13429 round_trippers.go:553] GET https://BF69CC8DDDFB36E86FE01E52B6F5641B.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 200 OK in 773 milliseconds
NAME                                               STATUS   ROLES    AGE   VERSION
ip-192-168-1-102.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   74m   v1.24.13-eks-0a21954
ip-192-168-2-117.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   74m   v1.24.13-eks-0a21954
ip-192-168-3-81.ap-northeast-2.compute.internal    Ready    &lt;none&gt;   74m   v1.24.13-eks-0a21954

$kubectl api-resources -v5
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
bindings                                       v1                                     true         Binding
componentstatuses                 cs           v1                                     false        ComponentStatus
configmaps                        cm           v1                                     true         ConfigMap
endpoints                         ep           v1                                     true         Endpoints
...
securitygrouppolicies             sgp          vpcresources.k8s.aws/v1beta1           true         SecurityGroupPolicy

$eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN                                            USERNAME                GROUPS                    ACCOUNT
arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS    system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes
arn:aws:iam::871103481195:user/testuser                            testuser                system:authenticated
</code></pre>
<ul>
<li>bastion 2 <code>kubectl</code> 사용 확인</li>
</ul>
<pre><code class="language-bash">$kubectl ns default

Context &quot;testuser&quot; modified.
Active namespace is &quot;default&quot;.
(testuser:default) [root@myeks-bastion-2 ~]#
$kubectl get node -v6
I0531 21:13:59.725066    2078 loader.go:373] Config loaded from file:  /root/.kube/config
I0531 21:14:00.542215    2078 round_trippers.go:553] GET https://BF69CC8DDDFB36E86FE01E52B6F5641B.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 200 OK in 809 milliseconds
NAME                                               STATUS   ROLES    AGE   VERSION
ip-192-168-1-102.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   70m   v1.24.13-eks-0a21954
ip-192-168-2-117.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   70m   v1.24.13-eks-0a21954
ip-192-168-3-81.ap-northeast-2.compute.internal    Ready    &lt;none&gt;   70m   v1.24.13-eks-0a21954
$kubectl krew install rbac-tool &amp;&amp; kubectl rbac-tool whoami
Updated the local copy of plugin index.
Installing plugin: rbac-tool
Installed plugin: rbac-tool
\
 | Use this plugin:
 |     kubectl rbac-tool
 | Documentation:
 |     https://github.com/alcideio/rbac-tool
/
WARNING: You installed plugin &quot;rbac-tool&quot; from the krew-index plugin repository.
   These plugins are not audited for security by the Krew maintainers.
   Run them at your own risk.
{Username: &quot;testuser&quot;,
 UID:      &quot;aws-iam-authenticator:871103481195:AIDA4VUOQIVVX254SX7ZZ&quot;,
 Groups:   [&quot;system:masters&quot;,
            &quot;system:authenticated&quot;],
 Extra:    {accessKeyId:  [&quot;AKIA4VUOQIVV2DMRFWMX&quot;],
            arn:          [&quot;arn:aws:iam::871103481195:user/testuser&quot;],
            canonicalArn: [&quot;arn:aws:iam::871103481195:user/testuser&quot;],
            principalId:  [&quot;AIDA4VUOQIVVX254SX7ZZ&quot;],
            sessionName:  [&quot;&quot;]}}
# 노드에 대한 권한은 클러스터 롤이기에, 차단된 모습이다. 
$kubectl get node -v6
I0531 21:16:11.465425    2208 loader.go:373] Config loaded from file:  /root/.kube/config
I0531 21:16:12.269649    2208 round_trippers.go:553] GET https://BF69CC8DDDFB36E86FE01E52B6F5641B.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 403 Forbidden in 782 milliseconds
I0531 21:16:12.269935    2208 helpers.go:246] server response object: [{
  &quot;kind&quot;: &quot;Status&quot;,
  &quot;apiVersion&quot;: &quot;v1&quot;,
  &quot;metadata&quot;: {},
  &quot;status&quot;: &quot;Failure&quot;,
  &quot;message&quot;: &quot;nodes is forbidden: User \&quot;testuser\&quot; cannot list resource \&quot;nodes\&quot; in API group \&quot;\&quot; at the cluster scope&quot;,
  &quot;reason&quot;: &quot;Forbidden&quot;,
  &quot;details&quot;: {
    &quot;kind&quot;: &quot;nodes&quot;
  },
  &quot;code&quot;: 403
}]
Error from server (Forbidden): nodes is forbidden: User &quot;testuser&quot; cannot list resource &quot;nodes&quot; in API group &quot;&quot; at the cluster scope

$kubectl api-resources -v5
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
bindings                                       v1                                     true         Binding
componentstatuses                 cs           v1                                     false        ComponentStatus
configmaps                        cm           v1                                     true         ConfigMap
endpoints                         ep           v1                                     true         Endpoints
events                            ev           v1                                     true         Event
...</code></pre>
<ul>
<li>[myeks-bastion]에서 testuser IAM 맵핑 삭제</li>
</ul>
<pre><code class="language-bash">$eksctl delete iamidentitymapping --cluster $CLUSTER_NAME --arn  arn:aws:iam::$ACCOUNT_ID:user/testuser
2023-05-31 21:21:05 [ℹ]  removing identity &quot;arn:aws:iam::871103481195:user/testuser&quot; from auth ConfigMap (username = &quot;testuser&quot;, groups = [&quot;system:authenticated&quot;])
$eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN                                            USERNAME                GROUPS                    ACCOUNT
arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS    system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes
$kubectl get cm -n kube-system aws-auth -o yaml | yh
apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-QR6CCYVFGKRS
      username: system:node:{{EC2PrivateDNSName}}
  mapUsers: |
    []
kind: ConfigMap
metadata:
  creationTimestamp: &quot;2023-05-31T11:03:14Z&quot;
  name: aws-auth
  namespace: kube-system
  resourceVersion: &quot;21204&quot;
  uid: 7e786e3c-cf39-4ade-a86f-ff92a4bcbb39</code></pre>
<h2 id="irsa">IRSA</h2>
<p><strong>IAM Role for Service Account</strong>의 약자로 각 파드 당 권한을 IAM 권한을 별도로 부여하는 방법이다. </p>
<p>운영하다보면 사용자가 아닌 하나의 서비스가 AWS에 접근해야 하는 일이 생긴다. ex) RDS or S3 스냅샷을 남기는 파드, 이때 <strong>EC2 Instance Profile을 사용하여</strong> 노드의 권한을 모든 파드가 공유하면 최소 권한 원칙에 위배된다. 파드마다 역할이 다르니 권한 부여 단위를 파드로 두는 것이다. 파드가 특정 IAM 역할로 Assume 할때 토큰을 AWS에 전송하고, AWS는 토큰과 EKS IdP를 통해 해당 IAM 역할을 사용할 수 있는지 검증한다.</p>
<h3 id="ec2-instance-profile">EC2 instance Profile</h3>
<p>설정예시를 참고해서 추후 진행</p>
<pre><code class="language-bash"># 설정 예시 1 : eksctl 사용 시
**eksctl create** cluster --name $CLUSTER_NAME ... **--external-dns-access --full-ecr-access --asg-access**

# 설정 예시 2 : eksctl로 yaml 파일로 노드 생성 시
**cat myeks.yaml | yh**
...
managedNodeGroups:
- amiFamily: AmazonLinux2
  iam:
    withAddonPolicies:
      albIngress: false
      appMesh: false
      appMeshPreview: false
      **autoScaler: true**
      awsLoadBalancerController: false
      **certManager: true**
      **cloudWatch: true**
      ebs: false
      efs: false
      **externalDNS: true**
      fsx: false
      **imageBuilder: true**
      xRay: false
...

# 설정 예시 3 : 테라폼
...</code></pre>
<h3 id="irsa-1">IRSA</h3>
<p>AWS 자원에 접근하는 파드 생성해보기</p>
<p><code>s3 ls 명령어를 사용하는 파드 생성</code></p>
<pre><code class="language-bash"> $cat &lt;&lt;EOF | kubectl apply -f -
 apiVersion: v1
 kind: Pod
 metadata:
   name: eks-iam-test1
 spec:
   containers:
     - name: my-aws-cli
       image: amazon/aws-cli:latest
       args: [&#39;s3&#39;, &#39;ls&#39;]
   restartPolicy: Never
   automountServiceAccountToken: false
EOF
pod/eks-iam-test1 created

#확인
$kubectl get pod
NAME            READY   STATUS              RESTARTS   AGE
eks-iam-test1   0/1     ContainerCreating   0          1s

$kubectl describe pod
Name:             eks-iam-test1
Namespace:        default
Priority:         0
Service Account:  default
Node:             ip-192-168-3-81.ap-northeast-2.compute.internal/192.168.3.81
Start Time:       Wed, 31 May 2023 21:28:03 +0900
Labels:           &lt;none&gt;
Annotations:      kubernetes.io/psp: eks.privileged
Status:           Pending
IP:
IPs:              &lt;none&gt;
Containers:
  my-aws-cli:
    Container ID:
    Image:         amazon/aws-cli:latest
    Image ID:
    Port:          &lt;none&gt;
    Host Port:     &lt;none&gt;
    Args:
      s3
      ls
    State:          Waiting
      Reason:       ContainerCreating
    Ready:          False
    Restart Count:  0
    Environment:    &lt;none&gt;
    Mounts:         &lt;none&gt;
...

#로그 확인
$kubectl logs eks-iam-test1
Error from server (BadRequest): container &quot;my-aws-cli&quot; in pod &quot;eks-iam-test1&quot; is waiting to start: ContainerCreating

#파드1 삭제
$kubectl logs eks-iam-test1
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
</code></pre>
<p>S3(<strong>ListBuckets)로그확인(접속하려하지만, 권한이 없어 접근에 실패한 것을 확인할 수 있다.)</strong></p>
<pre><code class="language-json">{
    &quot;eventVersion&quot;: &quot;1.08&quot;,
    &quot;userIdentity&quot;: {
        &quot;type&quot;: &quot;AssumedRole&quot;,
        &quot;principalId&quot;: &quot;AROASUCZUNGONCRSO3PBP:access-analyzer&quot;,
        &quot;arn&quot;: &quot;arn:aws:sts::180576610716:assumed-role/AWSServiceRoleForAccessAnalyzer/access-analyzer&quot;,
        &quot;accountId&quot;: &quot;180576610716&quot;,
        &quot;accessKeyId&quot;: &quot;ASIASUCZUNGOFEM7DDEV&quot;,
        &quot;sessionContext&quot;: {
            &quot;sessionIssuer&quot;: {
                &quot;type&quot;: &quot;Role&quot;,
                &quot;principalId&quot;: &quot;AROASUCZUNGONCRSO3PBP&quot;,
                &quot;arn&quot;: &quot;arn:aws:iam::180576610716:role/aws-service-role/access-analyzer.amazonaws.com/AWSServiceRoleForAccessAnalyzer&quot;,
                &quot;accountId&quot;: &quot;180576610716&quot;,
                &quot;userName&quot;: &quot;AWSServiceRoleForAccessAnalyzer&quot;
            },
            &quot;webIdFederationData&quot;: {},
            &quot;attributes&quot;: {
                &quot;creationDate&quot;: &quot;2023-05-31T07:03:36Z&quot;,
                &quot;mfaAuthenticated&quot;: &quot;false&quot;
            }
        },
        &quot;invokedBy&quot;: &quot;access-analyzer.amazonaws.com&quot;
    },
    &quot;eventTime&quot;: &quot;2023-05-31T07:03:37Z&quot;,
    &quot;eventSource&quot;: &quot;s3.amazonaws.com&quot;,
    &quot;eventName&quot;: &quot;ListBuckets&quot;,
    &quot;awsRegion&quot;: &quot;ap-northeast-2&quot;,
    &quot;sourceIPAddress&quot;: &quot;access-analyzer.amazonaws.com&quot;,
    &quot;userAgent&quot;: &quot;access-analyzer.amazonaws.com&quot;,
    &quot;requestParameters&quot;: {
        &quot;Host&quot;: &quot;s3.ap-northeast-2.amazonaws.com&quot;
    },
    &quot;responseElements&quot;: null,
    &quot;additionalEventData&quot;: {
        &quot;SignatureVersion&quot;: &quot;SigV4&quot;,
        &quot;CipherSuite&quot;: &quot;ECDHE-RSA-AES128-GCM-SHA256&quot;,
        &quot;bytesTransferredIn&quot;: 0,
        &quot;AuthenticationMethod&quot;: &quot;AuthHeader&quot;,
        &quot;x-amz-id-2&quot;: &quot;3euU4IpaPy9deQh..=&quot;,
        &quot;bytesTransferredOut&quot;: 661
    },
    &quot;requestID&quot;: &quot;CGKW0AV31SR85514&quot;,
    &quot;eventID&quot;: &quot;5afa53c5-31e4-4e22-88d3-324625cccdd0&quot;,
    &quot;readOnly&quot;: true,
    &quot;eventType&quot;: &quot;AwsApiCall&quot;,
    &quot;managementEvent&quot;: true,
    &quot;recipientAccountId&quot;: &quot;180576610716&quot;,
    &quot;eventCategory&quot;: &quot;Management&quot;
}</code></pre>
<p>이제 서비스 어카운트를 기반으로 접근 권한을 부여해서 진행해본다.</p>
<p>위와 같이 S3에 접근하는 파드 생성</p>
<pre><code class="language-bash">
$cat &lt;&lt;EOF | kubectl apply -f -
&gt; apiVersion: v1
&gt; kind: Pod
&gt; metadata:
&gt;   name: eks-iam-test2
&gt; spec:
&gt;   containers:
&gt;     - name: my-aws-cli
&gt;       image: amazon/aws-cli:latest
&gt;       command: [&#39;sleep&#39;, &#39;36000&#39;]
&gt;   restartPolicy: Never
&gt; EOF
pod/eks-iam-test2 created

$kubectl get pod
NAME            READY   STATUS    RESTARTS   AGE
eks-iam-test2   1/1     Running   0          6s

$kubectl describe pod
Name:             eks-iam-test2
Namespace:        default
...
Containers:
  my-aws-cli:
      Image:         amazon/aws-cli:latest
    Image ID:      docker.io/amazon/aws-cli@sha256:21e6273f0025755abfc842ca39e8ef4fed3d9d2ce61d93bb16ce86a6c1668ae5
    Port:          &lt;none&gt;
    Host Port:     &lt;none&gt;
    Command:
      sleep
      36000
    State:          Running
      Started:      Wed, 31 May 2023 21:30:32 +0900
    Ready:          True
    Restart Count:  0
    Environment:    &lt;none&gt;
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-4x9qc (ro)
...

# 접근 실패
$kubectl exec -it eks-iam-test2 -- aws s3 ls

# 서비스 어카운트 토큰 확인
SA_TOKEN=$(kubectl exec -it eks-iam-test2 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $SA_TOKEN
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
command terminated with exit code 254
</code></pre>
<p>IAM 서비스 어카운트 생성</p>
<pre><code class="language-bash">
$eksctl create iamserviceaccount \
&gt;   --name my-sa \
&gt;   --namespace default \
&gt;   --cluster $CLUSTER_NAME \
&gt;   --approve \
&gt;   --attach-policy-arn $(aws iam list-policies --query &#39;Policies[?PolicyName==`AmazonS3ReadOnlyAccess`].Arn&#39; --output text)

2023-05-31 21:40:06 [ℹ]  1 existing iamserviceaccount(s) (kube-system/aws-load-balancer-controller) will be excluded
2023-05-31 21:40:06 [ℹ]  1 iamserviceaccount (default/my-sa) was included (based on the include/exclude rules)
2023-05-31 21:40:06 [!]  serviceaccounts that exist in Kubernetes will be excluded, use --override-existing-serviceaccounts to override
2023-05-31 21:40:06 [ℹ]  1 task: {
    2 sequential sub-tasks: {
        create IAM role for serviceaccount &quot;default/my-sa&quot;,
        create serviceaccount &quot;default/my-sa&quot;,
    } }2023-05-31 21:40:06 [ℹ]  building iamserviceaccount stack &quot;eksctl-myeks-addon-iamserviceaccount-default-my-sa&quot;
2023-05-31 21:40:07 [ℹ]  deploying stack &quot;eksctl-myeks-addon-iamserviceaccount-default-my-sa&quot;
2023-05-31 21:40:08 [ℹ]  waiting for CloudFormation stack &quot;eksctl-myeks-addon-iamserviceaccount-default-my-sa&quot;
2023-05-31 21:40:38 [ℹ]  waiting for CloudFormation stack &quot;eksctl-myeks-addon-iamserviceaccount-default-my-sa&quot;
2023-05-31 21:40:38 [ℹ]  created serviceaccount &quot;default/my-sa&quot;

$eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE    NAME                ROLE ARN
default        my-sa                arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-193TXMP0QLZSN
kube-system    aws-load-balancer-controller    arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-M4DYRCMI95LR
$kubectl get sa
NAME      SECRETS   AGE
default   0         107m
my-sa     0         32s
$kubectl describe sa my-sa

Name:                my-sa
Namespace:           default
Labels:              app.kubernetes.io/managed-by=eksctl
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-193TXMP0QLZSN
Image pull secrets:  &lt;none&gt;
Mountable secrets:   &lt;none&gt;
Tokens:              &lt;none&gt;
Events:              &lt;none&gt;
</code></pre>
<p>콘솔에서 정보 확인</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/74dbcf1a-69ac-4be3-906f-0387a8d0343d/image.png" alt=""></p>
<p>만든 service account</p>
<pre><code class="language-json">{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: {
                &quot;Federated&quot;: &quot;arn:aws:iam::871103481195:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/BF69CC8DDDFB36E86FE01E52B6F5641B&quot;
            },
            &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
            &quot;Condition&quot;: {
                &quot;StringEquals&quot;: {
                    &quot;oidc.eks.ap-northeast-2.amazonaws.com/id/BF69CC8DDDFB36E86FE01E52B6F5641B:sub&quot;: &quot;system:serviceaccount:default:my-sa&quot;,
                    &quot;oidc.eks.ap-northeast-2.amazonaws.com/id/BF69CC8DDDFB36E86FE01E52B6F5641B:aud&quot;: &quot;sts.amazonaws.com&quot;
                }
            }
        }
    ]
}</code></pre>
<p>테스트를 위해 위와 같이 파드 생성</p>
<p>→ S3 에 접근되는 것을 확인할 수 있다. </p>
<pre><code class="language-bash">$cat &lt;&lt;EOF | kubectl apply -f -
&gt; apiVersion: v1
&gt; kind: Pod
&gt; metadata:
&gt;   name: eks-iam-test3
&gt; spec:
&gt;   serviceAccountName: **my-sa**
&gt;   containers:
&gt;     - name: my-aws-cli
&gt;       image: amazon/aws-cli:latest
&gt;       command: [&#39;sleep&#39;, &#39;36000&#39;]
&gt;   restartPolicy: Never
&gt; EOF
pod/eks-iam-test3 created

$kubectl get mutatingwebhookconfigurations pod-identity-webhook -o yaml | kubectl neat | yh
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: pod-identity-webhook
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakN...
    url: https://127.0.0.1:23443/mutate
  failurePolicy: Ignore
  matchPolicy: Equivalent
  name: iam-for-pods.amazonaws.com
  reinvocationPolicy: IfNeeded
  rules:
  - apiGroups:
    - &quot;&quot;
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: &#39;*&#39;
  sideEffects: None
  timeoutSeconds: 10

# 파드 확인
$kubectl get pod eks-iam-test3
NAME            READY   STATUS    RESTARTS   AGE
eks-iam-test3   1/1     Running   0          9s

# **Pod Identity Webhook**은 **mutating** webhook을 통해 아래 **Env 내용**과 **1개의 볼륨**을 추가함
$kubectl describe pod eks-iam-test3
Name:             eks-iam-test3
Namespace:        default
Priority:         0
**Service Account:  my-sa**
...
Containers:
  my-aws-cli:
    Container ID:  containerd://37b52c6ca91475c8b42f194fc21e32ad905ae8a982110443cbff910152be9264
    Image:         amazon/aws-cli:latest
    Image ID:      docker.io/amazon/aws-cli@sha256:21e6273f0025755abfc842ca39e8ef4fed3d9d2ce61d93bb16ce86a6c1668ae5
...
    **Environment:
      AWS_STS_REGIONAL_ENDPOINTS:   regional
      AWS_DEFAULT_REGION:           ap-northeast-2
      AWS_REGION:                   ap-northeast-2
      AWS_ROLE_ARN:                 arn:aws:iam::...:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-193..
      AWS_WEB_IDENTITY_TOKEN_FILE:  /var/run/secrets/eks.amazonaws.com/serviceaccount/token**
    Mounts:
      /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-2gvz2 (ro)
...
**Volumes:**
  **aws-iam-token:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  86400**
  kube-api-access-2gvz2:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       &lt;nil&gt;
    DownwardAPI:             true
...

$eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE    NAME                ROLE ARN
default        my-sa                arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-193TXMP0QLZSN
kube-system    aws-load-balancer-controller    arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-M4DYRCMI95LR
</code></pre>
<p><strong>테스트 진행</strong></p>
<pre><code class="language-bash">$kubectl exec -it eks-iam-test3 -- aws sts get-caller-identity --query Arn
&quot;arn:aws:sts::871103481195:assumed-role/eksctl-myeks-addon-iamserviceaccount-default-Role1-193TXMP0QLZSN/botocore-session-1685537063&quot;

# S3 접근 가능
$kubectl exec -it eks-iam-test3 -- aws s3 ls
2023-05-31 12:35:31 cf-templates-1pjjg014ag81h-ap-northeast-2

# 정책에서 S3 권한만 부여했으니,당연히 나머지 리소스는 접근하지 못한다. 
# ec2 접근 불가
$kubectl exec -it eks-iam-test3 -- aws ec2 describe-instances --region ap-northeast-2
An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation.
command terminated with exit code 254
# vpc 접근 불가 
$kubectl exec -it eks-iam-test3 -- aws ec2 describe-vpcs --region ap-northeast-2
An error occurred (UnauthorizedOperation) when calling the DescribeVpcs operation: You are not authorized to perform this operation.
command terminated with exit code 254
</code></pre>
<p><strong>AWS CloudTrail 로그를 통해 접근한 것을 확인할 수 있다.</strong> </p>
<h2 id="owasp-kubernetes-top-ten">OWASP Kubernetes Top Ten</h2>
<p>OWASP 에서 발표한 2022 쿠버네티스 환경 취약점 Top10 중 2가지에 대해 실습을 진행한다. 실습의 대부분은 악분님의 블로그를 참고한다. </p>
<p><a href="https://malwareanalysis.tistory.com/607">https://malwareanalysis.tistory.com/607</a></p>
<p><a href="https://malwareanalysis.tistory.com/606">https://malwareanalysis.tistory.com/606</a></p>
<h2 id="amazon-eks-best-practices-guide-for-security"><strong>Amazon EKS Best Practices Guide for Security</strong></h2>
<p><a href="https://aws.github.io/aws-eks-best-practices/security/docs/">https://aws.github.io/aws-eks-best-practices/security/docs/</a></p>
<h2 id="securing-secrets">Securing Secrets</h2>
<p>경험발표에서 공유해주신 하시코프에 Valut 시스템이다. 쿠버네티스의 보안을 지원한다. </p>
<ul>
<li><strong>Valut Secret Operator</strong> on K8s - <a href="https://youtu.be/NGLMPz3kAUU">Youtube</a> <a href="https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.html">개요</a> <a href="https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.html">설치</a> <a href="https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.html">실습</a> <a href="https://docmoa.github.io/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-integration-three-methods.html">비교</a> <a href="https://github.com/hashicorp/vault-secrets-operator/tree/main">GitHub</a><ul>
<li>VSO 실습 코드(Dynamic,PKI,Static) : <a href="https://github.com/hashicorp/vault-secrets-operator/tree/main/demo/infra/app">https://github.com/hashicorp/vault-secrets-operator/tree/main/demo/infra/app</a></li>
<li>VSO 실습 코드(Static) : <a href="https://github.com/hashicorp-education/learn-vault-secrets-operator/tree/main/vault">https://github.com/hashicorp-education/learn-vault-secrets-operator/tree/main/vault</a></li>
<li>Vault Static Secret 샘플 : <a href="https://developer.hashicorp.com/vault/tutorials/kubernetes/vault-secrets-operator">https://developer.hashicorp.com/vault/tutorials/kubernetes/vault-secrets-operator</a></li>
<li>VSO API Reference : <a href="https://developer.hashicorp.com/vault/docs/platform/k8s/vso/api-reference">https://developer.hashicorp.com/vault/docs/platform/k8s/vso/api-reference</a></li>
</ul>
</li>
</ul>
<h2 id="파드컨테이너-보안-컨텍스트">파드/컨테이너 보안 컨텍스트</h2>
<p><a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/">https://kubernetes.io/docs/tasks/configure-pod-container/security-context/</a></p>
<h3 id="참고링크">참고링크</h3>
<ul>
<li><p>OAuth
제 3자의 서비스에게 계정에 대한 정보를 줄 때, 계정이 아닌 Access Token 을 줌으로 알맞는 권한만 제공하는 방법</p>
</li>
<li><p>참고 링크</p>
<ol>
<li>AWS<ol>
<li>[Youtube] Amazon EKS 마이그레이션 요점정리(강인호) - <a href="https://youtu.be/bksogA-WXv8">링크</a></li>
<li>AWS EKS 마이그레이션 요점 정리로, EKS 관련 핵심 사항을 설명해준다. <a href="https://www.youtube.com/watch?v=bksogA-WXv8">YouTube</a></li>
<li>EKS 환경을 더 효율적으로, 더 안전하게 - 신은수 시큐리티 스페셜리스트 솔루션즈 아키텍트, AWS :: AWS Summit Korea 2022 - <a href="https://youtu.be/wgH9xL_48vM">링크</a> <a href="https://awskoreamarketingasset.s3.amazonaws.com/2022%20Summit/pdf/T10S1_EKS%20%ED%99%98%EA%B2%BD%EC%9D%84%20%EB%8D%94%20%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9C%BC%EB%A1%9C%20%EB%8D%94%20%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C.pdf">PDF</a></li>
</ol>
</li>
<li>[용찬호님] - EKS에서 쿠버네티스 포드의 IAM 권한 제어하기 - <a href="https://tech.devsisters.com/posts/pod-iam-role/">링크</a> / AWS IAM Authenticator - <a href="https://blog.naver.com/alice_k106/221967218283">링크</a> / OIDC 인증 - <a href="https://blog.naver.com/alice_k106/221598325656">링크</a></li>
<li>[커피고래님] - 인증 시리즈 <a href="https://coffeewhale.com/kubernetes/authentication/x509/2020/05/02/auth01/">X.509</a> <a href="https://coffeewhale.com/kubernetes/authentication/http-auth/2020/05/03/auth02/">HTTP인증</a> <a href="https://coffeewhale.com/kubernetes/authentication/oidc/2020/05/04/auth03/">OpenID Connect</a> <a href="https://coffeewhale.com/kubernetes/authentication/webhook/2020/05/05/auth04/">Webhook</a> <a href="https://coffeewhale.com/kubernetes/authentication/proxy/2020/05/06/auth05/">Proxy인증</a> , <a href="https://coffeewhale.com/kubernetes/admission-control/2021/04/28/opa1/">Admisstion Control</a><ul>
<li>AWS Cross-Accounts IRSA 적용기 - <a href="https://channel.io/ko/blog/tech-aws-cross-accounts-irsa">링크</a></li>
<li>OpenID(OIDC) 개념과 동작원리 - <a href="https://hudi.blog/open-id/">링크</a></li>
</ul>
</li>
<li>[Youtube] 생활코딩 OAuth 2.0 - <a href="https://www.youtube.com/watch?v=hm2r6LtUbk8&amp;list=PLuHgQVnccGMA4guyznDlykFJh28_R08Q-&amp;ab_channel=%EC%83%9D%ED%99%9C%EC%BD%94%EB%94%A9">링크</a></li>
<li>[learnk8s] User and workload identities in Kubernetes - <a href="https://learnk8s.io/authentication-kubernetes">링크</a><ul>
<li>Limiting access to Kubernetes resources with RBAC - <a href="https://learnk8s.io/rbac-kubernetes">링크</a></li>
<li>Implementing a custom Kubernetes authentication method - <a href="https://learnk8s.io/kubernetes-custom-authentication">링크</a></li>
<li>Authentication between microservices using Kubernetes identities - <a href="https://learnk8s.io/microservices-authentication-kubernetes">링크</a></li>
</ul>
</li>
<li>[Youtube] 쿠버네티스 해킹과 방어 (데모 포함)<ul>
<li>The Hacker&#39;s Guide to Kubernetes - Patrycja Wegrzynowicz, Form3 - <a href="https://youtu.be/Hf5qRgxjPLQ">링크</a></li>
<li>Hacking &amp; Defending Kubernetes Clusters - <a href="https://youtu.be/J3pS-XBdNpU">링크</a></li>
</ul>
</li>
<li>[Youtube] Kubecon 2023 Europe<ul>
<li>Keycloak: The Open-Source IAM for Modern Application - <a href="https://youtu.be/j7FwsYJCocA">링크</a></li>
<li>Kyverno Introduction and Deep Dive - <a href="https://youtu.be/Es_JgpR0wbg">링크</a></li>
<li>Open Policy Agent. (OPA) Intro &amp; Deep Dive - <a href="https://youtu.be/6RNp3m_THw4">링크</a></li>
</ul>
</li>
<li>AWS Blog<ul>
<li>Amazon EKS 환경에서 Pod Security Standard 구현하기 - <a href="https://aws.amazon.com/ko/blogs/tech/implementing-pod-security-standards-in-amazon-eks/">링크</a></li>
<li>Validating Amazon EKS optimized Bottlerocket AMI against the CIS Benchmark - <a href="https://aws.amazon.com/blogs/containers/validating-amazon-eks-optimized-bottlerocket-ami-against-the-cis-benchmark/">링크</a></li>
<li>Managing access to Amazon Elastic Kubernetes Service clusters with X.509 certificates - <a href="https://aws.amazon.com/blogs/containers/managing-access-to-amazon-elastic-kubernetes-service-clusters-with-x-509-certificates/">링크</a></li>
<li>Managing Pod Security on Amazon EKS with Kyverno - <a href="https://aws.amazon.com/blogs/containers/managing-pod-security-on-amazon-eks-with-kyverno/">링크</a><ul>
<li>Implementing Pod Security Standards in Amazon EKS - <a href="https://aws.amazon.com/blogs/containers/implementing-pod-security-standards-in-amazon-eks/">링크</a></li>
</ul>
</li>
<li>Preventing Kubernetes misconfigurations using Datree - <a href="https://aws.amazon.com/blogs/containers/preventing-kubernetes-misconfigurations-using-datree/">링크</a></li>
<li>Building Amazon Linux 2 CIS Benchmark AMIs for Amazon EKS - <a href="https://aws.amazon.com/blogs/containers/building-amazon-linux-2-cis-benchmark-amis-for-amazon-eks/">링크</a></li>
<li>Secure Bottlerocket deployments on Amazon EKS with KubeArmor - <a href="https://aws.amazon.com/blogs/containers/secure-bottlerocket-deployments-on-amazon-eks-with-kubearmor/">링크</a></li>
<li>Diving into IAM Roles for Service Accounts - <a href="https://aws.amazon.com/blogs/containers/diving-into-iam-roles-for-service-accounts/">링크</a></li>
<li>Leverage AWS secrets stores from EKS Fargate with External Secrets Operator - <a href="https://aws.amazon.com/ko/blogs/containers/leverage-aws-secrets-stores-from-eks-fargate-with-external-secrets-operator/">링크</a></li>
<li>Building STIG-compliant AMIs for Amazon EKS - <a href="https://aws.amazon.com/ko/blogs/containers/building-stig-compliant-amis-for-amazon-eks/">링크</a></li>
</ul>
</li>
<li>(참고) Valut Operator - <a href="https://youtu.be/NGLMPz3kAUU">Youtube</a> <a href="https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.html">개요</a> <a href="https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.html">설치</a> <a href="https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.html">실습</a></li>
<li>(참고) OWASP Kubernetes Top Ten - <a href="https://owasp.org/www-project-kubernetes-top-ten/">링크</a></li>
<li>(참고) Amazon Linux 2 Security Advisories - <a href="https://alas.aws.amazon.com/alas2.html">링크</a></li>
</ol>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[EKS 스터디 5주차]]></title>
            <link>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-5%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-5%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Tue, 23 May 2023 11:15:02 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<p>이번 주차에는 오토스케일링에 대해 진행했다. 파드의 오토스케일링으로 HPA, VPA 그리고 노드에 비례해서 파드 개수를 조정하는 CPA까지 있다. 노드를 스케줄링하는 KEDA, CA, Karpenter 까지 진행하면서 이번주차는 끝난다.</p>
<p>파드의 스케줄링은 파드의 리소스 사용량을 기준으로 진행한다. 파드의 개수를 늘리는 것은 부하분산이 없다면 의미없는 일이지만, <code>selector</code> 를 통한 부하분산을 지원하기에 효과적이다. 또한, VPA는 리소스를 늘리나 재실행이 필요하다는 단점이 있다. [앞으로 재실행없이 리소스 변경이 가능할지도 모른다. (<a href="https://kubernetes.io/blog/2023/05/12/in-place-pod-resize-alpha/">Docs</a>)], HPA -VPA 모두 쿠버네티스에서 지원해줘, 플러그인 설치 없이 진행가능하다. </p>
<p>노드를 스케줄링하는 CA, KEDA, Karpenter가 있다. CA는 파드를 통해 모니터링하고, 리소스 메트릭을 통해 스케일링한다. KEDA는 CA와는 다르게 리소스 메트릭이 아닌 이벤트 기반으로 스케일여부를 결정한다. 마지막으로 Karpenter는 다른 오토스케일링과 다르게 초 단위로 컴퓨팅 리소스를 제공한다. </p>
<p>Karpenter를 마지막으로 실습은 종료된다. 주제가 오토스케일링이다보니 끝나고 나서 자원을 꼭 삭제해줘야한다. </p>
<h3 id="용어설명">용어설명</h3>
<ul>
<li>스케일 아웃, 스케일 인</li>
</ul>
<p>스케일 아웃은 기존의 인프라이외에 새로운 인프라를 추가해서 확장하는 방식이다. ‘스케일인’은 반대</p>
<ul>
<li>스케일 업, 스케일 다운 </li>
</ul>
<p>스케일 업은 기존의 인프라를 확장하는 것이다. ‘스케일 다운’은 반대! ex) CPU 변경, RAM추가</p>
<h3 id="배포환경">배포환경</h3>
<p>기존의 배포환경과 동일하게, kube-ops-view, 프로메테우스, 그라파나까지 설치한다. 관련된 배포는 이전주차를 참고하면 된다.</p>
<p>이번 주차에서는 노드의 오토스케일링을 확인하기 위해 EKS Node Viewer를 설치하여 각 노드들의 CPU 사용량을 모니터링한다.</p>
<blockquote>
<p><strong>EKS Node Viewer</strong> 설치 : <strong>노드 할당 가능 용량</strong>과 <strong>요청 request 리소스 표시</strong>, <strong>실제 파드 리소스 사용량 X</strong></p>
</blockquote>
<pre><code class="language-bash"># Go 설치
$yum install -y go
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
...
# EKS Node Viewer 설치
$go install github.com/awslabs/eks-node-viewer/cmd/eks-node-viewer@latest

$tree ~/go/bin
/root/go/bin
└── eks-node-viewer
</code></pre>
<p>설치를 마무리 하고, 디렉토리에 들어가 <code>./eks-node-viewer</code> 를 실행하면 아래와 같이 노드를 모니터링화면이 나온다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/24711f0a-a5a1-498f-80ae-91064be028d9/image.png" alt=""></p>
<h3 id="pod-autuscaling">Pod AutuScaling</h3>
<p>해당 파트에서는 파드의 오토스케일링에 대해 실습한다. 아래의 그림에서 알 수 있듯이 같은 스펙의 파드를 증가시키는 HPA와 파드의 리소스를 증가시키는 VPA가 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/c819aee0-9736-4f54-802b-55f281cea929/image.png" alt=""></p>
<h4 id="hpa---horizontal-pod-autoscaler">HPA - Horizontal Pod Autoscaler</h4>
<p>HPA는 리소스 메트릭을 통해 파드의 리소스를 파악하여 스케일링한다. 아래의 아키텍처를 통해서 자세한 원리를 알 수 있다. HPA로 진행되는 파드는 로드밸런싱 설정을 진행해야 한다. 로드밸런싱을 통해 하나의 파드만 부하를 받는 것이 아닌 새로운 파드까지 부하를 분담한다.(selector) 그렇기에 효율적으로 리소스를 관리할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/644c35db-8ef8-4da3-b957-da3f1d9bb4be/image.png" alt=""></p>
<p><strong>그림 출처 - (🧝🏻‍♂️)김태민 기술 블로그 - <a href="https://kubetm.github.io/k8s/08-intermediate-controller/hpa/">링크</a></strong></p>
<p>테스트 애플리케이션 배포</p>
<pre><code class="language-bash">$curl -s -O https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/php-apache.yaml
$cat php-apache.yaml | yh
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: registry.k8s.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
          **requests:
            cpu: 200m**
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache

# 테스트 용 php-apache server 배포
$k apply -f php-apache.yaml
deployment.apps/php-apache created
service/php-apache created

# 확인, 연산의 복잡도를 주었다.
$kubectl exec -it deploy/php-apache -- cat /var/www/html/index.php
&lt;?php
$x = 0.0001;
for ($i = 0; $i &lt;= 1000000; $i++) {
    $x += sqrt($x);
}
echo &quot;OK!&quot;;
?&gt;

$PODIP=$(kubectl get pod -l run=php-apache -o jsonpath={.items[0].status.podIP})
# 위와 같이 커리를 날리면 위의 연산 후 트래픽이 날아온다.
$curl -s $PODIP; echo
OK!</code></pre>
<p>HPA 설정 및 확인</p>
<pre><code class="language-bash"># cpu 사용량을 기준으로 HPA
$kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
horizontalpodautoscaler.autoscaling/php-apache autoscaled
# 확인 가능
$kubectl describe hpa
Warning: autoscaling/v2beta2 HorizontalPodAutoscaler is deprecated in v1.23+, unavailable in v1.26+; use autoscaling/v2 HorizontalPodAutoscaler
Name:                                                  php-apache
Namespace:                                             default
Labels:                                                &lt;none&gt;
Annotations:                                           &lt;none&gt;
CreationTimestamp:                                     Sun, 21 May 2023 22:33:48 +0900
Reference:                                             Deployment/php-apache
Metrics:                                               ( current / target )
  resource cpu on pods  (as a percentage of request):  0% (1m) / 50%
Min replicas:                                          1
Max replicas:                                          10
Deployment pods:                                       1 current / 1 desired
Conditions:
  Type            Status  Reason               Message
  ----            ------  ------               -------
  AbleToScale     True    ScaleDownStabilized  recent recommendations were higher than current one, applying the highest recent recommendation
  ScalingActive   True    ValidMetricFound     the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)
  ScalingLimited  False   DesiredWithinRange   the desired count is within the acceptable range
Events:           &lt;none&gt;
</code></pre>
<p>매니페스트에서 상태값은 버리고, 정리해주는 툴인 <code>neat</code> 플러그인을 설치한다.</p>
<pre><code class="language-bash">$kubectl krew install neat
...
# 상태값까지 출력되어 보기 불편하다.
$kubectl get hpa php-apache -o yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  creationTimestamp: &quot;2023-05-21T13:33:48Z&quot;
  name: php-apache
  namespace: default
  resourceVersion: &quot;6344&quot;
  uid: cae373a4-fb84-4ff3-9945-d19984fe811c
spec:
  maxReplicas: 10
  metrics:
  - resource:
      name: cpu
      target:
        averageUtilization: 50
        type: Utilization
    type: Resource
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
status:
  conditions:
  - lastTransitionTime: &quot;2023-05-21T13:34:03Z&quot;
    message: recent recommendations were higher than current one, applying the highest
      recent recommendation
    reason: ScaleDownStabilized
    status: &quot;True&quot;
    type: AbleToScale
  - lastTransitionTime: &quot;2023-05-21T13:34:03Z&quot;
    message: the HPA was able to successfully calculate a replica count from cpu resource
      utilization (percentage of request)
    reason: ValidMetricFound
    status: &quot;True&quot;
    type: ScalingActive
  - lastTransitionTime: &quot;2023-05-21T13:34:03Z&quot;
    message: the desired count is within the acceptable range
    reason: DesiredWithinRange
    status: &quot;False&quot;
    type: ScalingLimited
  currentMetrics:
  - resource:
      current:
        averageUtilization: 0
        averageValue: 1m
      name: cpu
    type: Resource
  currentReplicas: 1
  desiredReplicas: 1

# 깔끔하게 출력되는 모습
# 아래의 내용을 확인하면 최대 10개이고 기준은 CPU의 평균활용량 50$이다!, 50%를 넘으면 자동으로 새로운 파드가 생성된다.
$kubectl get hpa php-apache -o yaml | kubectl neat | yh
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
  namespace: default
spec:
  **maxReplicas: 10**
  metrics:
  - resource:
      **name: cpu**
      target:
        **averageUtilization: 50**
        type: Utilization
    type: Resource
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache</code></pre>
<p>부하를 주어 테스트하는 모습, 파드가 증가하는 모습은 아래의 사진을 통해 자세하게 확인할 수 있다!</p>
<pre><code class="language-bash">
$while true;do curl -s $PODIP; sleep 0.5; done
OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!^C
$while true;do curl -s $PODIP; sleep 0.3; done
OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!^C
$kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c &quot;while sleep 0.01; do wget -q -O- http://php-apache; done&quot;
If you don&#39;t see a command prompt, try pressing enter.
OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!
OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!
OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!OK!

$kubectl delete deploy,svc,hpa,pod --all
deployment.apps &quot;php-apache&quot; deleted
service &quot;kubernetes&quot; deleted
service &quot;php-apache&quot; deleted
horizontalpodautoscaler.autoscaling &quot;php-apache&quot; deleted
pod &quot;load-generator&quot; deleted
pod &quot;php-apache-698db99f59-4s7d7&quot; deleted
pod &quot;php-apache-698db99f59-qkd2h&quot; deleted
pod &quot;php-apache-698db99f59-wcxvg&quot; deleted
pod &quot;php-apache-698db99f59-wh62k&quot; deleted
pod &quot;php-apache-698db99f59-x2x5r&quot; deleted
pod &quot;php-apache-698db99f59-zblfs&quot; deleted</code></pre>
<p>파드 하나당 CPU활용량이 50% 미만이되려면 6~7개정도의 파드가 생성되어야 했다. apache 서비스를 통해 지속적으로 트래픽을 날리는 것이므로 <code>selector</code> 에 의해 자동으로 로드밸런싱이 된다. 그렇기에 부하가 분산이 가능해 7개면 50%미만으로 떨어진다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/d0714bc2-0101-40ec-b760-a2fee9cbc5e2/image.png" alt=""></p>
<p><strong>그라파나 대시보드를 통해 확인한 파드의 개수!</strong></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/e824835f-9fbf-47b3-98f1-3caf169a244e/image.png" alt=""></p>
<h4 id="vpa---vertical-pod-autoscaler"><strong>VPA - Vertical Pod Autoscaler</strong></h4>
<p>HPA는 동일한 스펙의 파드의 개수를 늘리는 것이었다면, VPA는 현재 실행중인 파드의 리소스를 확장한다. 이 과정에서 재시작이 필요하다는 단점이 있다. 파드를 재시작해야되기에, 실제 운영환경에서는 별로 추천하지 않는다고 한다. 스터디원분이 알려주신 아래의 문서와 같이 재실행없이 리소스 변경이 가능하다면 운영환경에서도 쓸모있을 것 같다.</p>
<blockquote>
<p><a href="https://kubernetes.io/blog/2023/05/12/in-place-pod-resize-alpha/">https://kubernetes.io/blog/2023/05/12/in-place-pod-resize-alpha/</a></p>
</blockquote>
<p>또한, VPA는 메트릭서버를 통해 파드의 로그를 수집하여 리소스 최적값을 추천해준다. 아래의 아키텍처를 통해 작동방식을 파악할 수 있다. </p>
<p>[악분님의 블로그 참고! <a href="https://malwareanalysis.tistory.com/603">https://malwareanalysis.tistory.com/603</a>]</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/1c84e6f1-6249-49e2-9114-eacecfe6b8cc/image.png" alt=""></p>
<p>그림 출처 : <a href="https://devocean.sk.com/blog/techBoardDetail.do?ID=164786&amp;boardType=techBlog&amp;searchData=&amp;page=&amp;subIndex=%EC%B5%9C%EC%8B%A0+%EA%B8%B0%EC%88%A0+%EB%B8%94%EB%A1%9C%EA%B7%B8">Blog</a></p>
<p>이제 쿠버네티스에서 제공한 예시를 통해 실습을 진행한다.</p>
<pre><code class="language-bash"># 쿠버네티스 예시 프로젝트 
git clone https://github.com/kubernetes/autoscaler.git
cd ~/autoscaler/vertical-pod-autoscaler/
# openssl 업그레이드, (v1.1.1 이상이어야 한다.)
yum install openssl11 -y
sed -i &#39;s/openssl/openssl11/g&#39; ~/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/gencerts.sh
# 배포!
kubectl apply -f examples/hamster.yaml &amp;&amp; kubectl get vpa -w
</code></pre>
<p>아래는 배포된 <code>yaml</code> 파일이다. 파일에서 설정된 리소스 cpu 100m, memory 50Mi를 확인할 수 있지만, 추후 VPA에 의해 파드가 재생성되면서 리소스를 변경시킨다. </p>
<pre><code class="language-bash"># 아래의 예시 yaml 파일에서 설정된 리소스 cpu 100m, memory 50Mi
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hamster
spec:
  selector:
    matchLabels:
      app: hamster
  replicas: 2
  template:
    metadata:
      labels:
        app: hamster
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534 # nobody
      containers:
        - name: hamster
          image: registry.k8s.io/ubuntu-slim:0.1
          resources:
            requests:
              cpu: 100m
              memory: 50Mi
          command: [&quot;/bin/sh&quot;]
          args:
            - &quot;-c&quot;
            - &quot;while true; do timeout 0.5s yes &gt;/dev/null; sleep 0.5s; done&quot;</code></pre>
<p>아래의 터미널 사진에서 상위를 보면 VPA에 의해 파드가 재생성된 것을 알 수 있고, 하단 오른쪽을 보면 CPU 요청량이 달라진 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/09879010-1094-42d2-a62f-50f1fdb2eca6/image.png" alt=""></p>
<h4 id="krr"><strong>KRR</strong></h4>
<p>이번에 AWS에서 지원해주는 툴이며, 메트릭을 수집하고 스스로 판단하여 추천을 해준다고 한다. 아래의 그림을 참고하면 자세하게 리소스별 추천을 해주는 것을 확인할 수 있다.</p>
<blockquote>
<p><code>KRR</code> : Prometheus-based <strong>K</strong>ubernetes <strong>R</strong>esource <strong>R</strong>ecommendations - <a href="https://github.com/robusta-dev/krr#getting-started">링크</a> &amp; Youtube - <a href="https://www.youtube.com/live/uITOzpf82RY?feature=share">링크</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/han-0315/post/2569d889-253b-4f7f-9ee2-b57225bb3a22/image.png" alt=""></p>
<h3 id="keda---kubernetes-based-event-driven-autoscaler">KEDA - Kubernetes based Event Driven Autoscaler</h3>
<p>리소스 메트릭이 아닌 특정 이벤트를 기준으로 노드를 오토스케일링한다.</p>
<p>KEDA는 전용 메트릭서버를 별도로 둔다. 아마 메트릭관련 내용이 조금 달라서 그런 것 같다.!</p>
<p>또한, 그라파나의 공식대시보드에 없어서, Github에서 config파일을 복사하고, <code>import</code> 한다. </p>
<p><strong>배포된 kube-ops-view 사진</strong></p>
<pre><code class="language-bash"># KEDA 설치
$cat &lt;&lt;EOT &gt; keda-values.yaml
&gt; metricsServer:
&gt;   useHostNetwork: true
&gt;
&gt; prometheus:
&gt;   metricServer:
&gt;     enabled: true
&gt;     port: 9022
&gt;     portName: metrics
&gt;     path: /metrics
&gt;     serviceMonitor:
&gt;       # Enables ServiceMonitor creation for the Prometheus Operator
&gt;       enabled: true
&gt;     podMonitor:
&gt;       # Enables PodMonitor creation for the Prometheus Operator
&gt;       enabled: true
&gt;   operator:
&gt;     enabled: true
&gt;     port: 8080
&gt;     serviceMonitor:
&gt;       # Enables ServiceMonitor creation for the Prometheus Operator
&gt;       enabled: true
&gt;     podMonitor:
&gt;       # Enables PodMonitor creation for the Prometheus Operator
&gt;       enabled: true
&gt;
&gt;   webhooks:
&gt;     enabled: true
&gt;     port: 8080
&gt;     serviceMonitor:
&gt;       # Enables ServiceMonitor creation for the Prometheus webhooks
&gt;       enabled: true
&gt; EOT
# 별도의 네임스페이스 생성
$kubectl create namespace keda
namespace/keda created

$helm repo add kedacore https://kedacore.github.io/charts
&quot;kedacore&quot; has been added to your repositories

# helm을 통한 배포
$helm install keda kedacore/keda --version 2.10.2 --namespace keda -f keda-values.yaml
NAME: keda
LAST DEPLOYED: Sun May 21 22:45:17 2023
NAMESPACE: keda
STATUS: deployed
REVISION: 1
TEST SUITE: None
# KEDA 설치 확인
$kubectl get-all -n keda
W0521 22:45:24.843016   14118 client.go:102] Could not fetch complete list of API resources, results will be incomplete: unable to retrieve the complete list of server APIs: external.metrics.k8s.io/v1beta1: the server is currently unable to handle the request
NAME                                                                  NAMESPACE  AGE
configmap/kube-root-ca.crt                                            keda       26s
endpoints/keda-admission-webhooks                                     keda       5s
endpoints/keda-operator                                               keda       5s
endpoints/keda-operator-metrics-apiserver                             keda       5s
pod/keda-admission-webhooks-68cf687cbf-l6lpx                          keda       5s
pod/keda-operator-656478d687-4m47m                                    keda       5s
pod/keda-operator-metrics-apiserver-7fd585f657-xjltw                  keda       5s
secret/sh.helm.release.v1.keda.v1                                     keda       6s
serviceaccount/default                                                keda       26s
serviceaccount/keda-operator                                          keda       6s
service/keda-admission-webhooks                                       keda       5s
service/keda-operator                                                 keda       5s
service/keda-operator-metrics-apiserver                               keda       5s
deployment.apps/keda-admission-webhooks                               keda       5s
deployment.apps/keda-operator                                         keda       5s
deployment.apps/keda-operator-metrics-apiserver                       keda       5s
replicaset.apps/keda-admission-webhooks-68cf687cbf                    keda       5s
replicaset.apps/keda-operator-656478d687                              keda       5s
replicaset.apps/keda-operator-metrics-apiserver-7fd585f657            keda       5s
endpointslice.discovery.k8s.io/keda-admission-webhooks-jtdd2          keda       5s
endpointslice.discovery.k8s.io/keda-operator-7pdhn                    keda       5s
endpointslice.discovery.k8s.io/keda-operator-metrics-apiserver-9ltmf  keda       5s
podmonitor.monitoring.coreos.com/keda-operator                        keda       5s
podmonitor.monitoring.coreos.com/keda-operator-metrics-apiserver      keda       5s
servicemonitor.monitoring.coreos.com/keda-admission-webhooks          keda       5s
servicemonitor.monitoring.coreos.com/keda-operator                    keda       5s
servicemonitor.monitoring.coreos.com/keda-operator-metrics-apiserver  keda       5s
rolebinding.rbac.authorization.k8s.io/keda-operator                   keda       5s
role.rbac.authorization.k8s.io/keda-operator                          keda       6s
$kubectl get all -n keda
NAME                                                   READY   STATUS              RESTARTS   AGE
pod/keda-admission-webhooks-68cf687cbf-l6lpx           0/1     ContainerCreating   0          6s
pod/keda-operator-656478d687-4m47m                     0/1     ContainerCreating   0          6s
pod/keda-operator-metrics-apiserver-7fd585f657-xjltw   0/1     ContainerCreating   0          6s

NAME                                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                   AGE
service/keda-admission-webhooks           ClusterIP   10.100.253.109   &lt;none&gt;        443/TCP,8080/TCP          6s
service/keda-operator                     ClusterIP   10.100.239.105   &lt;none&gt;        9666/TCP,8080/TCP         6s
service/keda-operator-metrics-apiserver   ClusterIP   10.100.137.4     &lt;none&gt;        443/TCP,80/TCP,9022/TCP   6s

NAME                                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/keda-admission-webhooks           0/1     1            0           6s
deployment.apps/keda-operator                     0/1     1            0           6s
deployment.apps/keda-operator-metrics-apiserver   0/1     1            0           6s

NAME                                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/keda-admission-webhooks-68cf687cbf           1         1         0       6s
replicaset.apps/keda-operator-656478d687                     1         1         0       6s
replicaset.apps/keda-operator-metrics-apiserver-7fd585f657   1         1         0       6s
$kubectl get crd | grep keda
clustertriggerauthentications.keda.sh        2023-05-21T13:45:20Z
scaledjobs.keda.sh                           2023-05-21T13:45:20Z
scaledobjects.keda.sh                        2023-05-21T13:45:20Z
triggerauthentications.keda.sh               2023-05-21T13:45:20Z</code></pre>
<p>테스트를 위해, 위에서도 사용했던 php-apache 배포</p>
<pre><code class="language-bash"># 테스트용 디플로이 배포
$kubectl apply -f php-apache.yaml -n keda
deployment.apps/php-apache created
service/php-apache created

# php-apache 배포 확인
$kubectl get pod -n keda
NAME                                               READY   STATUS              RESTARTS      AGE
keda-admission-webhooks-68cf687cbf-l6lpx           1/1     Running             0             64s
keda-operator-656478d687-4m47m                     1/1     Running             1 (51s ago)   64s
keda-operator-metrics-apiserver-7fd585f657-xjltw   1/1     Running             0             64s
php-apache-698db99f59-rkqxp                        0/1     ContainerCreating   0             1s
</code></pre>
<p>특정시간(0,15,30,45)에 시작하고 (05,20,35,50)종료하는 이벤트 기반 정책 생성</p>
<pre><code class="language-bash"># ScaledObject 정책 생성 : cron
# triggers 부분에서 이벤트기반 정책을 확인할 수 있다.
$cat &lt;&lt;EOT &gt; keda-cron.yaml
&gt; apiVersion: keda.sh/v1alpha1
&gt; kind: ScaledObject
&gt; metadata:
&gt;   name: php-apache-cron-scaled
&gt; spec:
&gt;   minReplicaCount: 0
&gt;   maxReplicaCount: 2
&gt;   pollingInterval: 30
&gt;   cooldownPeriod: 300
&gt;   scaleTargetRef:
&gt;     apiVersion: apps/v1
&gt;     kind: Deployment
&gt;     name: php-apache
&gt;   triggers:
&gt;   - type: cron
&gt;     metadata:
&gt;       timezone: Asia/Seoul
&gt;       start: 00,15,30,45 * * * *
&gt;       end: 05,20,35,50 * * * *
&gt;       desiredReplicas: &quot;1&quot;
&gt; EOT

$kubectl apply -f keda-cron.yaml -n keda
scaledobject.keda.sh/php-apache-cron-scaled created

$k get ScaledObject -n  keda
NAME                     SCALETARGETKIND      SCALETARGETNAME   MIN   MAX   TRIGGERS   AUTHENTICATION   READY   ACTIVE   FALLBACK   AGE
php-apache-cron-scaled   apps/v1.Deployment   php-apache        0     2     cron                        True    False    Unknown    6m39s

아래의 명령어가 잘 안먹음
# $kubectl get ScaledObject -w

# 확인! 
$kubectl get ScaledObject,hpa,pod -n keda
NAME                                          SCALETARGETKIND      SCALETARGETNAME   MIN   MAX   TRIGGERS   AUTHENTICATION   READY   ACTIVE   FALLBACK   AGE
scaledobject.keda.sh/php-apache-cron-scaled   apps/v1.Deployment   php-apache        0     2     cron                        True    True     Unknown    2m45s

NAME                                                                  REFERENCE               TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/keda-hpa-php-apache-cron-scaled   Deployment/php-apache   &lt;unknown&gt;/1 (avg)   1         2         1          2m45s

NAME                                                   READY   STATUS    RESTARTS        AGE
pod/keda-admission-webhooks-68cf687cbf-l6lpx           1/1     Running   0               4m12s
pod/keda-operator-656478d687-4m47m                     1/1     Running   1 (3m59s ago)   4m12s
pod/keda-operator-metrics-apiserver-7fd585f657-xjltw   1/1     Running   0               4m12s
pod/php-apache-698db99f59-rkqxp                        1/1     Running   0               3m9s
# 위에서 ACTIVE인 이유는 현재 시각이 45분

$date
Sun May 21 22:50:43 KST 2023

# 50분이 되었으니 다시 명령 실행, 아래의 ACTIVE 속성이 &#39;False&#39;로 바뀜! ㄴ
$kubectl get ScaledObject,hpa,pod -n keda
NAME                                          SCALETARGETKIND      SCALETARGETNAME   MIN   MAX   TRIGGERS   AUTHENTICATION   READY   ACTIVE   FALLBACK   AGE
scaledobject.keda.sh/php-apache-cron-scaled   apps/v1.Deployment   php-apache        0     2     cron                        True    False    Unknown    4m15s

NAME                                                                  REFERENCE               TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/keda-hpa-php-apache-cron-scaled   Deployment/php-apache   &lt;unknown&gt;/1 (avg)   1         2         1          4m16s

NAME                                                   READY   STATUS    RESTARTS        AGE
pod/keda-admission-webhooks-68cf687cbf-l6lpx           1/1     Running   0               5m43s
pod/keda-operator-656478d687-4m47m                     1/1     Running   1 (5m30s ago)   5m43s
pod/keda-operator-metrics-apiserver-7fd585f657-xjltw   1/1     Running   0               5m43s
pod/php-apache-698db99f59-rkqxp                        1/1     Running   0               4m40s
# false 확인 가능!
$kubectl get ScaledObject,hpa,pod -n keda
NAME                                          SCALETARGETKIND      SCALETARGETNAME   MIN   MAX   TRIGGERS   AUTHENTICATION   READY   ACTIVE   FALLBACK   AGE
scaledobject.keda.sh/php-apache-cron-scaled   apps/v1.Deployment   php-apache        0     2     cron                        True    False    Unknown    4m47s

NAME                                                                  REFERENCE               TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/keda-hpa-php-apache-cron-scaled   Deployment/php-apache   &lt;unknown&gt;/1 (avg)   1         2         1          4m47s

NAME                                                   READY   STATUS    RESTARTS       AGE
pod/keda-admission-webhooks-68cf687cbf-l6lpx           1/1     Running   0              6m14s
pod/keda-operator-656478d687-4m47m                     1/1     Running   1 (6m1s ago)   6m14s
pod/keda-operator-metrics-apiserver-7fd585f657-xjltw   1/1     Running   0              6m14s
pod/php-apache-698db99f59-rkqxp                        1/1     Running   0              5m11s
$k get ScaledObject
No resources found in default namespace.
$kubectl get pod -n keda
NAME                                               READY   STATUS    RESTARTS        AGE
keda-admission-webhooks-68cf687cbf-l6lpx           1/1     Running   0               8m32s
keda-operator-656478d687-4m47m                     1/1     Running   1 (8m19s ago)   8m32s
keda-operator-metrics-apiserver-7fd585f657-xjltw   1/1     Running   0               8m32s
php-apache-698db99f59-rkqxp                        1/1     Running   0               7m29s

# ScaledObject 확인 가능! 
$kubectl describe -n keda ScaledObject
Name:         php-apache-cron-scaled
Namespace:    keda
Labels:       scaledobject.keda.sh/name=php-apache-cron-scaled
Annotations:  &lt;none&gt;
API Version:  keda.sh/v1alpha1
Kind:         ScaledObject
Metadata:
  Creation Timestamp:  2023-05-21T13:46:48Z
  Finalizers:
    finalizer.keda.sh
  Generation:  1
  Managed Fields:
    API Version:  keda.sh/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:finalizers:
          .:
          v:&quot;finalizer.keda.sh&quot;:
        f:labels:
          .:
          f:scaledobject.keda.sh/name:
    Manager:      keda
    Operation:    Update
    Time:         2023-05-21T13:46:48Z
    API Version:  keda.sh/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        .:
        f:conditions:
        f:externalMetricNames:
        f:hpaName:
        f:lastActiveTime:
        f:originalReplicaCount:
        f:scaleTargetGVKR:
          .:
          f:group:
          f:kind:
          f:resource:
          f:version:
        f:scaleTargetKind:
    Manager:      keda
    Operation:    Update
    Subresource:  status
    Time:         2023-05-21T13:46:48Z
    API Version:  keda.sh/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:cooldownPeriod:
        f:maxReplicaCount:
        f:minReplicaCount:
        f:pollingInterval:
        f:scaleTargetRef:
          .:
          f:apiVersion:
          f:kind:
          f:name:
        f:triggers:
    Manager:         kubectl-client-side-apply
    Operation:       Update
    Time:            2023-05-21T13:46:48Z
  Resource Version:  10339
  UID:               ac336308-4464-4c86-87d8-307de28d4df5
Spec:
  Cooldown Period:    300
  Max Replica Count:  2
  Min Replica Count:  0
  Polling Interval:   30
  Scale Target Ref:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         php-apache
  Triggers:
    Metadata:
      Desired Replicas:  1
      End:               05,20,35,50 * * * *
      Start:             00,15,30,45 * * * *
      Timezone:          Asia/Seoul
    Type:                cron
Status:
  Conditions:
    Message:  ScaledObject is defined correctly and is ready for scaling
    Reason:   ScaledObjectReady
    Status:   True
    Type:     Ready
    Message:  Scaler cooling down because triggers are not active
    Reason:   ScalerCooldown
    Status:   False
    Type:     Active
    Status:   Unknown
    Type:     Fallback
  External Metric Names:
    s0-cron-Asia-Seoul-00,15,30,45xxxx-05,20,35,50xxxx
  Hpa Name:                keda-hpa-php-apache-cron-scaled
  Last Active Time:        2023-05-21T13:49:48Z
  Original Replica Count:  1
  Scale Target GVKR:
    Group:            apps
    Kind:             Deployment
    Resource:         deployments
    Version:          v1
  Scale Target Kind:  apps/v1.Deployment
Events:
  Type    Reason              Age    From           Message
  ----    ------              ----   ----           -------
  Normal  KEDAScalersStarted  7m56s  keda-operator  Started scalers watch
  Normal  ScaledObjectReady   7m56s  keda-operator  ScaledObject is ready for scaling
$date
Sun May 21 22:55:17 KST 2023
</code></pre>
<p>keda-operator 자세히 확인!</p>
<pre><code class="language-bash">
$k -n keda describe pod keda-operator
Name:             keda-operator-656478d687-4m47m
Namespace:        keda
Priority:         0
Service Account:  keda-operator
Node:             ip-192-168-3-10.ap-northeast-2.compute.internal/192.168.3.10
Start Time:       Sun, 21 May 2023 22:45:21 +0900
Labels:           app=keda-operator
                  app.kubernetes.io/component=operator
                  app.kubernetes.io/instance=keda
                  app.kubernetes.io/managed-by=Helm
                  app.kubernetes.io/name=keda-operator
                  app.kubernetes.io/part-of=keda-operator
                  app.kubernetes.io/version=2.10.1
                  helm.sh/chart=keda-2.10.2
                  name=keda-operator
                  pod-template-hash=656478d687
Annotations:      container.seccomp.security.alpha.kubernetes.io/keda-operator: runtime/default
                  kubernetes.io/psp: eks.privileged
Status:           Running
IP:               192.168.3.180
IPs:
  IP:           192.168.3.180
Controlled By:  ReplicaSet/keda-operator-656478d687
Containers:
  keda-operator:
    Container ID:  containerd://e2fc885cb68003dce46ae15bb58f1b2156a4389bb55c6961cde0182fae232697
    Image:         ghcr.io/kedacore/keda:2.10.1
    Image ID:      ghcr.io/kedacore/keda@sha256:1489b706aa959a07765510edb579af34fa72636a26cfb755544c0ef776f3addf
    Port:          8080/TCP
    Host Port:     0/TCP
    Command:
      /keda
    Args:
      --leader-elect
      --zap-log-level=info
      --zap-encoder=console
      --zap-time-encoding=rfc3339
      --cert-dir=/certs
      --enable-cert-rotation=true
      --cert-secret-name=kedaorg-certs
      --operator-service-name=keda-operator
      --metrics-server-service-name=keda-operator-metrics-apiserver
      --webhooks-service-name=keda-admission-webhooks
      --metrics-bind-address=:8080
    State:          Running
      Started:      Sun, 21 May 2023 22:45:36 +0900
    Last State:     Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Sun, 21 May 2023 22:45:30 +0900
      Finished:     Sun, 21 May 2023 22:45:34 +0900
    Ready:          True
    Restart Count:  1
    Limits:
      cpu:     1
      memory:  1000Mi
    Requests:
      cpu:      100m
      memory:   100Mi
    Liveness:   http-get http://:8081/healthz delay=25s timeout=1s period=10s #success=1 #failure=3
    Readiness:  http-get http://:8081/readyz delay=20s timeout=1s period=10s #success=1 #failure=3
    Environment:
      WATCH_NAMESPACE:
      POD_NAME:                   keda-operator-656478d687-4m47m (v1:metadata.name)
      POD_NAMESPACE:              keda (v1:metadata.namespace)
      OPERATOR_NAME:              keda-operator
      KEDA_HTTP_DEFAULT_TIMEOUT:  3000
      KEDA_HTTP_MIN_TLS_VERSION:  TLS12
    Mounts:
      /certs from certificates (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-5ktsx (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  certificates:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  kedaorg-certs
    Optional:    true
  kube-api-access-5ktsx:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       &lt;nil&gt;
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              kubernetes.io/os=linux
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason       Age                From               Message
  ----     ------       ----               ----               -------
  Normal   Scheduled    10m                default-scheduler  Successfully assigned keda/keda-operator-656478d687-4m47m to ip-192-168-3-10.ap-northeast-2.compute.internal
  Warning  FailedMount  10m                kubelet            MountVolume.SetUp failed for volume &quot;certificates&quot; : failed to sync secret cache: timed out waiting for the condition
  Normal   Pulled       10m                kubelet            Successfully pulled image &quot;ghcr.io/kedacore/keda:2.10.1&quot; in 6.362549579s
  Normal   Pulling      10m (x2 over 10m)  kubelet            Pulling image &quot;ghcr.io/kedacore/keda:2.10.1&quot;
  Normal   Created      10m (x2 over 10m)  kubelet            Created container keda-operator
  Normal   Started      10m (x2 over 10m)  kubelet            Started container keda-operator
  Normal   Pulled       10m                kubelet            Successfully pulled image &quot;ghcr.io/kedacore/keda:2.10.1&quot; in 699.131003ms

Name:             keda-operator-metrics-apiserver-7fd585f657-xjltw
Namespace:        keda
Priority:         0
Service Account:  keda-operator
Node:             ip-192-168-3-10.ap-northeast-2.compute.internal/192.168.3.10
Start Time:       Sun, 21 May 2023 22:45:21 +0900
Labels:           app=keda-operator-metrics-apiserver
                  app.kubernetes.io/component=operator
                  app.kubernetes.io/instance=keda
                  app.kubernetes.io/managed-by=Helm
                  app.kubernetes.io/name=keda-operator-metrics-apiserver
                  app.kubernetes.io/part-of=keda-operator
                  app.kubernetes.io/version=2.10.1
                  helm.sh/chart=keda-2.10.2
                  pod-template-hash=7fd585f657
Annotations:      container.seccomp.security.alpha.kubernetes.io/keda-operator-metrics-apiserver: runtime/default
                  kubernetes.io/psp: eks.privileged
Status:           Running
IP:               192.168.3.10
IPs:
  IP:           192.168.3.10
Controlled By:  ReplicaSet/keda-operator-metrics-apiserver-7fd585f657
Containers:
  keda-operator-metrics-apiserver:
    Container ID:  containerd://8eca27ff65b4e907ece4d4fe53b8b85920a1023ca070dc806f0c32be612134df
    Image:         ghcr.io/kedacore/keda-metrics-apiserver:2.10.1
    Image ID:      ghcr.io/kedacore/keda-metrics-apiserver@sha256:d1f1ccc8d14e33ee448ec0c820f65b8a3e01b2dad23d9fa38fa7204a6c0194ca
    Ports:         6443/TCP, 8080/TCP, 9022/TCP
    Host Ports:    6443/TCP, 8080/TCP, 9022/TCP
    Args:
      /usr/local/bin/keda-adapter
      --port=8080
      --secure-port=6443
      --logtostderr=true
      --metrics-service-address=keda-operator.keda.svc.cluster.local:9666
      --client-ca-file=/certs/ca.crt
      --tls-cert-file=/certs/tls.crt
      --tls-private-key-file=/certs/tls.key
      --cert-dir=/certs
      --metrics-port=9022
      --metrics-path=/metrics
      --v=0
    State:          Running
      Started:      Sun, 21 May 2023 22:45:46 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     1
      memory:  1000Mi
    Requests:
      cpu:      100m
      memory:   100Mi
    Liveness:   http-get https://:6443/healthz delay=5s timeout=1s period=10s #success=1 #failure=3
    Readiness:  http-get https://:6443/readyz delay=5s timeout=1s period=10s #success=1 #failure=3
    Environment:
      WATCH_NAMESPACE:
      POD_NAMESPACE:              keda (v1:metadata.namespace)
      KEDA_HTTP_DEFAULT_TIMEOUT:  3000
      KEDA_HTTP_MIN_TLS_VERSION:  TLS12
    Mounts:
      /certs from certificates (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-psqzd (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  certificates:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  kedaorg-certs
    Optional:    false
  kube-api-access-psqzd:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       &lt;nil&gt;
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              kubernetes.io/os=linux
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason       Age                From               Message
  ----     ------       ----               ----               -------
  Normal   Scheduled    10m                default-scheduler  Successfully assigned keda/keda-operator-metrics-apiserver-7fd585f657-xjltw to ip-192-168-3-10.ap-northeast-2.compute.internal
  Warning  FailedMount  10m                kubelet            MountVolume.SetUp failed for volume &quot;certificates&quot; : failed to sync secret cache: timed out waiting for the condition
  Warning  FailedMount  10m (x4 over 10m)  kubelet            MountVolume.SetUp failed for volume &quot;certificates&quot; : secret &quot;kedaorg-certs&quot; not found
  Normal   Pulling      10m                kubelet            Pulling image &quot;ghcr.io/kedacore/keda-metrics-apiserver:2.10.1&quot;
  Normal   Pulled       10m                kubelet            Successfully pulled image &quot;ghcr.io/kedacore/keda-metrics-apiserver:2.10.1&quot; in 6.593596109s
  Normal   Created      10m                kubelet            Created container keda-operator-metrics-apiserver
  Normal   Started      10m                kubelet            Started container keda-operator-metrics-apiserver
</code></pre>
<p><img src="https://velog.velcdn.com/images/han-0315/post/5c755392-ebb5-4a93-b56d-c623a9c712dc/image.png" alt=""></p>
<p>결과가 진행된 그라파나 대시보드 사진(50분전에 시작하고,,, 55분에 끝나는 조금 이상하지만?!) 암튼 이벤트기반으로 스케일링된다!</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/31fb45aa-fb3a-4c93-b9d2-c4a5eb4fbbbf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/80a96400-ef13-4954-9e8e-662eb31b156d/image.png" alt=""></p>
<p><code>[도전과제2]</code> KEDA 활용 : Karpenter + KEDA로 특정 시간에 AutoScaling - <a href="https://jenakim47.tistory.com/90">링크</a> <a href="https://youtu.be/FPlCVVrCD64">Youtube</a> <a href="https://swalloow.github.io/airflow-worker-keda-autoscaler/">Airflow</a></p>
<p>→ 은행의 월요일아침, 이벤트 추첨시간 등 대규모 트래픽이 예상되는 시간에 KEDA를 통해, 오토스케일링</p>
<h3 id="ca---cluster-autoscalercas">CA - Cluster Autoscaler(CAS)</h3>
<p>CA는 노드의 리소스를 파악하고, 리소스 메트릭에 의해 트리거되어 노드를 스케일링한다. 오토스케일러 동작을 위해 별도의 디플로이를 배포해둔다. CA는 <code>pending</code> 상태의 파드가 존재할 경우, 워커노드를 스케일 아웃한다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/2a067472-4819-4d25-9124-a8ed2c24a7c7/image.png" alt=""></p>
<p>클라우드 플랫폼에서 주로 사용하고, 실제 노드 리소스가 없는 데 파드를 배포해야 하면 신규 워커노드를 추가한다. 온프라미스에선 적용하긴 힘들다.</p>
<p><strong>실습진행</strong></p>
<p>일부로 워커노드가 감당못하게 request를 500m = 0.5 코어로 할당 → 스케일링을 통해 15개로 늘림 → 노드 부족!</p>
<pre><code class="language-bash">$**aws autoscaling describe-auto-scaling-groups --query &quot;AutoScalingGroups[? Tags[? (Key==&#39;eks:cluster-name&#39;) &amp;&amp; Value==&#39;myeks&#39;]].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]&quot; --output table**
-----------------------------------------------------------------
|                   DescribeAutoScalingGroups                   |
+------------------------------------------------+----+----+----+
|  eks-ng1-c2c41e26-6213-a429-9a58-02374389d5c3  |  3 |  6 |  3 |
+------------------------------------------------+----+----+----+
아래는 min, max, desired</code></pre>
<pre><code class="language-bash"># autuscaler 권한 확인(관련 태그)
$aws ec2 describe-instances  --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query &quot;Reservations[*].Instances[*].Tags[*]&quot; --output yaml | yh | grep autoscaler
    - Key: k8s.io/cluster-autoscaler/enabled
    - Key: k8s.io/cluster-autoscaler/myeks
    - Key: k8s.io/cluster-autoscaler/enabled
    - Key: k8s.io/cluster-autoscaler/myeks
    - Key: k8s.io/cluster-autoscaler/myeks
    - Key: k8s.io/cluster-autoscaler/enabled
# 아래의 테이블은 차례로, (min,max,desired)을 의미!
$aws autoscaling describe-auto-scaling-groups \
&gt;     --query &quot;AutoScalingGroups[? Tags[? (Key==&#39;eks:cluster-name&#39;) &amp;&amp; Value==&#39;myeks&#39;]].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]&quot; \
&gt;     --output table
-----------------------------------------------------------------
|                   DescribeAutoScalingGroups                   |
+------------------------------------------------+----+----+----+
|  eks-ng1-cec41f38-acab-b45a-0479-ca4ecb1586cc  |  3 |  3 |  3 |
+------------------------------------------------+----+----+----+

$export ASG_NAME=$(aws autoscaling describe-auto-scaling-groups --query &quot;AutoScalingGroups[? Tay==&#39;eks:cluster-name&#39;) &amp;&amp; Value==&#39;myeks&#39;]].AutoScalingGroupName&quot; --output text)
# MaxSize 6개로 수정
$aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 3 --desired-capacity 3 --max-size 6
# 정보 확인
$aws autoscaling describe-auto-scaling-groups --query &quot;AutoScalingGroups[? Tags[? (Key==&#39;eks:cluster-name&#39;) &amp;&amp; Value==&#39;myeks&#39;]].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]&quot; --output table
-----------------------------------------------------------------
|                   DescribeAutoScalingGroups                   |
+------------------------------------------------+----+----+----+
|  eks-ng1-cec41f38-acab-b45a-0479-ca4ecb1586cc  |  3 |  6 |  3 |
+------------------------------------------------+----+----+----+

# 배포 : Deploy the Cluster Autoscaler (CA)
$curl -s -O https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml
$sed -i &quot;s/&lt;YOUR CLUSTER NAME&gt;/$CLUSTER_NAME/g&quot; cluster-autoscaler-autodiscover.yaml
$kubectl apply -f cluster-autoscaler-autodiscover.yaml
serviceaccount/cluster-autoscaler created
clusterrole.rbac.authorization.k8s.io/cluster-autoscaler created
role.rbac.authorization.k8s.io/cluster-autoscaler created
clusterrolebinding.rbac.authorization.k8s.io/cluster-autoscaler created
rolebinding.rbac.authorization.k8s.io/cluster-autoscaler created
deployment.apps/cluster-autoscaler created
$kubectl get pod -n kube-system | grep cluster-autoscaler
cluster-autoscaler-74785c8d45-wrtjp             0/1     ContainerCreating   0          7s
$kubectl describe deployments.apps -n kube-system cluster-autoscaler
Name:                   cluster-autoscaler
Namespace:              kube-system
CreationTimestamp:      Sun, 21 May 2023 23:16:38 +0900
Labels:                 app=cluster-autoscaler
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=cluster-autoscaler
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:           app=cluster-autoscaler
  Annotations:      prometheus.io/port: 8085
                    prometheus.io/scrape: true
  Service Account:  cluster-autoscaler
  Containers:
   cluster-autoscaler:
    Image:      registry.k8s.io/autoscaling/cluster-autoscaler:v1.26.2
    Port:       &lt;none&gt;
    Host Port:  &lt;none&gt;
    Command:
      ./cluster-autoscaler
      --v=4
      --stderrthreshold=info
      --cloud-provider=aws
      --skip-nodes-with-local-storage=false
      --expander=least-waste
      --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/myeks
    Limits:
      cpu:     100m
      memory:  600Mi
    Requests:
      cpu:        100m
      memory:     600Mi
    Environment:  &lt;none&gt;
    Mounts:
      /etc/ssl/certs/ca-certificates.crt from ssl-certs (ro)
  Volumes:
   ssl-certs:
    Type:               HostPath (bare host directory volume)
    Path:               /etc/ssl/certs/ca-bundle.crt
    HostPathType:
  Priority Class Name:  system-cluster-critical
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  &lt;none&gt;
NewReplicaSet:   cluster-autoscaler-74785c8d45 (1/1 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  18s   deployment-controller  Scaled up replica set cluster-autoscaler-74785c8d45 to 1
$kubectl -n kube-system annotate deployment.apps/cluster-autoscaler cluster-autoscaler.kubernetes.io/safe-to-evict=&quot;false&quot;^C
$kubectl -n kube-system annotate deployment.apps/cluster-autoscaler cluster-autoscaler.kubernetes.io/safe-to-evict=&quot;false&quot;^C
# (옵션) cluster-autoscaler 파드가 동작하는 워커 노드가 퇴출(evict) 되지 않게 설정
$kubectl -n kube-system annotate deployment.apps/cluster-autoscaler cluster-autoscaler.kubernetes.io/safe-to-evict=&quot;false&quot;
deployment.apps/cluster-autoscaler annotated</code></pre>
<p>테스트 진행, 파드 하나당 리소스 요청량을 크게 잡고, 스케일아웃하여 오토스케일링이 정상적으로 작동되나 확인</p>
<pre><code class="language-bash">$cat &lt;&lt;EoF&gt; nginx.yaml
&gt; apiVersion: apps/v1
&gt; kind: Deployment
&gt; metadata:
&gt;   name: nginx-to-scaleout
&gt; spec:
&gt;   replicas: 1
&gt;   selector:
&gt;     matchLabels:
&gt;       app: nginx
&gt;   template:
&gt;     metadata:
&gt;       labels:
&gt;         service: nginx
&gt;         app: nginx
&gt;     spec:
&gt;       containers:
&gt;       - image: nginx
&gt;         name: nginx-to-scaleout
&gt;         resources:
&gt;           limits:
&gt;             cpu: 500m
&gt;             memory: 512Mi
&gt;           requests:
&gt;             cpu: 500m
&gt;             memory: 512Mi
&gt; EoF
$kubectl apply -f nginx.yaml
deployment.apps/nginx-to-scaleout created
$kubectl get deployment/nginx-to-scaleout
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
nginx-to-scaleout   0/1     1            0           3s

# 파드 개수 증가
$kubectl scale --replicas=15 deployment/nginx-to-scaleout &amp;&amp; date
deployment.apps/nginx-to-scaleout scaled
Sun May 21 23:18:42 KST 2023
$kubectl get pods -l app=nginx -o wide --watch
NAME                                 READY   STATUS    RESTARTS   AGE    IP              NODE                                               NOMINATED NODE   READINESS GATES
nginx-to-scaleout-79df8996f6-2vjqm   1/1     Running   0          82s    192.168.3.11    ip-192-168-3-6.ap-northeast-2.compute.internal     &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-5pc5k   1/1     Running   0          82s    192.168.3.221   ip-192-168-3-6.ap-northeast-2.compute.internal     &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-5wvgl   1/1     Running   0          82s    192.168.2.172   ip-192-168-2-104.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-8bbc9   1/1     Running   0          82s    192.168.3.188   ip-192-168-3-10.ap-northeast-2.compute.internal    &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-9j5rx   1/1     Running   0          2m4s   192.168.3.88    ip-192-168-3-10.ap-northeast-2.compute.internal    &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-9kks9   1/1     Running   0          82s    192.168.1.166   ip-192-168-1-146.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-b5qmk   1/1     Running   0          82s    192.168.2.32    ip-192-168-2-104.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-c5hhq   1/1     Running   0          82s    192.168.1.127   ip-192-168-1-189.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-dzwsn   1/1     Running   0          82s    192.168.1.10    ip-192-168-1-146.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-nlnjq   1/1     Running   0          82s    192.168.2.253   ip-192-168-2-104.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-qmg8v   1/1     Running   0          82s    192.168.1.5     ip-192-168-1-189.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-rn7sw   1/1     Running   0          82s    192.168.1.130   ip-192-168-1-146.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-sd4rs   1/1     Running   0          82s    192.168.1.249   ip-192-168-1-189.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-wrw9d   1/1     Running   0          82s    192.168.3.91    ip-192-168-3-10.ap-northeast-2.compute.internal    &lt;none&gt;           &lt;none&gt;
nginx-to-scaleout-79df8996f6-wwz6f   1/1     Running   0          82s    192.168.3.121   ip-192-168-3-6.ap-northeast-2.compute.internal     &lt;none&gt;           &lt;none&gt;
# 노드가 추가적으로 붙는 모습, 관련된 eks-node-view, 그라파나 사진도 아래에 있습니다.
$kubectl get nodes
NAME                                               STATUS   ROLES    AGE   VERSION
ip-192-168-1-146.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   38s   v1.24.13-eks-0a21954
ip-192-168-1-189.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   65m   v1.24.13-eks-0a21954
ip-192-168-2-104.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   65m   v1.24.13-eks-0a21954
ip-192-168-3-10.ap-northeast-2.compute.internal    Ready    &lt;none&gt;   65m   v1.24.13-eks-0a21954
ip-192-168-3-6.ap-northeast-2.compute.internal     Ready    &lt;none&gt;   37s   v1.24.13-eks-0a21954
# 테이블로 확인
$aws autoscaling describe-auto-scaling-groups \
&gt;     --query &quot;AutoScalingGroups[? Tags[? (Key==&#39;eks:cluster-name&#39;) &amp;&amp; Value==&#39;myeks&#39;]].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]&quot; \
&gt;     --output table
-----------------------------------------------------------------
|                   DescribeAutoScalingGroups                   |
+------------------------------------------------+----+----+----+
|  eks-ng1-cec41f38-acab-b45a-0479-ca4ecb1586cc  |  3 |  6 |  5 |
+------------------------------------------------+----+----+----+
$kubectl delete -f nginx.yaml &amp;&amp; date
deployment.apps &quot;nginx-to-scaleout&quot; deleted
Sun May 21 23:21:27 KST 2023

# 자동으로 삭제되는 데, 시간이 10분이상 지체되어 삭제!</code></pre>
<p>파드의 개수를 증가하자, CPU 사용량 폭증</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/2d1b66bf-ac12-45ce-aecc-62d2d2a97a07/image.png" alt=""></p>
<p>kube-ops-view에서도 할당못받는 파드를 확인가능</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/a96d567c-7c7d-469a-9f0c-c717ccd2ada9/image.png" alt=""></p>
<p>노드가 오토스케일링되어, 증가되는 것을 확인가능!</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/40569b75-e527-4685-9eea-adc47d4d9d11/image.png" alt=""></p>
<p>삭제 후 사용량이 작아진 모습</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/7034a309-2321-41fd-b4a7-8969e3f432e8/image.png" alt=""></p>
<p>데몬셋도 적고, 여러 가지 노드의 기본적인 세팅이 별로 없어서 테스트 결과 빠르지만, 실제 운영환경에서는 빠르지 않음! → 자동으로 축소하는 10분정도로 시간도 느리다. (실제 환경이면 기본적으로 배포하는 것들이 많아 더 느리다고 한다.)</p>
<h3 id="cpa---cluster-proportional-autoscaler">CPA - Cluster Proportional Autoscaler</h3>
<p>coredns 파드와 같이 클러스터가 커질수록 부하가 증가하는 중요한 파드는 노드의 개수에 비례해서 파드를 오토스케일링한다. CPA는 노드 비례 파드 스케일링이다. 직접 규칙을 만들어서 정책을 수립한다. <strong>Metrics server 등을 사용하지 않고 kubapi server API를 사용</strong>합니다. 사용자 입장에서는 적절한 규칙만 세우면 된다.!</p>
<p>배포! </p>
<pre><code class="language-bash">$helm repo add cluster-proportional-autoscaler https://kubernetes-sigs.github.io/cluster-proportional-autoscaler
&quot;cluster-proportional-autoscaler&quot; already exists with the same configuration, skipping

# cluster-proportional-autoscaler 부터 배포!
$helm upgrade --install cluster-proportional-autoscaler cluster-proportional-autoscaler/cluster-proportional-autoscaler
Release &quot;cluster-proportional-autoscaler&quot; does not exist. Installing it now.

$cat &lt;&lt;EOT &gt; cpa-nginx.yaml
&gt; apiVersion: apps/v1
&gt; kind: Deployment
&gt; metadata:
&gt;   name: nginx-deployment
&gt; spec:
&gt;   replicas: 1
&gt;   selector:
&gt;     matchLabels:
&gt;       app: nginx
&gt;   template:
&gt;     metadata:
&gt;       labels:
&gt;         app: nginx
&gt;     spec:
&gt;       containers:
&gt;       - name: nginx
&gt;         image: nginx:latest
&gt;         resources:
&gt;           limits:
&gt;             cpu: &quot;100m&quot;
&gt;             memory: &quot;64Mi&quot;
&gt;           requests:
&gt;             cpu: &quot;100m&quot;
&gt;             memory: &quot;64Mi&quot;
&gt;         ports:
&gt;         - containerPort: 80
&gt; EOT
$kubectl apply -f cpa-nginx.yaml

deployment.apps/nginx-deployment created

# 아래의 규칙 확인
$cat &lt;&lt;EOF &gt; cpa-values.yaml
&gt; config:
&gt;   ladder:
&gt;     nodesToReplicas:
&gt;       - [1, 1]
&gt;       - [2, 2]
&gt;       - [3, 3]
&gt;       - [4, 3]
&gt;       - [5, 5]
&gt; options:
&gt;   namespace: default
&gt;   target: &quot;deployment/nginx-deployment&quot;
&gt; EOF

$helm upgrade --install cluster-proportional-autoscaler -f cpa-values.yaml cluster-proportional-autoscaler/cluster-proportional-autoscaler
NAME: cluster-proportional-autoscaler
LAST DEPLOYED: Sun May 21 23:57:59 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
autoscaler  awscliv2.zip    cpa-values.yaml  externaldns.yaml  irsa.yaml         krew-linux_amd64.tar.gz  kube-ps1  monitor-values.yaml  precmd.yaml
aws         cpa-nginx.yaml  create-eks.log   go                krew-linux_amd64  kubectl                  LICENSE   myeks.yaml           yh-linux-amd64.zip
addon-resizer  balancer  builder  charts  cluster-autoscaler  code-of-conduct.md  CONTRIBUTING.md  hack  LICENSE  OWNERS  README.md  SECURITY_CONTACTS  vertical-pod-autoscaler

$helm repo add cluster-proportional-autoscaler https://kubernetes-sigs.github.io/cluster-proportional-autoscaler
&quot;cluster-proportional-autoscaler&quot; already exists with the same configuration, skipping
$helm upgrade --install cluster-proportional-autoscaler cluster-proportional-autoscaler/cluster-proportional-autoscaler
Release &quot;cluster-proportional-autoscaler&quot; has been upgraded. Happy Helming!
NAME: cluster-proportional-autoscaler
LAST DEPLOYED: Sun May 21 23:59:35 2023
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None

$helm upgrade --install cluster-proportional-autoscaler -f cpa-values.yaml cluster-proportional-autoscaler/cluster-proportional-autoscaler
Release &quot;cluster-proportional-autoscaler&quot; has been upgraded. Happy Helming!
NAME: cluster-proportional-autoscaler
LAST DEPLOYED: Sun May 21 23:59:49 2023
NAMESPACE: default
STATUS: deployed
REVISION: 3
TEST SUITE: None
</code></pre>
<p>이제 노드를 증가시켜 테스트! 아래는 [노드, 파드] 테이블이다. 
<code>[1, 1], [2, 2], [3, 3], [4, 3], [5, 5]</code></p>
<pre><code class="language-bash">
$export ASG_NAME=$(aws autoscaling describe-auto-scaling-groups --query &quot;AutoScalingGroups[? Tags[? (Key==&#39;eks:cluster-name&#39;) &amp;&amp; Value==&#39;myeks&#39;]].AutoScalingGroupName&quot; --output text)
# 노드 5개로 증가
$aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 5 --desired-capacity 5 --max-size 5
$aws autoscaling describe-auto-scaling-groups --query &quot;AutoScalingGroups[? Tags[? (Key==&#39;eks:cluster-name&#39;) &amp;&amp; Value==&#39;myeks&#39;]].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]&quot; --output table
-----------------------------------------------------------------
|                   DescribeAutoScalingGroups                   |
+------------------------------------------------+----+----+----+
|  eks-ng1-cec41f38-acab-b45a-0479-ca4ecb1586cc  |  5 |  5 |  5 |
+------------------------------------------------+----+----+----+

# 노드 3개
$k get no
NAME                                               STATUS   ROLES    AGE    VERSION
ip-192-168-1-146.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   41m    v1.24.13-eks-0a21954
ip-192-168-2-104.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   105m   v1.24.13-eks-0a21954
ip-192-168-3-6.ap-northeast-2.compute.internal     Ready    &lt;none&gt;   41m    v1.24.13-eks-0a21954

# 파드 3개, [3,3] 규칙만족
$k get po
NAME                                               READY   STATUS    RESTARTS   AGE
cluster-proportional-autoscaler-75bddf49cb-lwhxs   1/1     Running   0          2m46s
nginx-deployment-858477475d-8lj5g                  1/1     Running   0          2m45s
nginx-deployment-858477475d-l8rsd                  1/1     Running   0          2m45s
nginx-deployment-858477475d-z8jbb                  1/1     Running   0          3m1s

# 노드를 5개로 스케일링, [5,5] 규칙확인
$k get no
NAME                                               STATUS     ROLES    AGE    VERSION
ip-192-168-1-146.ap-northeast-2.compute.internal   Ready      &lt;none&gt;   41m    v1.24.13-eks-0a21954
ip-192-168-2-104.ap-northeast-2.compute.internal   Ready      &lt;none&gt;   106m   v1.24.13-eks-0a21954
ip-192-168-2-217.ap-northeast-2.compute.internal   Ready      &lt;none&gt;   17s    v1.24.13-eks-0a21954
ip-192-168-3-6.ap-northeast-2.compute.internal     Ready      &lt;none&gt;   41m    v1.24.13-eks-0a21954
ip-192-168-3-76.ap-northeast-2.compute.internal    NotReady   &lt;none&gt;   6s     v1.24.13-eks-0a21954

# 파드가 5개로 만족하는 것을 볼 수 있음
$k get po
NAME                                               READY   STATUS    RESTARTS   AGE
cluster-proportional-autoscaler-75bddf49cb-lwhxs   1/1     Running   0          3m24s
nginx-deployment-858477475d-8lj5g                  1/1     Running   0          3m23s
nginx-deployment-858477475d-jv2z6                  1/1     Running   0          23s
nginx-deployment-858477475d-l8rsd                  1/1     Running   0          3m23s
nginx-deployment-858477475d-rpd2s                  1/1     Running   0          23s
nginx-deployment-858477475d-z8jbb                  1/1     Running   0          3m39s

# 노드를 4개로 스케일링
$aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 4 --desired-capacity 4 --max-size 4
# 노드 하나로 줄어서 이제 파드도 한개 줄어야함
# 죽지 않고, 스케줄불가 상태로 두면 알아서, [4,4] 규칙 만족
$k get no
NAME                                               STATUS                     ROLES    AGE    VERSION
ip-192-168-1-146.ap-northeast-2.compute.internal   Ready                      &lt;none&gt;   42m    v1.24.13-eks-0a21954
ip-192-168-2-104.ap-northeast-2.compute.internal   Ready,SchedulingDisabled   &lt;none&gt;   107m   v1.24.13-eks-0a21954
ip-192-168-2-217.ap-northeast-2.compute.internal   Ready                      &lt;none&gt;   74s    v1.24.13-eks-0a21954
ip-192-168-3-6.ap-northeast-2.compute.internal     Ready                      &lt;none&gt;   42m    v1.24.13-eks-0a21954
ip-192-168-3-76.ap-northeast-2.compute.internal    Ready                      &lt;none&gt;   63s    v1.24.13-eks-0a21954
$k get po
NAME                                               READY   STATUS    RESTARTS   AGE
cluster-proportional-autoscaler-75bddf49cb-lwhxs   1/1     Running   0          4m6s
nginx-deployment-858477475d-jv2z6                  1/1     Running   0          65s
nginx-deployment-858477475d-l8rsd                  1/1     Running   0          4m5s
nginx-deployment-858477475d-z8jbb                  1/1     Running   0          4m21s
#  - [4, 3] 테이블 규칙 만족
$aws autoscaling describe-auto-scaling-groups --query &quot;AutoScalingGroups[? Tags[? (Key==&#39;eks:cluster-name&#39;) &amp;&amp; Value==&#39;myeks&#39;]].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]&quot; --output table
-----------------------------------------------------------------
|                   DescribeAutoScalingGroups                   |
+------------------------------------------------+----+----+----+
|  eks-ng1-cec41f38-acab-b45a-0479-ca4ecb1586cc  |  4 |  4 |  4 |
+------------------------------------------------+----+----+----+
$helm uninstall cluster-proportional-autoscaler &amp;&amp; kubectl delete -f cpa-nginx.yaml
release &quot;cluster-proportional-autoscaler&quot; uninstalled
deployment.apps &quot;nginx-deployment&quot; deleted
</code></pre>
<p>Karpenter 실습 환경 준비를 위해서 현재 EKS 실습 환경 전부 삭제</p>
<pre><code class="language-bash">$helm uninstall -n kube-system kube-ops-view
release &quot;kube-ops-view&quot; uninstalled
$helm uninstall -n monitoring kube-prometheus-stack
release &quot;kube-prometheus-stack&quot; uninstalled
$eksctl delete cluster --name $CLUSTER_NAME &amp;&amp; aws cloudformation delete-stack --stack-name $CLUSTER_NAME</code></pre>
<h3 id="karpenter--k8s-native-autoscaler--fargate">Karpenter : K8S Native AutoScaler &amp; Fargate</h3>
<p>Karpenter는 오픈소스 노드 수명주기 관리 솔루션으로, 기존 오토스케일링 툴과는 다르게 초단위로 컴퓨팅 리소스를 제공한다. 아래의 그림과 같이 기존의 오토스케일링에서 거치던 단계를 생략하기에 가능하다. 자세한 내용은 스터디원분의 블로그를 참고하면 된다.</p>
<blockquote>
<p><strong>linuxer 정태환</strong>님이 <strong>EKS Nodeless 컨셉</strong>을 정리해주셨다! <a href="https://verifa.io/blog/how-to-create-nodeless-aws-eks-clusters-with-karpenter/index.html">링크</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/han-0315/post/febd6550-8ef4-44e0-8b39-91543f484d5d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/ed1ae0a9-14e6-48b4-9273-8b2f7da891b3/image.png" alt=""></p>
<p><strong>시간이 어느정도 걸려서 배포부터 진행 → 작업용 EC2만 만들어짐, 스택이름을 기존과 일부로 구분!</strong></p>
<pre><code class="language-bash"># 환경 변수 확인
$export | egrep &#39;ACCOUNT|AWS_|CLUSTER&#39; | egrep -v &#39;SECRET|KEY&#39;
declare -x ACCOUNT_ID=&quot;871103481195&quot;
declare -x AWS_ACCOUNT_ID=&quot;871103481195&quot;
declare -x AWS_DEFAULT_REGION=&quot;ap-northeast-2&quot;
declare -x AWS_PAGER=&quot;&quot;
declare -x AWS_REGION=&quot;ap-northeast-2&quot;
declare -x CLUSTER_NAME=&quot;myeks2&quot;

# 환경변수 설정
$export KARPENTER_VERSION=v0.27.5
$export TEMPOUT=$(mktemp)
$echo $KARPENTER_VERSION $CLUSTER_NAME $AWS_DEFAULT_REGION $AWS_ACCOUNT_ID $TEMPOUT
v0.27.5 myeks2 ap-northeast-2 871103481195 /tmp/tmp.X5iRenSltU

# CloudFormation 스택으로 IAM Policy, Role, EC2 Instance Profile 생성 : 3분 정도 소요
$curl -fsSL https://karpenter.sh/&quot;${KARPENTER_VERSION}&quot;/getting-started/getting-started-with-karpenter/cloudformation.yaml  &gt; $TEMPOUT \
&gt; &amp;&amp; aws cloudformation deploy \
&gt;   --stack-name &quot;Karpenter-${CLUSTER_NAME}&quot; \
&gt;   --template-file &quot;${TEMPOUT}&quot; \
&gt;   --capabilities CAPABILITY_NAMED_IAM \
&gt;   --parameter-overrides &quot;ClusterName=${CLUSTER_NAME}&quot;
Waiting for changeset to be created..
No changes to deploy. Stack Karpenter-myeks2 is up to date
</code></pre>
<p><strong>클러스터 생성</strong></p>
<ul>
<li><p>배포 파일</p>
<pre><code class="language-bash">  eksctl create cluster -f - &lt;&lt;EOF
  ---
  apiVersion: eksctl.io/v1alpha5
  kind: ClusterConfig
  metadata:
    name: ${CLUSTER_NAME}
    region: ${AWS_DEFAULT_REGION}
    version: &quot;1.24&quot;
    tags:
      karpenter.sh/discovery: ${CLUSTER_NAME}

  iam:
    withOIDC: true
    serviceAccounts:
    - metadata:
        name: karpenter
        namespace: karpenter
      roleName: ${CLUSTER_NAME}-karpenter
      attachPolicyARNs:
      - arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}
      roleOnly: true

  iamIdentityMappings:
  - arn: &quot;arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}&quot;
    username: system:node:{{EC2PrivateDNSName}}
    groups:
    - system:bootstrappers
    - system:nodes

  managedNodeGroups:
  - instanceType: m5.large
    amiFamily: AmazonLinux2
    name: ${CLUSTER_NAME}-ng
    desiredCapacity: 2
    minSize: 1
    maxSize: 10
    iam:
      withAddonPolicies:
        externalDNS: true

  ## Optionally run on fargate
  # fargateProfiles:
  # - name: karpenter
  #  selectors:
  #  - namespace: karpenter
  EOF
</code></pre>
</li>
</ul>
<p><strong>클러스터 생성 확인</strong></p>
<pre><code class="language-bash"># 클러스터 생성 완료, eks 배포 확인
$eksctl get cluster
NAME    REGION        EKSCTL CREATED
myeks2    ap-northeast-2    True

$eksctl get nodegroup --cluster $CLUSTER_NAME
CLUSTER    NODEGROUP    STATUS    CREATED            MIN SIZE    MAX SIZE    DESIRED CAPACITY    INSTANCE TYPE    IMAGE ID    ASG NAME                        TYPE
myeks2    myeks2-ng    ACTIVE    2023-05-22T10:04:38Z    1        10        2            m5.large    AL2_x86_64    eks-myeks2-ng-56c42175-7bbd-1c3a-4e92-b89992023b8c    managed

$eksctl get iamidentitymapping --cluster $CLUSTER_NAME
ARN                                                USERNAME            GROUPS                    ACCOUNT
arn:aws:iam::871103481195:role/KarpenterNodeRole-myeks2                        system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes
arn:aws:iam::871103481195:role/eksctl-myeks2-nodegroup-myeks2-ng-NodeInstanceRole-1B2P33I00KCYF    system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes

$eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE    NAME        ROLE ARN
karpenter    karpenter    arn:aws:iam::871103481195:role/myeks2-karpenter
kube-system    aws-node    arn:aws:iam::871103481195:role/eksctl-myeks2-addon-iamserviceaccount-kube-s-Role1-18H2GYOABHE8X

# $eksctl get addon --cluster $CLUSTER_NAME
# 2023-05-22 20:41:43 [ℹ]  Kubernetes version &quot;1.24&quot; in use by cluster &quot;myeks2&quot;
# 2023-05-22 20:41:43 [ℹ]  getting all addons
# No addons found

# kubernetes cluster 정보 확인
$kubectl cluster-info
Kubernetes control plane is running at https://AC31499751431C8E285778113EC3F0B3.gr7.ap-northeast-2.eks.amazonaws.com
CoreDNS is running at https://AC31499751431C8E285778113EC3F0B3.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use &#39;kubectl cluster-info dump&#39;.

$kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
NAME                                                STATUS   ROLES    AGE   VERSION                INSTANCE-TYPE   CAPACITYTYPE   ZONE
ip-192-168-30-154.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   96m   v1.24.13-eks-0a21954   m5.large        ON_DEMAND      ap-northeast-2a
ip-192-168-86-220.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   96m   v1.24.13-eks-0a21954   m5.large        ON_DEMAND      ap-northeast-2c

$kubectl get pod -n kube-system -owide
NAME                      READY   STATUS    RESTARTS   AGE    IP               NODE                                                NOMINATED NODE   READINESS GATES
aws-node-9fh2n            1/1     Running   0          96m    192.168.86.220   ip-192-168-86-220.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
aws-node-tk9mc            1/1     Running   0          96m    192.168.30.154   ip-192-168-30-154.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
coredns-dc4979556-98j9g   1/1     Running   0          104m   192.168.26.223   ip-192-168-30-154.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
coredns-dc4979556-lw7gf   1/1     Running   0          104m   192.168.6.105    ip-192-168-30-154.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-proxy-gzs29          1/1     Running   0          96m    192.168.86.220   ip-192-168-86-220.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-proxy-hlnpt          1/1     Running   0          96m    192.168.30.154   ip-192-168-30-154.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;

$kubectl describe cm -n kube-system aws-auth
Name:         aws-auth
Namespace:    kube-system
Labels:       &lt;none&gt;
Annotations:  &lt;none&gt;

Data
====
mapRoles:
----
- groups:
  - system:bootstrappers
  - system:nodes
  ## 주요
  rolearn: arn:aws:iam::871103481195:role/KarpenterNodeRole-myeks2
  username: system:node:{{EC2PrivateDNSName}}
- groups:
  - system:bootstrappers
  - system:nodes
  ## 주요
  rolearn: arn:aws:iam::871103481195:role/eksctl-myeks2-nodegroup-myeks2-ng-NodeInstanceRole-1B2P33I00KCYF
  username: system:node:{{EC2PrivateDNSName}}

mapUsers:
----
[]

BinaryData
====

Events:  &lt;none&gt;
</code></pre>
<p> 카펜터 설치!</p>
<pre><code class="language-bash"># 설정 변수 대입
$export CLUSTER_ENDPOINT=&quot;$(aws eks describe-cluster --name ${CLUSTER_NAME} --query &quot;cluster.endpoint&quot; --output text)&quot;
$export KARPENTER_IAM_ROLE_ARN=&quot;arn:aws:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-karpenter&quot;
$echo $CLUSTER_ENDPOINT $KARPENTER_IAM_ROLE_ARN
https://AC31499751431C8E285778113EC3F0B3.gr7.ap-northeast-2.eks.amazonaws.com arn:aws:iam::871103481195:role/myeks2-karpenter

# IAM 생성
$aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true
{
    &quot;Role&quot;: {
        &quot;Path&quot;: &quot;/aws-service-role/spot.amazonaws.com/&quot;,
        &quot;RoleName&quot;: &quot;AWSServiceRoleForEC2Spot&quot;,
        &quot;RoleId&quot;: &quot;AROA4VUOQIVVYLQHDALB7&quot;,
        &quot;Arn&quot;: &quot;arn:aws:iam::871103481195:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot&quot;,
        &quot;CreateDate&quot;: &quot;2023-05-22T11:42:43+00:00&quot;,
        &quot;AssumeRolePolicyDocument&quot;: {
            &quot;Version&quot;: &quot;2012-10-17&quot;,
            &quot;Statement&quot;: [
                {
                    &quot;Action&quot;: [
                        &quot;sts:AssumeRole&quot;
                    ],
                    &quot;Effect&quot;: &quot;Allow&quot;,
                    &quot;Principal&quot;: {
                        &quot;Service&quot;: [
                            &quot;spot.amazonaws.com&quot;
                        ]
                    }
                }
            ]
        }
    }
}

# docker logout : Logout of docker to perform an unauthenticated pull against the public ECR
$docker logout public.ecr.aws
Removing login credentials for public.ecr.aws

# karpenter 설치
$helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter --version ${KARPENTER_VERSION} --namespace karpenter --create-namespace \
&gt;   --set serviceAccount.annotations.&quot;eks\.amazonaws\.com/role-arn&quot;=${KARPENTER_IAM_ROLE_ARN} \
&gt;   --set settings.aws.clusterName=${CLUSTER_NAME} \
&gt;   --set settings.aws.defaultInstanceProfile=KarpenterNodeInstanceProfile-${CLUSTER_NAME} \
&gt;   --set settings.aws.interruptionQueueName=${CLUSTER_NAME} \
&gt;   --set controller.resources.requests.cpu=1 \
&gt;   --set controller.resources.requests.memory=1Gi \
&gt;   --set controller.resources.limits.cpu=1 \
&gt;   --set controller.resources.limits.memory=1Gi \
&gt;   --wait
Release &quot;karpenter&quot; does not exist. Installing it now.
Pulled: public.ecr.aws/karpenter/karpenter:v0.27.5
Digest: sha256:9491ba645592ab9485ca8ce13f53193826044522981d75975897d229b877d4c2
NAME: karpenter
LAST DEPLOYED: Mon May 22 20:42:54 2023
NAMESPACE: karpenter
STATUS: deployed
REVISION: 1
TEST SUITE: None

# karpenter 설치 확인
$kubectl get-all -n karpenter
NAME                                                 NAMESPACE  AGE
configmap/config-logging                             karpenter  17s
configmap/karpenter-global-settings                  karpenter  17s
configmap/kube-root-ca.crt                           karpenter  17s
endpoints/karpenter                                  karpenter  17s
pod/karpenter-6c6bdb7766-2kq5b                       karpenter  16s
pod/karpenter-6c6bdb7766-bj6nn                       karpenter  16s
secret/karpenter-cert                                karpenter  17s
secret/sh.helm.release.v1.karpenter.v1               karpenter  17s
serviceaccount/default                               karpenter  17s
serviceaccount/karpenter                             karpenter  17s
service/karpenter                                    karpenter  17s
deployment.apps/karpenter                            karpenter  17s
replicaset.apps/karpenter-6c6bdb7766                 karpenter  17s
lease.coordination.k8s.io/karpenter-leader-election  karpenter  8s
endpointslice.discovery.k8s.io/karpenter-mt5ph       karpenter  17s
poddisruptionbudget.policy/karpenter                 karpenter  17s
rolebinding.rbac.authorization.k8s.io/karpenter      karpenter  17s
role.rbac.authorization.k8s.io/karpenter             karpenter  17s

$kubectl get cm -n karpenter karpenter-global-settings -o jsonpath={.data} | jq
{
  &quot;aws.clusterEndpoint&quot;: &quot;&quot;,
  &quot;aws.clusterName&quot;: &quot;myeks2&quot;,
  &quot;aws.defaultInstanceProfile&quot;: &quot;KarpenterNodeInstanceProfile-myeks2&quot;,
  &quot;aws.enableENILimitedPodDensity&quot;: &quot;true&quot;,
  &quot;aws.enablePodENI&quot;: &quot;false&quot;,
  &quot;aws.interruptionQueueName&quot;: &quot;myeks2&quot;,
  &quot;aws.isolatedVPC&quot;: &quot;false&quot;,
  &quot;aws.nodeNameConvention&quot;: &quot;ip-name&quot;,
  &quot;aws.vmMemoryOverheadPercent&quot;: &quot;0.075&quot;,
  &quot;batchIdleDuration&quot;: &quot;1s&quot;,
  &quot;batchMaxDuration&quot;: &quot;10s&quot;,
  &quot;featureGates.driftEnabled&quot;: &quot;false&quot;
}

$kubectl get crd | grep karpenter
awsnodetemplates.karpenter.k8s.aws           2023-05-22T11:42:54Z
provisioners.karpenter.sh                    2023-05-22T11:42:54Z
</code></pre>
<blockquote>
</blockquote>
<p><strong>Create Provisioner : 관리 리소스는 securityGroupSelector and subnetSelector 로 찾음, ttlSecondsAfterEmpty(미사용 노드 정리, 데몬셋 제외)</strong></p>
<blockquote>
</blockquote>
<p><strong>provisioner install</strong></p>
<pre><code class="language-bash">$cat &lt;&lt;EOF | kubectl apply -f -
&gt; apiVersion: karpenter.sh/v1alpha5
&gt; kind: Provisioner
&gt; metadata:
&gt;   name: default
&gt; spec:
&gt;   requirements:
&gt;     - key: karpenter.sh/capacity-type
&gt;       operator: In
&gt;       values: [&quot;spot&quot;]
&gt;   limits:
&gt;     resources:
&gt;       cpu: 1000
&gt;   providerRef:
&gt;     name: default
&gt;   ttlSecondsAfterEmpty: 30
&gt; ---
&gt; apiVersion: karpenter.k8s.aws/v1alpha1
&gt; kind: AWSNodeTemplate
&gt; metadata:
&gt;   name: default
&gt; spec:
&gt;   subnetSelector:
&gt;     karpenter.sh/discovery: ${CLUSTER_NAME}
&gt;   securityGroupSelector:
&gt;     karpenter.sh/discovery: ${CLUSTER_NAME}
&gt; EOF
provisioner.karpenter.sh/default created
awsnodetemplate.karpenter.k8s.aws/default created
</code></pre>
<p><strong>provisioners 설치 확인</strong></p>
<pre><code class="language-bash">$kubectl get awsnodetemplates,provisioners
NAME                                        AGE
awsnodetemplate.karpenter.k8s.aws/default   9s

NAME                               AGE
provisioner.karpenter.sh/default   9s
</code></pre>
<p><strong>external dns, kube-ops-view, 그라파나, 프로메테우스 설치!</strong></p>
<pre><code class="language-bash">$helm repo add grafana-charts https://grafana.github.io/helm-charts
&quot;grafana-charts&quot; has been added to your repositories
$helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
&quot;prometheus-community&quot; has been added to your repositories
$helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the &quot;grafana-charts&quot; chart repository
...Successfully got an update from the &quot;prometheus-community&quot; chart repository
Update Complete. ⎈Happy Helming!⎈
$kubectl create namespace monitoring
namespace/monitoring created
$curl -fsSL https://karpenter.sh/&quot;${KARPENTER_VERSION}&quot;/getting-started/getting-started-with-karpenter/prometheus-values.yaml | tee prometheus-values.yaml
alertmanager:
  persistentVolume:
    enabled: false

server:
  fullnameOverride: prometheus-server
  persistentVolume:
    enabled: false

extraScrapeConfigs: |
    - job_name: karpenter
      kubernetes_sd_configs:
      - role: endpoints
        namespaces:
          names:
          - karpenter
      relabel_configs:
      - source_labels: [__meta_kubernetes_endpoint_port_name]
        regex: http-metrics
        action: keep

$helm install --namespace monitoring prometheus prometheus-community/prometheus --values prometheus-values.yaml --set alertmanager.enabled=false

NAME: prometheus
LAST DEPLOYED: Mon May 22 20:44:27 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-server.monitoring.svc.cluster.local

Get the Prometheus server URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace monitoring -l &quot;app=prometheus,component=server&quot; -o jsonpath=&quot;{.items[0].metadata.name}&quot;)
  kubectl --namespace monitoring port-forward $POD_NAME 9090
#################################################################################
######   WARNING: Persistence is disabled!!! You will lose your data when   #####
######            the Server pod is terminated.                             #####
#################################################################################

#################################################################################
######   WARNING: Pod Security Policy has been disabled by default since    #####
######            it deprecated after k8s 1.25+. use                        #####
######            (index .Values &quot;prometheus-node-exporter&quot; &quot;rbac&quot;          #####
###### .          &quot;pspEnabled&quot;) with (index .Values                         #####
######            &quot;prometheus-node-exporter&quot; &quot;rbac&quot; &quot;pspAnnotations&quot;)       #####
######            in case you still need it.                                #####
#################################################################################

The Prometheus PushGateway can be accessed via port 9091 on the following DNS name from within your cluster:
prometheus-prometheus-pushgateway.monitoring.svc.cluster.local

Get the PushGateway URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace monitoring -l &quot;app=prometheus-pushgateway,component=pushgateway&quot; -o jsonpath=&quot;{.items[0].metadata.name}&quot;)
  kubectl --namespace monitoring port-forward $POD_NAME 9091

For more information on running Prometheus, visit:
https://prometheus.io/

$curl -fsSL https://karpenter.sh/&quot;${KARPENTER_VERSION}&quot;/getting-started/getting-started-with-karpenter/grafana-values.yaml | tee grafana-values.yaml
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      version: 1
      url: http://prometheus-server:80
      access: proxy
dashboardProviders:
  dashboardproviders.yaml:
    apiVersion: 1
    providers:
    - name: &#39;default&#39;
      orgId: 1
      folder: &#39;&#39;
      type: file
      disableDeletion: false
      editable: true
      options:
        path: /var/lib/grafana/dashboards/default
dashboards:
  default:
    capacity-dashboard:
      url: https://karpenter.sh/v0.27.5/getting-started/getting-started-with-karpenter/karpenter-capacity-dashboard.json
    performance-dashboard:
      url: https://karpenter.sh/v0.27.5/getting-started/getting-started-with-karpenter/karpenter-performance-dashboard.json
$helm install --namespace monitoring grafana grafana-charts/grafana --values grafana-values.yaml --set service.type=LoadBalancer
NAME: grafana
LAST DEPLOYED: Mon May 22 20:44:35 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
NOTES:
1. Get your &#39;admin&#39; user password by running:

   kubectl get secret --namespace monitoring grafana -o jsonpath=&quot;{.data.admin-password}&quot; | base64 --decode ; echo

2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:

   grafana.monitoring.svc.cluster.local

   Get the Grafana URL to visit by running these commands in the same shell:
   NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        You can watch the status of by running &#39;kubectl get svc --namespace monitoring -w grafana&#39;
     export SERVICE_IP=$(kubectl get svc --namespace monitoring grafana -o jsonpath=&#39;{.status.loadBalancer.ingress[0].ip}&#39;)
     http://$SERVICE_IP:80

3. Login with the password from step 1 and the username: admin
#################################################################################
######   WARNING: Persistence is disabled!!! You will lose your data when   #####
######            the Grafana pod is terminated.                            #####
#################################################################################

$kubectl get secret --namespace monitoring grafana -o jsonpath=&quot;{.data.admin-password}&quot; | base64 --decode ; echo
rPBZBl47fe72XObwGKsXNYtvkGGEWA8FCFYXujwC
$MyDomain=kaneawsdns.com
$echo &quot;export MyDomain=kaneawsdns.com&quot; &gt;&gt; /etc/profile
$MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name &quot;${MyDomain}.&quot; --query &quot;HostedZones[0].Id&quot; --output text)

$echo $MyDomain, $MyDnzHostedZoneId
kaneawsdns.com, /hostedzone/Z06702063E7RRITLLMJRM

$curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
$MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst &lt; externaldns.yaml | kubectl apply -f -

serviceaccount/external-dns created
clusterrole.rbac.authorization.k8s.io/external-dns created
clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created
deployment.apps/external-dns created

$kubectl annotate service grafana -n monitoring &quot;external-dns.alpha.kubernetes.io/hostname=grafana.$MyDomain&quot;
service/grafana annotated

$echo -e &quot;grafana URL = http://grafana.$MyDomain&quot;
grafana URL = http://grafana.kaneawsdns.com

#kube-ops-view install
$helm repo add geek-cookbook https://geek-cookbook.github.io/charts/

&quot;geek-cookbook&quot; has been added to your repositories

$helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ=&quot;Asia/Seoul&quot; --namespace kube-system

NAME: kube-ops-view
LAST DEPLOYED: Mon May 22 20:47:00 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace kube-system -l &quot;app.kubernetes.io/name=kube-ops-view,app.kubernetes.io/instance=kube-ops-view&quot; -o jsonpath=&quot;{.items[0].metadata.name}&quot;)
  echo &quot;Visit http://127.0.0.1:8080 to use your application&quot;
  kubectl port-forward $POD_NAME 8080:8080

$kubectl patch svc -n kube-system kube-ops-view -p &#39;{&quot;spec&quot;:{&quot;type&quot;:&quot;LoadBalancer&quot;}}&#39;

service/kube-ops-view patched
$kubectl annotate service kube-ops-view -n kube-system &quot;external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain&quot;
service/kube-ops-view annotated
$echo -e &quot;Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5&quot;
Kube Ops View URL = http://kubeopsview.kaneawsdns.com:8080/#scale=1.5
$kubectl get awsnodetemplates,provisioners
NAME                                        AGE
awsnodetemplate.karpenter.k8s.aws/default   3m40s

NAME                               AGE
provisioner.karpenter.sh/default   3m40s
</code></pre>
<p>테스트 실시!</p>
<p>파드 한 개당 요구량을 1코어로 두고, 이후 증가시켜 오토스케일링 확인</p>
<pre><code class="language-bash"># pause 파드 1개에 CPU 1개 최소 보장 할당
$cat &lt;&lt;EOF | kubectl apply -f -
&gt; apiVersion: apps/v1
&gt; kind: Deployment
&gt; metadata:
&gt;   name: inflate
&gt; spec:
&gt;   replicas: 0
&gt;   selector:
&gt;     matchLabels:
&gt;       app: inflate
&gt;   template:
&gt;     metadata:
&gt;       labels:
&gt;         app: inflate
&gt;     spec:
&gt;       terminationGracePeriodSeconds: 0
&gt;       containers:
&gt;         - name: inflate
&gt;           image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
&gt;           resources:
&gt;             requests:
&gt;               cpu: 1
&gt; EOF
deployment.apps/inflate created
</code></pre>
<pre><code class="language-bash"># 파드 개수 증가!
$kubectl scale deployment inflate --replicas 5
deployment.apps/inflate scaled
$kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
2023-05-22T11:43:55.652Z    DEBUG    controller.deprovisioning    discovered instance types    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;count&quot;: 366}
2023-05-22T11:43:55.743Z    DEBUG    controller.deprovisioning    discovered offerings for instance types    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;zones&quot;: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2c&quot;, &quot;ap-northeast-2d&quot;], &quot;instance-type-count&quot;: 367, &quot;node-template&quot;: &quot;default&quot;}
2023-05-22T11:48:08.748Z    INFO    controller.provisioner    found provisionable pod(s)    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;pods&quot;: 5}
2023-05-22T11:48:08.748Z    INFO    controller.provisioner    computed new machine(s) to fit pod(s)    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;machines&quot;: 1, &quot;pods&quot;: 5}
2023-05-22T11:48:08.749Z    INFO    controller.provisioner    launching machine with 5 pods requesting {&quot;cpu&quot;:&quot;5125m&quot;,&quot;pods&quot;:&quot;8&quot;} from types c6i.2xlarge, c6i.24xlarge, r5ad.24xlarge, r6i.24xlarge, r5.24xlarge and 135 other(s)    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:48:09.106Z    DEBUG    controller.provisioner.cloudprovider    discovered kubernetes version    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;kubernetes-version&quot;: &quot;1.24&quot;}
2023-05-22T11:48:09.136Z    DEBUG    controller.provisioner.cloudprovider    discovered ami    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;ami&quot;: &quot;ami-0fa3b31d56b9a36b2&quot;, &quot;query&quot;: &quot;/aws/service/eks/optimized-ami/1.24/amazon-linux-2/recommended/image_id&quot;}
2023-05-22T11:48:09.163Z    DEBUG    controller.provisioner.cloudprovider    discovered ami    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;ami&quot;: &quot;ami-021b63322f1c5fc23&quot;, &quot;query&quot;: &quot;/aws/service/eks/optimized-ami/1.24/amazon-linux-2-gpu/recommended/image_id&quot;}
2023-05-22T11:48:09.173Z    DEBUG    controller.provisioner.cloudprovider    discovered ami    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;ami&quot;: &quot;ami-0a31a3ce85ee4a8e6&quot;, &quot;query&quot;: &quot;/aws/service/eks/optimized-ami/1.24/amazon-linux-2-arm64/recommended/image_id&quot;}
2023-05-22T11:48:09.311Z    DEBUG    controller.provisioner.cloudprovider    created launch template    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;launch-template-name&quot;: &quot;karpenter.k8s.aws/16624063517551845275&quot;, &quot;launch-template-id&quot;: &quot;lt-0458277a0a9530173&quot;}
2023-05-22T11:43:03.998Z    DEBUG    controller    discovered kube dns    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kube-dns-ip&quot;: &quot;10.100.0.10&quot;}
2023-05-22T11:43:03.999Z    DEBUG    controller    discovered version    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;version&quot;: &quot;v0.27.5&quot;}
2023/05/22 11:43:03 Registering 2 clients
2023/05/22 11:43:03 Registering 2 informer factories
2023/05/22 11:43:03 Registering 3 informers
2023/05/22 11:43:03 Registering 5 controllers
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;path&quot;: &quot;/metrics&quot;, &quot;kind&quot;: &quot;metrics&quot;, &quot;addr&quot;: &quot;[::]:8080&quot;}
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kind&quot;: &quot;health probe&quot;, &quot;addr&quot;: &quot;[::]:8081&quot;}
I0522 11:43:04.101075       1 leaderelection.go:248] attempting to acquire leader lease karpenter/karpenter-leader-election...
2023-05-22T11:43:04.189Z    INFO    controller    Starting informers...    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:48:11.910Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-0f03bf23055b38747&quot;, &quot;hostname&quot;: &quot;ip-192-168-188-148.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;c4.2xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;spot&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;8&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;14208Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}

$kubectl get node --label-columns=eks.amazonaws.com/capacityType,karpenter.sh/capacity-type,node.kubernetes.io/instance-type
NAME                                                 STATUS     ROLES    AGE    VERSION                CAPACITYTYPE   CAPACITY-TYPE   INSTANCE-TYPE
ip-192-168-188-148.ap-northeast-2.compute.internal   NotReady   &lt;none&gt;   26s    v1.24.13-eks-0a21954                  spot            c4.2xlarge
ip-192-168-30-154.ap-northeast-2.compute.internal    Ready      &lt;none&gt;   102m   v1.24.13-eks-0a21954   ON_DEMAND                      m5.large
ip-192-168-86-220.ap-northeast-2.compute.internal    Ready      &lt;none&gt;   102m   v1.24.13-eks-0a21954   ON_DEMAND                      m5.large

$k get po -A
NAMESPACE     NAME                                                READY   STATUS              RESTARTS   AGE
default       inflate-ccf449f59-cswnb                             0/1     ContainerCreating   0          41s
default       inflate-ccf449f59-m5cq6                             0/1     ContainerCreating   0          41s
default       inflate-ccf449f59-t5fxt                             0/1     ContainerCreating   0          41s
default       inflate-ccf449f59-tl79k                             0/1     ContainerCreating   0          41s
default       inflate-ccf449f59-wtk4l                             0/1     ContainerCreating   0          41s
karpenter     karpenter-6c6bdb7766-2kq5b                          1/1     Running             0          5m52s
karpenter     karpenter-6c6bdb7766-bj6nn                          1/1     Running             0          5m52s
kube-system   aws-node-9fh2n                                      1/1     Running             0          102m
kube-system   aws-node-dmzdv                                      0/1     Running             0          37s
kube-system   aws-node-tk9mc                                      1/1     Running             0          102m
kube-system   coredns-dc4979556-98j9g                             1/1     Running             0          111m
kube-system   coredns-dc4979556-lw7gf                             1/1     Running             0          111m
kube-system   external-dns-cc5c8cd74-v4frr                        1/1     Running             0          2m55s
kube-system   kube-ops-view-558d87b798-z4wng                      1/1     Running             0          107s
kube-system   kube-proxy-gzs29                                    1/1     Running             0          102m
kube-system   kube-proxy-hlnpt                                    1/1     Running             0          102m
kube-system   kube-proxy-xb2hs                                    1/1     Running             0          37s
monitoring    grafana-b488f8cdb-8mwn5                             1/1     Running             0          4m11s
monitoring    prometheus-kube-state-metrics-6fcf5978bf-rzbh8      1/1     Running             0          4m20s
monitoring    prometheus-prometheus-node-exporter-jpfgq           1/1     Running             0          36s
monitoring    prometheus-prometheus-node-exporter-r8vqh           1/1     Running             0          4m20s
monitoring    prometheus-prometheus-node-exporter-vwsk9           1/1     Running             0          4m19s
monitoring    prometheus-prometheus-pushgateway-fdb75d75f-jdbhr   1/1     Running             0          4m20s
monitoring    prometheus-server-6f974fdfd-l7rv7                   2/2     Running             0          4m20s</code></pre>
<p>AWS 자원 확인</p>
<ul>
<li><p>AWS 자원 확인</p>
<pre><code class="language-bash">  $aws ec2 describe-spot-instance-requests --filters &quot;Name=state,Values=active&quot; --output table

  ------------------------------------------------------------------------------------------------------
  |                                    DescribeSpotInstanceRequests                                    |
  +----------------------------------------------------------------------------------------------------+
  ||                                       SpotInstanceRequests                                       ||
  |+--------------------------------------------------+-----------------------------------------------+|
  ||  CreateTime                                      |  2023-05-22T11:48:11+00:00                    ||
  ||  InstanceId                                      |  i-0f03bf23055b38747                          ||
  ||  InstanceInterruptionBehavior                    |  terminate                                    ||
  ||  LaunchedAvailabilityZone                        |  ap-northeast-2c                              ||
  ||  ProductDescription                              |  Linux/UNIX                                   ||
  ||  SpotInstanceRequestId                           |  sir-q2r6k2vp                                 ||
  ||  SpotPrice                                       |  0.454000                                     ||
  ||  State                                           |  active                                       ||
  ||  Type                                            |  one-time                                     ||
  |+--------------------------------------------------+-----------------------------------------------+|
  |||                                       LaunchSpecification                                      |||
  ||+------------------------------------+-----------------------------------------------------------+||
  |||  ImageId                           |  ami-0fa3b31d56b9a36b2                                    |||
  |||  InstanceType                      |  c4.2xlarge                                               |||
  ||+------------------------------------+-----------------------------------------------------------+||
  ||||                                      BlockDeviceMappings                                     ||||
  |||+------------------------------------------------+---------------------------------------------+|||
  ||||  DeviceName                                    |  /dev/xvda                                  ||||
  |||+------------------------------------------------+---------------------------------------------+|||
  |||||                                             Ebs                                            |||||
  ||||+--------------------------------------------------------------------+-----------------------+||||
  |||||  DeleteOnTermination                                               |  True                 |||||
  |||||  Encrypted                                                         |  True                 |||||
  |||||  VolumeSize                                                        |  20                   |||||
  |||||  VolumeType                                                        |  gp3                  |||||
  ||||+--------------------------------------------------------------------+-----------------------+||||
  ||||                                      IamInstanceProfile                                      ||||
  |||+-------+--------------------------------------------------------------------------------------+|||
  ||||  Arn  |  arn:aws:iam::871103481195:instance-profile/KarpenterNodeInstanceProfile-myeks2      ||||
  ||||  Name |  KarpenterNodeInstanceProfile-myeks2                                                 ||||
  |||+-------+--------------------------------------------------------------------------------------+|||
  ||||                                          Monitoring                                          ||||
  |||+---------------------------------------------------+------------------------------------------+|||
  ||||  Enabled                                          |  False                                   ||||
  |||+---------------------------------------------------+------------------------------------------+|||
  ||||                                       NetworkInterfaces                                      ||||
  |||+-----------------------------------------+----------------------------------------------------+|||
  ||||  DeleteOnTermination                    |  True                                              ||||
  ||||  DeviceIndex                            |  0                                                 ||||
  ||||  SubnetId                               |  subnet-04e3bff3afc9245a6                          ||||
  |||+-----------------------------------------+----------------------------------------------------+|||
  ||||                                           Placement                                          ||||
  |||+-----------------------------------------------+----------------------------------------------+|||
  ||||  AvailabilityZone                             |  ap-northeast-2c                             ||||
  ||||  Tenancy                                      |  default                                     ||||
  |||+-----------------------------------------------+----------------------------------------------+|||
  ||||                                        SecurityGroups                                        ||||
  |||+-----------------------+----------------------------------------------------------------------+|||
  ||||        GroupId        |                              GroupName                               ||||
  |||+-----------------------+----------------------------------------------------------------------+|||
  ||||  sg-0ba1ccd4016a58521 |  eksctl-myeks2-cluster-ControlPlaneSecurityGroup-WNSB7WW4HLIF        ||||
  ||||  sg-0317cb7e7df34881a |  eksctl-myeks2-cluster-ClusterSharedNodeSecurityGroup-1VRI6GJ40ZT7A  ||||
  |||+-----------------------+----------------------------------------------------------------------+|||
  |||                                             Status                                             |||
  ||+--------------------------+---------------------------------------------------------------------+||
  |||  Code                    |  fulfilled                                                          |||
  |||  Message                 |  Your Spot request is fulfilled.                                    |||
  |||  UpdateTime              |  2023-05-22T11:48:22+00:00                                          |||
  ||+--------------------------+---------------------------------------------------------------------+||

  $kubectl get node -l karpenter.sh/capacity-type=spot -o jsonpath=&#39;{.items[0].metadata.labels}&#39; | jq
  {
    &quot;beta.kubernetes.io/arch&quot;: &quot;amd64&quot;,
    &quot;beta.kubernetes.io/os&quot;: &quot;linux&quot;,
    &quot;k8s.io/cloud-provider-aws&quot;: &quot;35a3405a9b5c02025fe6ff647a94190b&quot;,
    &quot;karpenter.k8s.aws/instance-ami-id&quot;: &quot;ami-0fa3b31d56b9a36b2&quot;,
    &quot;karpenter.k8s.aws/instance-category&quot;: &quot;c&quot;,
    &quot;karpenter.k8s.aws/instance-cpu&quot;: &quot;8&quot;,
    &quot;karpenter.k8s.aws/instance-encryption-in-transit-supported&quot;: &quot;false&quot;,
    &quot;karpenter.k8s.aws/instance-family&quot;: &quot;c4&quot;,
    &quot;karpenter.k8s.aws/instance-generation&quot;: &quot;4&quot;,
    &quot;karpenter.k8s.aws/instance-hypervisor&quot;: &quot;xen&quot;,
    &quot;karpenter.k8s.aws/instance-memory&quot;: &quot;15360&quot;,
    &quot;karpenter.k8s.aws/instance-network-bandwidth&quot;: &quot;2500&quot;,
    &quot;karpenter.k8s.aws/instance-pods&quot;: &quot;58&quot;,
    &quot;karpenter.k8s.aws/instance-size&quot;: &quot;2xlarge&quot;,
    &quot;karpenter.sh/capacity-type&quot;: &quot;spot&quot;,
    &quot;karpenter.sh/provisioner-name&quot;: &quot;default&quot;,
    &quot;kubernetes.io/arch&quot;: &quot;amd64&quot;,
    &quot;kubernetes.io/os&quot;: &quot;linux&quot;,
    &quot;node.kubernetes.io/instance-type&quot;: &quot;c4.2xlarge&quot;,
    &quot;topology.kubernetes.io/region&quot;: &quot;ap-northeast-2&quot;,
    &quot;topology.kubernetes.io/zone&quot;: &quot;ap-northeast-2c&quot;
  }
</code></pre>
</li>
</ul>
<p>테스트 종료</p>
<pre><code class="language-bash">$kubectl delete deployment inflate
deployment.apps &quot;inflate&quot; deleted

$kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
2023-05-22T11:43:03.998Z    DEBUG    controller    discovered kube dns    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kube-dns-ip&quot;: &quot;10.100.0.10&quot;}
2023-05-22T11:43:03.999Z    DEBUG    controller    discovered version    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;version&quot;: &quot;v0.27.5&quot;}
2023/05/22 11:43:03 Registering 2 clients
2023/05/22 11:43:03 Registering 2 informer factories
2023/05/22 11:43:03 Registering 3 informers
2023/05/22 11:43:03 Registering 5 controllers
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;path&quot;: &quot;/metrics&quot;, &quot;kind&quot;: &quot;metrics&quot;, &quot;addr&quot;: &quot;[::]:8080&quot;}
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kind&quot;: &quot;health probe&quot;, &quot;addr&quot;: &quot;[::]:8081&quot;}
I0522 11:43:04.101075       1 leaderelection.go:248] attempting to acquire leader lease karpenter/karpenter-leader-election...
2023-05-22T11:43:04.189Z    INFO    controller    Starting informers...    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:48:09.106Z    DEBUG    controller.provisioner.cloudprovider    discovered kubernetes version    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;kubernetes-version&quot;: &quot;1.24&quot;}
2023-05-22T11:48:09.136Z    DEBUG    controller.provisioner.cloudprovider    discovered ami    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;ami&quot;: &quot;ami-0fa3b31d56b9a36b2&quot;, &quot;query&quot;: &quot;/aws/service/eks/optimized-ami/1.24/amazon-linux-2/recommended/image_id&quot;}
2023-05-22T11:48:09.163Z    DEBUG    controller.provisioner.cloudprovider    discovered ami    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;ami&quot;: &quot;ami-021b63322f1c5fc23&quot;, &quot;query&quot;: &quot;/aws/service/eks/optimized-ami/1.24/amazon-linux-2-gpu/recommended/image_id&quot;}
2023-05-22T11:48:09.173Z    DEBUG    controller.provisioner.cloudprovider    discovered ami    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;ami&quot;: &quot;ami-0a31a3ce85ee4a8e6&quot;, &quot;query&quot;: &quot;/aws/service/eks/optimized-ami/1.24/amazon-linux-2-arm64/recommended/image_id&quot;}
2023-05-22T11:48:09.311Z    DEBUG    controller.provisioner.cloudprovider    created launch template    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;launch-template-name&quot;: &quot;karpenter.k8s.aws/16624063517551845275&quot;, &quot;launch-template-id&quot;: &quot;lt-0458277a0a9530173&quot;}
2023-05-22T11:48:11.910Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-0f03bf23055b38747&quot;, &quot;hostname&quot;: &quot;ip-192-168-188-148.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;c4.2xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;spot&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;8&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;14208Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:50:24.721Z    DEBUG    controller.node    added TTL to empty node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-188-148.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:50:56.648Z    INFO    controller.deprovisioning    deprovisioning via emptiness delete, terminating 1 machines ip-192-168-188-148.ap-northeast-2.compute.internal/c4.2xlarge/spot    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:50:56.682Z    INFO    controller.termination    cordoned node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-188-148.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:50:57.064Z    INFO    controller.termination    deleted node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-188-148.ap-northeast-2.compute.internal&quot;}

$kubectl delete provisioners default
provisioner.karpenter.sh &quot;default&quot; deleted
</code></pre>
<p>새로운 Provisiner 설치</p>
<pre><code class="language-bash">
$cat &lt;&lt;EOF | kubectl apply -f -
&gt; apiVersion: karpenter.sh/v1alpha5
&gt; kind: Provisioner
&gt; metadata:
&gt;   name: default
&gt; spec:
&gt;   consolidation:
&gt;     enabled: true
&gt;   labels:
&gt;     type: karpenter
&gt;   limits:
&gt;     resources:
&gt;       cpu: 1000
&gt;       memory: 1000Gi
&gt;   providerRef:
&gt;     name: default
&gt;   requirements:
&gt;     - key: karpenter.sh/capacity-type
&gt;       operator: In
&gt;       values:
&gt;         - on-demand
**&gt;     - key: node.kubernetes.io/instance-type
&gt;       operator: In
&gt;       values:
&gt;         - c5.large
&gt;         - m5.large
&gt;         - m5.xlarge**
&gt; EOF
provisioner.karpenter.sh/default created
</code></pre>
<p>테스트용 디플로이먼트 배포</p>
<pre><code class="language-bash">$cat &lt;&lt;EOF | kubectl apply -f -
&gt; apiVersion: apps/v1
&gt; kind: Deployment
&gt; metadata:
&gt;   name: inflate
&gt; spec:
&gt;   replicas: 0
&gt;   selector:
&gt;     matchLabels:
&gt;       app: inflate
&gt;   template:
&gt;     metadata:
&gt;       labels:
&gt;         app: inflate
&gt;     spec:
&gt;       terminationGracePeriodSeconds: 0
&gt;       containers:
&gt;         - name: inflate
&gt;           image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
&gt;           resources:
&gt;             requests:
&gt;               cpu: 1
&gt; EOF
deployment.apps/inflate created
</code></pre>
<p>오토스케일링 확인 → 파드개수 증가시키기</p>
<pre><code class="language-bash">$kubectl scale deployment inflate --replicas 12
deployment.apps/inflate scaled</code></pre>
<p>로그로 시간 확인 → 10초 이내로 반응</p>
<pre><code class="language-bash">
$kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
2023-05-22T11:43:03.998Z    DEBUG    controller    discovered kube dns    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kube-dns-ip&quot;: &quot;10.100.0.10&quot;}
2023-05-22T11:43:03.999Z    DEBUG    controller    discovered version    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;version&quot;: &quot;v0.27.5&quot;}
2023/05/22 11:43:03 Registering 2 clients
2023/05/22 11:43:03 Registering 2 informer factories
2023/05/22 11:43:03 Registering 3 informers
2023/05/22 11:43:03 Registering 5 controllers
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;path&quot;: &quot;/metrics&quot;, &quot;kind&quot;: &quot;metrics&quot;, &quot;addr&quot;: &quot;[::]:8080&quot;}
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kind&quot;: &quot;health probe&quot;, &quot;addr&quot;: &quot;[::]:8081&quot;}
I0522 11:43:04.101075       1 leaderelection.go:248] attempting to acquire leader lease karpenter/karpenter-leader-election...
2023-05-22T11:43:04.189Z    INFO    controller    Starting informers...    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:52:47.012Z    INFO    controller.provisioner    computed new machine(s) to fit pod(s)    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;machines&quot;: 4, &quot;pods&quot;: 12}
2023-05-22T11:52:47.012Z    INFO    controller.provisioner    launching machine with 3 pods requesting {&quot;cpu&quot;:&quot;3125m&quot;,&quot;pods&quot;:&quot;6&quot;} from types m5.xlarge    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:52:47.015Z    INFO    controller.provisioner    launching machine with 3 pods requesting {&quot;cpu&quot;:&quot;3125m&quot;,&quot;pods&quot;:&quot;6&quot;} from types m5.xlarge    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:52:47.021Z    INFO    controller.provisioner    launching machine with 3 pods requesting {&quot;cpu&quot;:&quot;3125m&quot;,&quot;pods&quot;:&quot;6&quot;} from types m5.xlarge    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:52:47.026Z    INFO    controller.provisioner    launching machine with 3 pods requesting {&quot;cpu&quot;:&quot;3125m&quot;,&quot;pods&quot;:&quot;6&quot;} from types m5.xlarge    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:52:47.562Z    DEBUG    controller.provisioner.cloudprovider    created launch template    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;launch-template-name&quot;: &quot;karpenter.k8s.aws/10691513453991989385&quot;, &quot;launch-template-id&quot;: &quot;lt-0bdf1b82218c3ff67&quot;}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-03691dff7f7c5089b&quot;, &quot;hostname&quot;: &quot;ip-192-168-95-67.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-0dd5ad429b8a4fb1c&quot;, &quot;hostname&quot;: &quot;ip-192-168-70-7.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-0c0613125e339e499&quot;, &quot;hostname&quot;: &quot;ip-192-168-182-17.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-08a1e375dc91770ff&quot;, &quot;hostname&quot;: &quot;ip-192-168-174-179.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}

$kubectl get node --label-columns=node.kubernetes.io/instance-type,topology.kubernetes.io/zone
NAME                                                 STATUS   ROLES    AGE    VERSION                INSTANCE-TYPE   ZONE
ip-192-168-174-179.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   45s    v1.24.13-eks-0a21954   m5.xlarge       ap-northeast-2c
ip-192-168-182-17.ap-northeast-2.compute.internal    Ready    &lt;none&gt;   45s    v1.24.13-eks-0a21954   m5.xlarge       ap-northeast-2c
ip-192-168-30-154.ap-northeast-2.compute.internal    Ready    &lt;none&gt;   107m   v1.24.13-eks-0a21954   m5.large        ap-northeast-2a
ip-192-168-70-7.ap-northeast-2.compute.internal      Ready    &lt;none&gt;   45s    v1.24.13-eks-0a21954   m5.xlarge       ap-northeast-2c
ip-192-168-86-220.ap-northeast-2.compute.internal    Ready    &lt;none&gt;   107m   v1.24.13-eks-0a21954   m5.large        ap-northeast-2c
ip-192-168-95-67.ap-northeast-2.compute.internal     Ready    &lt;none&gt;   45s    v1.24.13-eks-0a21954   m5.xlarge       ap-northeast-2c
</code></pre>
<p>스케일 다운! → 오토스케일링 확인 </p>
<pre><code class="language-bash">$kubectl scale deployment inflate --replicas 5
deployment.apps/inflate scaled</code></pre>
<p>로그 확인</p>
<pre><code class="language-bash">$kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
2023-05-22T11:43:03.998Z    DEBUG    controller    discovered kube dns    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kube-dns-ip&quot;: &quot;10.100.0.10&quot;}
2023-05-22T11:43:03.999Z    DEBUG    controller    discovered version    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;version&quot;: &quot;v0.27.5&quot;}
2023/05/22 11:43:03 Registering 2 clients
2023/05/22 11:43:03 Registering 2 informer factories
2023/05/22 11:43:03 Registering 3 informers
2023/05/22 11:43:03 Registering 5 controllers
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;path&quot;: &quot;/metrics&quot;, &quot;kind&quot;: &quot;metrics&quot;, &quot;addr&quot;: &quot;[::]:8080&quot;}
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kind&quot;: &quot;health probe&quot;, &quot;addr&quot;: &quot;[::]:8081&quot;}
I0522 11:43:04.101075       1 leaderelection.go:248] attempting to acquire leader lease karpenter/karpenter-leader-election...
2023-05-22T11:43:04.189Z    INFO    controller    Starting informers...    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:52:47.012Z    INFO    controller.provisioner    launching machine with 3 pods requesting {&quot;cpu&quot;:&quot;3125m&quot;,&quot;pods&quot;:&quot;6&quot;} from types m5.xlarge    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:52:47.015Z    INFO    controller.provisioner    launching machine with 3 pods requesting {&quot;cpu&quot;:&quot;3125m&quot;,&quot;pods&quot;:&quot;6&quot;} from types m5.xlarge    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:52:47.021Z    INFO    controller.provisioner    launching machine with 3 pods requesting {&quot;cpu&quot;:&quot;3125m&quot;,&quot;pods&quot;:&quot;6&quot;} from types m5.xlarge    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:52:47.026Z    INFO    controller.provisioner    launching machine with 3 pods requesting {&quot;cpu&quot;:&quot;3125m&quot;,&quot;pods&quot;:&quot;6&quot;} from types m5.xlarge    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;}
2023-05-22T11:52:47.562Z    DEBUG    controller.provisioner.cloudprovider    created launch template    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;launch-template-name&quot;: &quot;karpenter.k8s.aws/10691513453991989385&quot;, &quot;launch-template-id&quot;: &quot;lt-0bdf1b82218c3ff67&quot;}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-03691dff7f7c5089b&quot;, &quot;hostname&quot;: &quot;ip-192-168-95-67.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-0dd5ad429b8a4fb1c&quot;, &quot;hostname&quot;: &quot;ip-192-168-70-7.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-0c0613125e339e499&quot;, &quot;hostname&quot;: &quot;ip-192-168-182-17.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-08a1e375dc91770ff&quot;, &quot;hostname&quot;: &quot;ip-192-168-174-179.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:53:04.005Z    DEBUG    controller    deleted launch template    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;launch-template&quot;: &quot;karpenter.k8s.aws/16624063517551845275&quot;}
2023-05-22T11:54:04.652Z    INFO    controller.deprovisioning    deprovisioning via consolidation delete, terminating 2 machines ip-192-168-182-17.ap-northeast-2.compute.internal/m5.xlarge/on-demand, ip-192-168-70-7.ap-northeast-2.compute.internal/m5.xlarge/on-demand    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:54:04.709Z    INFO    controller.termination    cordoned node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-182-17.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:54:04.720Z    INFO    controller.termination    cordoned node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-70-7.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:54:05.138Z    INFO    controller.termination    deleted node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-182-17.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:54:05.140Z    INFO    controller.termination    deleted node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-70-7.ap-northeast-2.compute.internal&quot;}</code></pre>
<p>스케일 다운! → 1개로 줄임</p>
<pre><code class="language-bash">$kubectl scale deployment inflate --replicas 1
deployment.apps/inflate scaled</code></pre>
<p>로그 확인</p>
<pre><code class="language-bash">$kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-03691dff7f7c5089b&quot;, &quot;hostname&quot;: &quot;ip-192-168-95-67.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-0dd5ad429b8a4fb1c&quot;, &quot;hostname&quot;: &quot;ip-192-168-70-7.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-0c0613125e339e499&quot;, &quot;hostname&quot;: &quot;ip-192-168-182-17.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:52:49.588Z    INFO    controller.provisioner.cloudprovider    launched instance    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;provisioner&quot;: &quot;default&quot;, &quot;id&quot;: &quot;i-08a1e375dc91770ff&quot;, &quot;hostname&quot;: &quot;ip-192-168-174-179.ap-northeast-2.compute.internal&quot;, &quot;instance-type&quot;: &quot;m5.xlarge&quot;, &quot;zone&quot;: &quot;ap-northeast-2c&quot;, &quot;capacity-type&quot;: &quot;on-demand&quot;, &quot;capacity&quot;: {&quot;cpu&quot;:&quot;4&quot;,&quot;ephemeral-storage&quot;:&quot;20Gi&quot;,&quot;memory&quot;:&quot;15155Mi&quot;,&quot;pods&quot;:&quot;58&quot;}}
2023-05-22T11:53:04.005Z    DEBUG    controller    deleted launch template    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;launch-template&quot;: &quot;karpenter.k8s.aws/16624063517551845275&quot;}
2023-05-22T11:54:04.652Z    INFO    controller.deprovisioning    deprovisioning via consolidation delete, terminating 2 machines ip-192-168-182-17.ap-northeast-2.compute.internal/m5.xlarge/on-demand, ip-192-168-70-7.ap-northeast-2.compute.internal/m5.xlarge/on-demand    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:54:04.709Z    INFO    controller.termination    cordoned node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-182-17.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:54:04.720Z    INFO    controller.termination    cordoned node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-70-7.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:54:05.138Z    INFO    controller.termination    deleted node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-182-17.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:54:05.140Z    INFO    controller.termination    deleted node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-70-7.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:43:03.998Z    DEBUG    controller    discovered kube dns    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kube-dns-ip&quot;: &quot;10.100.0.10&quot;}
2023-05-22T11:43:03.999Z    DEBUG    controller    discovered version    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;version&quot;: &quot;v0.27.5&quot;}
2023/05/22 11:43:03 Registering 2 clients
2023/05/22 11:43:03 Registering 2 informer factories
2023/05/22 11:43:03 Registering 3 informers
2023/05/22 11:43:03 Registering 5 controllers
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;path&quot;: &quot;/metrics&quot;, &quot;kind&quot;: &quot;metrics&quot;, &quot;addr&quot;: &quot;[::]:8080&quot;}
2023-05-22T11:43:04.000Z    INFO    controller    Starting server    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;kind&quot;: &quot;health probe&quot;, &quot;addr&quot;: &quot;[::]:8081&quot;}
I0522 11:43:04.101075       1 leaderelection.go:248] attempting to acquire leader lease karpenter/karpenter-leader-election...
2023-05-22T11:43:04.189Z    INFO    controller    Starting informers...    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:54:31.756Z    INFO    controller.deprovisioning    deprovisioning via consolidation delete, terminating 1 machines ip-192-168-174-179.ap-northeast-2.compute.internal/m5.xlarge/on-demand    {&quot;commit&quot;: &quot;698f22f-dirty&quot;}
2023-05-22T11:54:31.811Z    INFO    controller.termination    cordoned node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-174-179.ap-northeast-2.compute.internal&quot;}
2023-05-22T11:54:32.143Z    INFO    controller.termination    deleted node    {&quot;commit&quot;: &quot;698f22f-dirty&quot;, &quot;node&quot;: &quot;ip-192-168-174-179.ap-northeast-2.compute.internal&quot;}

$kubectl get node -l type=karpenter
NAME                                               STATUS   ROLES    AGE    VERSION
ip-192-168-95-67.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   109s   v1.24.13-eks-0a21954
$kubectl get node --label-columns=eks.amazonaws.com/capacityType,karpenter.sh/capacity-type
NAME                                                STATUS   ROLES    AGE    VERSION                CAPACITYTYPE   CAPACITY-TYPE
ip-192-168-30-154.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   108m   v1.24.13-eks-0a21954   ON_DEMAND
ip-192-168-86-220.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   108m   v1.24.13-eks-0a21954   ON_DEMAND
ip-192-168-95-67.ap-northeast-2.compute.internal    Ready    &lt;none&gt;   114s   v1.24.13-eks-0a21954                  on-demand
$kubectl get node --label-columns=node.kubernetes.io/instance-type,topology.kubernetes.io/zone
NAME                                                STATUS                     ROLES    AGE     VERSION                INSTANCE-TYPE   ZONE
ip-192-168-116-39.ap-northeast-2.compute.internal   Unknown                    &lt;none&gt;   11s                            c5.large        ap-northeast-2a
ip-192-168-30-154.ap-northeast-2.compute.internal   Ready                      &lt;none&gt;   109m    v1.24.13-eks-0a21954   m5.large        ap-northeast-2a
ip-192-168-86-220.ap-northeast-2.compute.internal   Ready                      &lt;none&gt;   109m    v1.24.13-eks-0a21954   m5.large        ap-northeast-2c
ip-192-168-95-67.ap-northeast-2.compute.internal    Ready,SchedulingDisabled   &lt;none&gt;   2m12s   v1.24.13-eks-0a21954   m5.xlarge       ap-northeast-2c

$kubectl delete deployment inflate
deployment.apps &quot;inflate&quot; deleted

$kubectl delete svc -n monitoring grafana
service &quot;grafana&quot; deleted
$helm uninstall karpenter --namespace karpenter
release &quot;karpenter&quot; uninstalled

$aws ec2 describe-launch-templates --filters Name=tag:karpenter.k8s.aws/cluster,Values=${CLUSTER_NAME} |
&gt;     jq -r &quot;.LaunchTemplates[].LaunchTemplateName&quot; |
&gt;     xargs -I{} aws ec2 delete-launch-template --launch-template-name {}
{
    &quot;LaunchTemplate&quot;: {
        &quot;LaunchTemplateId&quot;: &quot;lt-0bdf1b82218c3ff67&quot;,
        &quot;LaunchTemplateName&quot;: &quot;karpenter.k8s.aws/10691513453991989385&quot;,
        &quot;CreateTime&quot;: &quot;2023-05-22T11:52:47+00:00&quot;,
        &quot;CreatedBy&quot;: &quot;arn:aws:sts::871103481195:assumed-role/myeks2-karpenter/1684755783702281349&quot;,
        &quot;DefaultVersionNumber&quot;: 1,
        &quot;LatestVersionNumber&quot;: 1
    }
}

$eksctl delete cluster --name &quot;${CLUSTER_NAME}&quot;
2023-05-22 20:56:27 [ℹ]  deleting EKS cluster &quot;myeks2&quot;
2023-05-22 20:56:28 [ℹ]  will drain 0 unmanaged nodegroup(s) in cluster &quot;myeks2&quot;
2023-05-22 20:56:28 [ℹ]  starting parallel draining, max in-flight of 1
</code></pre>
<p>아래는 배포된 자원을 삭제한 후의 스크린 샷이다. 로그와 EKS-NODE-VIEW를 통해 관련 내용을 확인하면 된다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/a70b04b3-3f5d-40e4-ace9-112a5e553232/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EKS 스터디 4주차]]></title>
            <link>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 20 May 2023 10:34:25 GMT</pubDate>
            <description><![CDATA[<!-- # 4주차 -->

<h3 id="요약">요약</h3>
<p>이번 4주차 주제는 <strong>Observability</strong>이다. 이번 실습에서는 AWS에서 기본적으로 제공해주는 기능과 프로메테우스, 그라파나 등을 직접 배포해보며 학습했다. 순서는 AWS에서 제공해주는 콘솔을 통한 로깅, CloudWatch로 시작되고 Metrics-server, kwatch 등 다양한 툴을 실제 클러스터에 배포해본다. 이후 프로메테우스와 그라파나와 같은 대표적인 모니터링 툴을 사용해보며 마무리된다. </p>
<!-- 쿠버네티스 환경에서는 컨테이너가 단지 몇분만 존재하고 사라질 수 있으며 인프라는 여러 물리적서버에 분산된 환경이다. 로그를 체계적으로 수집하고 메트릭에 대한 적절한 컨텍스트가 없다면 실패한 프로세스와 영향을 받는 애플리케이션, 관련 쿠버네티스 리소스등에 대한 대처를 할 수 없습니다. **Observability**의 중요성이 증가하고 있습니다. 

이번 주차 실습의 주제는 **Observability입니다.**  -->

<h3 id="핵심용어">핵심용어</h3>
<ul>
<li><strong>observability</strong></li>
</ul>
<p>IT 및 클라우드 컴퓨팅에서 통합 가시성이란 로그, 메트릭, 추적과 같이 시스템이 생성하는 데이터를 기반으로 시스템의 현재 상태를 측정하는 기능!</p>
<p><strong>Logging 어떤일 , Metrics 어떤지표 , Tracing 왜? 분석</strong> </p>
<ul>
<li><strong>메트릭 시스템이란?</strong></li>
</ul>
<p>먼저, 메트릭에 대해 알아보면 숫자 측정값으로 모니터링하는 데 주로 사용한다. <strong>메트릭 시스템이란 목표대상의 상태를 수집하고 관리, 모니터링하는 시스템이</strong>다. </p>
<!-- 아래 그림은 메트릭 시스템이 수집한 Argo cd 메트릭을 보여주는 예입니다. -->

<!-- ![https://blog.kakaocdn.net/dn/bQ8rWE/btr61mJj26O/lwLfYcoSCpkmRD4I8P6Ta0/img.png](https://blog.kakaocdn.net/dn/bQ8rWE/btr61mJj26O/lwLfYcoSCpkmRD4I8P6Ta0/img.png) -->

<h3 id="배포환경">배포환경</h3>
<p>프로메테우스, 그라파나 등 이번에는 리소스를 많이 쓰는 툴을 사용하므로 노드의 인스턴스 사양이 기존과 달라졌다. 이번에는 <code>t3.xlarge</code> 을 인스턴스 사양을 사용한다. 가시다님이 <strong>AWS CloudFormation <a href="https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick3.yaml">파일</a></strong>을 준비해주셨다.</p>
<p>3주차에서 진행했던 것과 같이, 기본 설정을 진행해야 한다. 그 중 LB &amp; External DNS 을 진행하지 않으면 이번 실습 중 안되는 것이 있으니 꼭 진행해야 한다. <a href="https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%A3%BC%EC%B0%A8">3주차 링크</a></p>
<p>추가적으로 SSL인증서 발급이 필요하다. 관련된 내용은 <code>Logging</code> 파트에서 확인할 수 있다.</p>
<h3 id="eks-console">EKS Console</h3>
<p>쿠버네티스 API를 통해서 리소스 및 정보를 확인 할 수 있습니다.  관련된 시스템을 AWS에서 지속적으로 관리 및 업데이트를 한다고 한다. </p>
<p><a href="https://www.eksworkshop.com/docs/observability/resource-view/">AWS Workshop</a>에서 자세하게 확인할 수 있다. </p>
<h3 id="logging">Logging</h3>
<p>AWS EKS에서 다양한 로깅과 모니터링도 제공한다. AWS에서 컨트롤 플레인을 제어하기에 접근할 수 없지만, 로그는 확인할 수 있다. <a href="https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/eks-observe.html">AWS Docs</a>에서 자세한 내용을 확인할 수 있다. </p>
<p>또, audit log가 전체 스트림의 90프로 이상을 차지 하기 때문에 활성화할 때 비용관점에서 유의하시는게 좋다고 한다. </p>
<p>aws cli를 통해 클러스터의 로깅옵션을 아래와 같이 설정시키면 로그를 AWS 콘솔에서 확인할 수 있다.</p>
<pre><code class="language-bash">$aws eks **update-cluster-config** --region $AWS_DEFAULT_REGION --name $CLUSTER_NAME \
    --logging &#39;{&quot;clusterLogging&quot;:[{&quot;types&quot;:[&quot;**api**&quot;,&quot;**audit**&quot;,&quot;**authenticator**&quot;,&quot;**controllerManager**&quot;,&quot;**scheduler**&quot;],&quot;enabled&quot;:**true**}]}&#39;</code></pre>
<p>API서버가 메트릭을 노출하는 엔드포인트 <a href="https://aws.amazon.com/blogs/containers/managing-etcd-database-size-on-amazon-eks-clusters/">AWS Blog</a> → 생략</p>
<pre><code class="language-bash"># 아래의 명령어를 통해 반환하는 엔드포인트는 API서버가 메트릭을 노출하는 엔드포인트
$kubectl get --raw /metrics | grep &quot;etcd_db_total_size_in_bytes&quot;</code></pre>
<p>아래는 관련된 로그를 직접 확인해보는 명령어이다.</p>
<pre><code class="language-bash"># 로그 스트림
$aws logs tail /aws/eks/$CLUSTER_NAME/cluster --log-stream-name-prefix kube-controller-manager --follow
$kubectl scale deployment -n kube-system coredns --replicas=1
deployment.apps/coredns scaled
$kubectl scale deployment -n kube-system coredns --replicas=2
deployment.apps/coredns scaled
# 로그 스트림 확인
$aws logs tail /aws/eks/$CLUSTER_NAME/cluster --log-stream-name-prefix kube-controller-manager --follow
2023-05-17T10:37:01.000000+00:00 kube-controller-manager-03d7b752d418a3019486688cc6ced1a5 I0517 10:37:01.356908      10 replica_set.go:613] &quot;Too many replicas&quot; replicaSet=&quot;kube-system/coredns-6777fcd775&quot; need=1 deleting=1
2023-05-17T10:37:01.000000+00:00 kube-controller-manager-03d7b752d418a3019486688cc6ced1a5 I0517 10:37:01.356955      10 replica_set.go:241] &quot;Found related ReplicaSets&quot; replicaSet=&quot;kube-system/coredns-6777fcd775&quot; relatedReplicaSets=[kube-system/coredns-dc4979556 kube-system/coredns-6777fcd775]
2023-05-17T10:37:01.000000+00:00 kube-controller-manager-03d7b752d418a3019486688cc6ced1a5 I0517 10:37:01.357037      10 controller_utils.go:592] &quot;Deleting pod&quot; controller=&quot;coredns-6777fcd775&quot; pod=&quot;kube-system/coredns-6777fcd775-k9ksb&quot;
2023-05-17T10:37:01.000000+00:00 kube-controller-manager-03d7b752d418a3019486688cc6ced1a5 I0517 10:37:01.357138      10 event.go:294] &quot;Event occurred&quot; object=&quot;kube-system/coredns&quot; fieldPath=&quot;&quot; kind=&quot;Deployment&quot; apiVersion=&quot;apps/v1&quot; type=&quot;Normal&quot; reason=&quot;ScalingReplicaSet&quot; message=&quot;Scaled down replica set coredns-6777fcd775 to 1&quot;
2023-05-17T10:37:01.000000+00:00 kube-controller-manager-03d7b752d418a3019486688cc6ced1a5 I0517 10:37:01.407444      10 event.go:294] &quot;Event occurred&quot; object=&quot;kube-system/coredns-6777fcd775&quot; fieldPath=&quot;&quot; kind=&quot;ReplicaSet&quot; apiVersion=&quot;apps/v1&quot; type=&quot;Normal&quot; reason=&quot;SuccessfulDelete&quot; message=&quot;Deleted pod: coredns-6777fcd775-k9ksb&quot;
$eksctl utils update-cluster-logging --cluster $CLUSTER_NAME --region $AWS_DEFAULT_REGI --disable-types all --approve
2023-05-17 19:37:40 [ℹ]  will update CloudWatch logging for cluster &quot;myeks&quot; in &quot;ap-northeast-2&quot; (no types to enable &amp; disable types: api, audit, authenticator, controllerManager, scheduler)
2023-05-17 19:38:12 [✔]  configured CloudWatch logging for cluster &quot;myeks&quot; in &quot;ap-northeast-2&quot; (no types enabled &amp; disabled types: api, audit, authenticator, controllerManager, scheduler)
</code></pre>
<p><strong>Control Plane metrics</strong> with Prometheus &amp; CW <strong>Logs Insights 쿼리</strong> - <a href="https://docs.aws.amazon.com/eks/latest/userguide/prometheus.html">Docs</a></p>
<p>아래의 명령어를 통해 다양한 metrics를 확인할 수 있다. 로그를 필터링 하고 싶으면, Logs insights을 사용한다.</p>
<pre><code class="language-bash">
$kubectl get --raw /metrics | more
# HELP aggregator_openapi_v2_regeneration_count [ALPHA] Counter of OpenAPI v2 spec regeneration count broken down by causing APIServi
ce name and reason.
# TYPE aggregator_openapi_v2_regeneration_count counter
aggregator_openapi_v2_regeneration_count{apiservice=&quot;*&quot;,reason=&quot;startup&quot;} 0
aggregator_openapi_v2_regeneration_count{apiservice=&quot;k8s_internal_local_delegation_chain_0000000002&quot;,reason=&quot;update&quot;} 0
# HELP aggregator_openapi_v2_regeneration_duration [ALPHA] Gauge of OpenAPI v2 spec regeneration duration in seconds.
# TYPE aggregator_openapi_v2_regeneration_duration gauge
aggregator_openapi_v2_regeneration_duration{reason=&quot;startup&quot;} 0.064469015
aggregator_openapi_v2_regeneration_duration{reason=&quot;update&quot;} 0.022995886
# HELP aggregator_unavailable_apiservice [ALPHA] Gauge of APIServices which are marked as unavailable broken down by APIService name.
# TYPE aggregator_unavailable_apiservice gauge
aggregator_unavailable_apiservice{name=&quot;v1.&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.admissionregistration.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.apiextensions.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.apps&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.authentication.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.authorization.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.autoscaling&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.batch&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.certificates.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.coordination.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.discovery.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.events.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.networking.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.node.k8s.io&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.policy&quot;} 0
aggregator_unavailable_apiservice{name=&quot;v1.rbac.authorization.k8s.io&quot;} 0

$kubectl get --raw /metrics | grep &quot;etcd_db_total_size_in_bytes&quot;
# HELP etcd_db_total_size_in_bytes [ALPHA] Total size of the etcd database file physically allocated in bytes.
# TYPE etcd_db_total_size_in_bytes gauge
etcd_db_total_size_in_bytes{endpoint=&quot;http://10.0.160.16:2379&quot;} 4.337664e+06
etcd_db_total_size_in_bytes{endpoint=&quot;http://10.0.32.16:2379&quot;} 4.374528e+06
etcd_db_total_size_in_bytes{endpoint=&quot;http://10.0.96.16:2379&quot;} 4.370432e+06

$kubectl get --raw=/metrics | grep apiserver_storage_objects |awk &#39;$2&gt;100&#39; |sort -g -k 2
# HELP apiserver_storage_objects [STABLE] Number of stored objects at the time of last check split by kind.
# TYPE apiserver_storage_objects gauge
apiserver_storage_objects{resource=&quot;events&quot;} 246


$kubectl get --raw=/metrics | grep apiserver_storage_objects |awk &#39;$2&gt;100&#39; |sort -g -k 2
# HELP apiserver_storage_objects [STABLE] Number of stored objects at the time of last check split by kind.
# TYPE apiserver_storage_objects gauge
apiserver_storage_objects{resource=&quot;events&quot;} 246

$kubectl get --raw=/metrics | grep apiserver_storage_objects |awk &#39;$2&gt;50&#39; |sort -g -k 2
# HELP apiserver_storage_objects [STABLE] Number of stored objects at the time of last check split by kind.
# TYPE apiserver_storage_objects gauge
apiserver_storage_objects{resource=&quot;clusterrolebindings.rbac.authorization.k8s.io&quot;} 72
apiserver_storage_objects{resource=&quot;clusterroles.rbac.authorization.k8s.io&quot;} 86
apiserver_storage_objects{resource=&quot;events&quot;} 246
</code></pre>
<p>아래의 실습을 진행하기 위한 선수작업으로 SSL인증서 발급이 필요하다.(SSL 인증서 발급이 필요하다)</p>
<p><code>CERT_ARN=$(aws acm list-certificates --query &#39;CertificateSummaryList[].CertificateArn[]&#39; --output text)
echo $CERT_ARN</code></p>
<p><a href="https://ap-northeast-2.console.aws.amazon.com/acm/home?region=ap-northeast-2#/welcome">AWS Certificate Manager</a>에서 인증서를 발급하면 된다. 나는 DNS 인증방식을 택했고, 인증서를 클릭하여 세부정보를 본 뒤, Route53에서 레코드 생성을 누르면 된다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/7386bdb9-2dfa-4947-8644-ec9e83923276/image.png" alt=""></p>
<p>하지만, 시간이 오래걸려 나는 이메일인증방식으로 바꿨다. 그러면 aws와 연결된 이메일로 인증메일이 온다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/1c52ecd0-42f6-4de1-9bf0-12739049fed3/image.png" alt=""></p>
<p>수락하면, 바로 인증이 완료된다. </p>
<p>스터디원 중에 감사하게, 관련 명령어를 정리해주신 분이 있다. 터미널을 통해 아래와 같이 진행할 수 있다.</p>
<details>
<summary>ACM 인증서 명령어</summary>

<pre><code class="language-bash">    # CloudWatch Log Insight Query
    aws logs get-query-results --query-id $(aws logs start-query \
    --log-group-name &#39;/aws/eks/myeks/cluster&#39; \
    --start-time `date -d &quot;-1 hours&quot; +%s` \
    --end-time `date +%s` \
    --query-string &#39;fields @timestamp, @message | filter @logStream ~= &quot;kube-scheduler&quot; | sort @timestamp desc&#39; \
    | jq --raw-output &#39;.queryId&#39;)

    # ACM 퍼블릭 인증서 요청
    CERT_ARN=$(aws acm request-certificate \
    --domain-name $MyDomain \
    --validation-method &#39;DNS&#39; \
    --key-algorithm &#39;RSA_2048&#39; \
    |jq --raw-output &#39;.CertificateArn&#39;)

    # 생성한 인증서 CNAME 이름 가져오기
    CnameName=$(aws acm describe-certificate \
    --certificate-arn $CERT_ARN \
    --query &#39;Certificate.DomainValidationOptions[*].ResourceRecord.Name&#39; \
    --output text)

    # 생성한 인증서 CNAME 값 가져오기
    CnameValue=$(aws acm describe-certificate \
    --certificate-arn $CERT_ARN \
    --query &#39;Certificate.DomainValidationOptions[*].ResourceRecord.Value&#39; \
    --output text)

    # 정상 출력 확인하기
    echo $CERT_ARN, $CnameName, $CnameValue

    # 레코드 파일
    cat &lt;&lt;EOT &gt; cname.json
    {
      &quot;Comment&quot;: &quot;create a acm&#39;s CNAME record&quot;,
      &quot;Changes&quot;: [
        {
          &quot;Action&quot;: &quot;CREATE&quot;,
          &quot;ResourceRecordSet&quot;: {
            &quot;Name&quot;: &quot;CnameName&quot;,
            &quot;Type&quot;: &quot;CNAME&quot;,
            &quot;TTL&quot;: 300,
            &quot;ResourceRecords&quot;: [
              {
                &quot;Value&quot;: &quot;CnameValue&quot;
              }
            ]
          }
        }
      ]
    }
    EOT

    # CNAME 이름, 값 치환하기
    sed -i &quot;s/CnameName/$CnameName/g&quot; cname.json
    sed -i &quot;s/CnameValue/$CnameValue/g&quot; cname.json
    cat cname.json

    # Route53 레코드 생성
    aws route53 change-resource-record-sets --hosted-zone-id $MyDnzHostedZoneId --change-batch file://cname.json</code></pre>
</details>


<br>
이제 nginx를 배포해보자. 아래는 helm을 통해 nginx를 배포하는 코드이다.

<pre><code class="language-bash">
# helm을 통해 배포
$helm repo add bitnami https://charts.bitnami.com/bitnami
&quot;bitnami&quot; has been added to your repositories
# 위에서 발급받은 인증서 사용
$CERT_ARN=$(aws acm list-certificates --query &#39;CertificateSummaryList[].CertificateArn[]&#39; --output text)
$echo $CERT_ARN
arn:aws:acm:ap-northeast-2:871103481195:certificate/...
$MyDomain=kaneawsdns.com
$echo $MyDomain
kaneawsdns.com
$cat &lt;&lt;EOT &gt; nginx-values.yaml
&gt; service:
&gt;     type: NodePort
&gt;
&gt; ingress:
&gt;   enabled: true
&gt;   ingressClassName: alb
&gt;   hostname: nginx.$MyDomain
&gt;   path: /*
&gt;   annotations:
&gt;     alb.ingress.kubernetes.io/scheme: internet-facing
&gt;     alb.ingress.kubernetes.io/target-type: ip
&gt;     alb.ingress.kubernetes.io/listen-ports: &#39;[{&quot;HTTPS&quot;:443}, {&quot;HTTP&quot;:80}]&#39;
&gt;     alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
&gt;     alb.ingress.kubernetes.io/success-codes: 200-399
&gt;     alb.ingress.kubernetes.io/load-balancer-name: $CLUSTER_NAME-ingress-alb
&gt;     alb.ingress.kubernetes.io/group.name: study
&gt;     alb.ingress.kubernetes.io/ssl-redirect: &#39;443&#39;
&gt; EOT

# 배포!
$helm install nginx bitnami/nginx --version 14.1.0 -f nginx-values.yaml
NAME: nginx
LAST DEPLOYED: Wed May 17 19:42:42 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: nginx
CHART VERSION: 14.1.0
APP VERSION: 1.24.0
** Please be patient while the chart is being deployed **
NGINX can be accessed through the following DNS name from within your cluster:

    nginx.default.svc.cluster.local (port 80)

To access NGINX from outside the cluster, follow the steps below:

1. Get the NGINX URL and associate its hostname to your cluster external IP:

   export CLUSTER_IP=$(minikube ip) # On Minikube. Use: `kubectl cluster-info` on others K8s clusters
   echo &quot;NGINX URL: http://nginx.kaneawsdns.com&quot;
   echo &quot;$CLUSTER_IP  nginx.kaneawsdns.com&quot; | sudo tee -a /etc/hosts
</code></pre>
<p>이제 쿠버네티스의 리소스를 조회해보면 nginx를 확인할 수 있다.</p>
<pre><code class="language-bash">$kubectl get ingress,deploy,svc,ep nginx
NAME                              CLASS   HOSTS                  ADDRESS   PORTS   AGE
ingress.networking.k8s.io/nginx   alb     nginx.kaneawsdns.com             80      15s

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx   0/1     1            0           15s

NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
service/nginx   NodePort   10.100.62.41   &lt;none&gt;        80:30847/TCP   15s

NAME              ENDPOINTS   AGE
endpoints/nginx               15s
# 로그를 남기기 위해, 웹사이트 접속시도
$while true; do curl -s https://nginx.$MyDomain -I | head -n 1; date; sleep 1; done
Wed May 17 19:46:26 KST 2023
Wed May 17 19:46:27 KST 2023
Wed May 17 19:46:28 KST 2023
Wed May 17 19:46:29 KST 2023
Wed May 17 19:46:30 KST 2023</code></pre>
<p>이제 아래에서 로그를 확인해보면, 설치와 관련된 로그와 위에서 실행한 접속 관련된 로그를 확인할 수 있다.</p>
<pre><code class="language-bash"># 관련 파드 로그
$kubectl logs deploy/nginx -f
nginx 12:13:25.20
nginx 12:13:25.21 Welcome to the Bitnami nginx container
nginx 12:13:25.21 Subscribe to project updates by watching https://github.com/bitnami/containers
nginx 12:13:25.21 Submit issues and feature requests at https://github.com/bitnami/containers/issues
nginx 12:13:25.21
nginx 12:13:25.21 INFO  ==&gt; ** Starting NGINX setup **
nginx 12:13:25.22 INFO  ==&gt; Validating settings in NGINX_* env vars
Generating RSA private key, 4096 bit long modulus (2 primes)
..........++++
......................++++
e is 65537 (0x010001)
Signature ok
subject=CN = example.com
Getting Private key
nginx 12:13:25.38 INFO  ==&gt; No custom scripts in /docker-entrypoint-initdb.d
nginx 12:13:25.38 INFO  ==&gt; Initializing NGINX
realpath: /bitnami/nginx/conf/vhosts: No such file or directory
nginx 12:13:25.40 INFO  ==&gt; ** NGINX setup finished! **
nginx 12:13:25.41 INFO  ==&gt; ** Starting NGINX **
...
192.168.1.72 - - [18/May/2023:12:15:37 +0000] &quot;GET / HTTP/1.1&quot; 200  409 &quot;-&quot; &quot;ELB-HealthChecker/2.0&quot; &quot;-&quot;
192.168.3.219 - - [18/May/2023:12:15:37 +0000] &quot;GET / HTTP/1.1&quot; 200  409 &quot;-&quot; &quot;ELB-HealthChecker/2.0&quot; &quot;-&quot;
192.168.2.230 - - [18/May/2023:12:15:43 +0000] &quot;GET / HTTP/1.1&quot; 200  409 &quot;-&quot; &quot;ELB-HealthChecker/2.0&quot; &quot;-&quot;
# 위에서 접속 시도한 로그
192.168.2.230 - - [18/May/2023:12:15:49 +0000] &quot;GET / HTTP/1.1&quot; 200  409 &quot;-&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Whale/3.20.182.14 Safari/537.36&quot; &quot;218.235.82.74&quot;
192.168.2.230 - - [18/May/2023:12:15:49 +0000] &quot;GET /favicon.ico HTTP/1.1&quot; 404  180 &quot;https://nginx.kaneawsdns.com/&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Whale/3.20.182.14 Safari/537.36&quot; &quot;218.235.82.74&quot;
</code></pre>
<p>실제 도메인으로 접속한 모습</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/5efd6325-1745-47e6-973e-7d1c52864f2c/image.png" alt=""></p>
<pre><code class="language-bash"># 로그는 아래와 같이 /dev/stdout에 저장된다. 
$kubectl exec -it deploy/nginx -- ls -l /opt/bitnami/nginx/logs/
total 0
lrwxrwxrwx 1 root root 11 Apr 24 10:13 access.log -&gt; /dev/stdout
lrwxrwxrwx 1 root root 11 Apr 24 10:13 error.log -&gt; /dev/stderr</code></pre>
<blockquote>
<p><strong>컨테이너 로그 환경의 로그는 표준 출력 stdout과 표준 에러 stderr로 보내는 것을 권고 - <a href="https://docs.docker.com/config/containers/logging/">링크</a></strong></p>
</blockquote>
<p>위와 같이 진행하면 파드의 로그를 파드에 들어가지 않고 확인할 수 있다고 한다. 아래는 관련 도커 파일이다. </p>
<pre><code class="language-bash">RUN ln -sf **/dev/stdout** **/opt/bitnami/nginx/logs/access.log**
RUN ln -sf **/dev/stderr** **/opt/bitnami/nginx/logs/error.log**</code></pre>
<pre><code class="language-bash"># forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
 &amp;&amp; ln -sf /dev/stderr /var/log/nginx/error.log</code></pre>
<p>단점은 종료된 파드는 조회 불가하고, 로그파일의 크기에 한계가 있다.(로그 파일의 크기는 바꿔줄 수 있다.) 하지만, 파드의 로그는 별도의 로깅시스템을 이용해서 로깅을 한다.</p>
<h3 id="container-insights-metrics-in-amazon-cloudwatch--fluent-bit-logs">Container Insights metrics in Amazon CloudWatch &amp; Fluent Bit (Logs)</h3>
<p>Fluent Bit 는 CloudWatch에서 부족한 로깅데이터를 채워주며 더 유연하고 사용자 정의가 가능하다고 한다.</p>
<p>아래는 아키텍처이다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/7b24fac6-5580-455d-b709-c5a024113029/image.png" alt=""></p>
<pre><code class="language-bash">for node in $N1 $N2 $N3; do echo &quot;&gt;&gt;&gt;&gt;&gt; $node &lt;&lt;&lt;&lt;&lt;&quot;; ssh ec2-user@$node sudo tree /var/log/containers; echo; done
for node in $N1 $N2 $N3; do echo &quot;&gt;&gt;&gt;&gt;&gt; $node &lt;&lt;&lt;&lt;&lt;&quot;; ssh ec2-user@$node sudo ls -al /var/log/containers; echo; done
#해당 로그를 찾아가서, cat 하면 관련 정보가 다 나온다.</code></pre>
<blockquote>
<p><strong>CloudWatch Container Insight</strong>는 컨테이너형 애플리케이션 및 마이크로 서비스에 대한 <strong>모니터링</strong>, <strong>트러블 슈팅</strong> 및 <strong>알람</strong>을 위한 <strong>완전 관리형 관측 서비스</strong>입니다.</p>
</blockquote>
<p>아래는 CloudWatch Container Insight 설치하는 실습코드입니다.</p>
<pre><code class="language-bash">FluentBitHttpServer=&#39;On&#39;
FluentBitHttpPort=&#39;2020&#39;
FluentBitReadFromHead=&#39;Off&#39;
FluentBitReadFromTail=&#39;On&#39;

$**curl -s https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/quickstart/cwagent-fluent-bit-quickstart.yaml | sed &#39;s/{{cluster_name}}/&#39;${CLUSTER_NAME}&#39;/;s/{{region_name}}/&#39;${AWS_DEFAULT_REGION}&#39;/;s/{{http_server_toggle}}/&quot;&#39;${FluentBitHttpServer}&#39;&quot;/;s/{{http_server_port}}/&quot;&#39;${FluentBitHttpPort}&#39;&quot;/;s/{{read_from_head}}/&quot;&#39;${FluentBitReadFromHead}&#39;&quot;/;s/{{read_from_tail}}/&quot;&#39;${FluentBitReadFromTail}&#39;&quot;/&#39; | kubectl apply -f -**

#각 노드에 정상적으로 설치됐는 지 확인
$for node in $N1 $N2 $N3; do echo &quot;&gt;&gt;&gt;&gt;&gt; $node &lt;&lt;&lt;&lt;&lt;&quot;; ssh ec2-user@$node sudo ss -tnlp | grep fluent-bit; echo; done
&gt;&gt;&gt;&gt;&gt; 192.168.1.37 &lt;&lt;&lt;&lt;&lt;
LISTEN 0      128          0.0.0.0:2020       0.0.0.0:*    users:((&quot;fluent-bit&quot;,pid=1227,fd=187))

&gt;&gt;&gt;&gt;&gt; 192.168.2.127 &lt;&lt;&lt;&lt;&lt;
LISTEN 0      128          0.0.0.0:2020       0.0.0.0:*    users:((&quot;fluent-bit&quot;,pid=2016,fd=193))

&gt;&gt;&gt;&gt;&gt; 192.168.3.97 &lt;&lt;&lt;&lt;&lt;
LISTEN 0      128          0.0.0.0:2020       0.0.0.0:*    users:((&quot;fluent-bit&quot;,pid=1834,fd=193))</code></pre>
<p>cluster role 확인</p>
<pre><code class="language-bash"># cloud watch 는 cm, events, nodes에 대한 create 권한도 가진다.
# fluent-bit 는 ns, nodes, pods 에 대한 조회권한만 있다.(get, list, watch)
$kubectl describe clusterrole cloudwatch-agent-role fluent-bit-role                          # 클러스터롤 확인
Name:         cloudwatch-agent-role
Labels:       &lt;none&gt;
Annotations:  &lt;none&gt;
PolicyRule:
  Resources         Non-Resource URLs  Resource Names           Verbs
  ---------         -----------------  --------------           -----
  configmaps        []                 []                       [create]
  events            []                 []                       [create]
  nodes/stats       []                 []                       [create]
  configmaps        []                 [cwagent-clusterleader]  [get update]
  nodes/proxy       []                 []                       [get]
  endpoints         []                 []                       [list watch]
  nodes             []                 []                       [list watch]
  pods              []                 []                       [list watch]
  replicasets.apps  []                 []                       [list watch]
  jobs.batch        []                 []                       [list watch]

Name:         fluent-bit-role
Labels:       &lt;none&gt;
Annotations:  &lt;none&gt;
PolicyRule:
  Resources    Non-Resource URLs  Resource Names  Verbs
  ---------    -----------------  --------------  -----
  namespaces   []                 []              [get list watch]
  nodes/proxy  []                 []              [get list watch]
  nodes        []                 []              [get list watch]
  pods/logs    []                 []              [get list watch]
  pods         []                 []              [get list watch]
               [/metrics]         []              [get]
# cluster role binging은 각각 
ServiceAccount  cloudwatch-agent  amazon-cloudwatch</code></pre>
<p>이제 아래에서 관련된 로그를 확인한다. </p>
<p>(cloudwatch, fluent-bit파드의 로그 확인, 각 노드에 fluent-bit 확인)</p>
<pre><code class="language-bash"># 파드 로그 확인
$kubectl -n amazon-cloudwatch logs -l name=cloudwatch-agent -f 
2023-05-17T10:49:50Z I! [processors.ec2tagger] ec2tagger: Initial retrieval of tags succeeded
2023-05-17T10:49:50Z I! [processors.ec2tagger] ec2tagger: EC2 tagger has started, finished initial retrieval of tags and Volumes
2023-05-17T10:49:50Z I! [processors.ec2tagger] ec2tagger: Initial retrieval of tags succeeded
2023-05-17T10:49:56Z W! [outputs.cloudwatchlogs] Retried 0 time, going to sleep 126.586058ms before retrying.
...
2023-05-17T10:54:51Z I! [processors.ec2tagger] ec2tagger: Refresh is no longer needed, stop refreshTicker.
2023-05-17T10:55:50Z I! number of namespace to running pod num map[amazon-cloudwatch:6 default:1 kube-system:13]

# 파드 로그 확인
$kubectl -n amazon-cloudwatch logs -l k8s-app=fluent-bit -f    
[2023/05/17 10:49:54] [ info] [output:cloudwatch_logs:cloudwatch_logs.2] Creating log stream ip-192-168-2-127.ap-northeast-2.compute.internal.host.messages in log group /aws/containerinsights/myeks/host
[2023/05/17 10:49:54] [ info] [output:cloudwatch_logs:cloudwatch_logs.0] Creating log stream ip-192-168-2-127.ap-northeast-2.compute.internal-application.var.log.containers.fluent-bit-tv2jp_amazon-cloudwatch_fluent-bit-323122d37cdff0e32606fc4c2b4a1e418056daf3880181a2984b2ef802912453.log in log group /aws/containerinsights/myeks/application
[2023/05/17 10:49:54] [ info] [output:cloudwatch_logs:cloudwatch_logs.1] Creating log stream ip-192-168-2-127.ap-northeast-2.compute.internal-dataplane.systemd.kubelet.service in log group /aws/containerinsights/myeks/dataplane
[2023/05/17 10:49:54] [ info] [output:cloudwatch_logs:cloudwatch_logs.2] Created log stream ip-192-168-2-127.ap-northeast-2.compute.internal.host.messages
[2023/05/17 10:49:54] [ info] [output:cloudwatch_logs:cloudwatch_logs.0] Created log stream ip-192-168-2-127.ap-northeast-2.compute.internal-application.var.log.containers.fluent-bit-tv2jp_amazon-cloudwatch_fluent-bit-323122d37cdff0e32606fc4c2b4a1e418056daf3880181a2984b2ef802912453.log
[2023/05/17 10:49:54] [ info] [output:cloudwatch_logs:cloudwatch_logs.1] Created log stream ip-192-168-2-127.ap-northeast-2.compute.internal-dataplane.systemd.kubelet.service
[2023/05/17 10:49:54] [ info] [output:cloudwatch_logs:cloudwatch_logs.0] Creating log stream ip-192-168-2-127.ap-northeast-2.compute.internal-application.var.log.containers.cloudwatch-agent-dqxhl_amazon-cloudwatch_cloudwatch-agent-d1a7d2fe1ff214af72cb368ed9cc4e0ffb5826b7ca6bdac926da943a8cd10c29.log in log group /aws/containerinsights/myeks/application
[2023/05/17 10:49:54] [ info] [output:cloudwatch_logs:cloudwatch_logs.0] Created log stream ip-192-168-2-127.ap-northeast-2.compute.internal-application.var.log.containers.cloudwatch-agent-dqxhl_amazon-cloudwatch_cloudwatch-agent-d1a7d2fe1ff214af72cb368ed9cc4e0ffb5826b7ca6bdac926da943a8cd10c29.log
[2023/05/17 10:50:24] [ info] [output:cloudwatch_logs:cloudwatch_logs.2] Creating log stream ip-192-168-2-127.ap-northeast-2.compute.internal.host.secure in log group /aws/containerinsights/myeks/host
[2023/05/17 10:50:24] [ info] [output:cloudwatch_logs:cloudwatch_logs.2] Created log stream ip-192-168-2-127.ap-northeast-2.compute.internal.host.secure
... 
나머지 2개의 노드도 동일</code></pre>
<p>아래는 각 노드의 fluent-bit </p>
<pre><code class="language-bash"># sudo ss tnlp -&gt; TCP 연결을 확인하는 Linux 명령어
$for node in $N1 $N2 $N3; do echo &quot;&gt;&gt;&gt;&gt;&gt; $node &lt;&lt;&lt;&lt;&lt;&quot;; ssh ec2-user@$node sudo ss -tnlp | grep fluent-bit; echo; done
&gt;&gt;&gt;&gt;&gt; 192.168.1.37 &lt;&lt;&lt;&lt;&lt;
LISTEN 0      128          0.0.0.0:2020       0.0.0.0:*    users:((&quot;fluent-bit&quot;,pid=1227,fd=187))

&gt;&gt;&gt;&gt;&gt; 192.168.2.127 &lt;&lt;&lt;&lt;&lt;
LISTEN 0      128          0.0.0.0:2020       0.0.0.0:*    users:((&quot;fluent-bit&quot;,pid=2016,fd=193))

&gt;&gt;&gt;&gt;&gt; 192.168.3.97 &lt;&lt;&lt;&lt;&lt;
LISTEN 0      128          0.0.0.0:2020       0.0.0.0:*    users:((&quot;fluent-bit&quot;,pid=1834,fd=193))</code></pre>
<p>cwagentconfig configmap 확인 Cloudwatch에 대한 설정값은 cwagentconfig에서 확인할 수 있다.</p>
<p>관련 옵션에 대한 문서는 <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-metrics.html">AWS Docs</a> 에서 참고할 수 있다.</p>
<pre><code class="language-bash">$kubectl describe cm cwagentconfig -n amazon-cloudwatch
Name:         cwagentconfig
Namespace:    amazon-cloudwatch
...
{
  &quot;agent&quot;: {
    &quot;region&quot;: &quot;ap-northeast-2&quot;
  },
  &quot;logs&quot;: {
    &quot;metrics_collected&quot;: {
      &quot;kubernetes&quot;: {
        &quot;cluster_name&quot;: &quot;myeks&quot;,
        &quot;metrics_collection_interval&quot;: 60 # 60초 마다 metrics 수집
      }
    },
    &quot;force_flush_interval&quot;: 5 # 로그이벤트에 대한 일괄처리 간격 5초
  }
}
</code></pre>
<p>로그를 저장하는 방식확인 HostPath를 이용해서 로그를 저장하며 관련된 내용은 아래의 Volume에서 확인할 수 있다.</p>
<pre><code class="language-bash">$kubectl describe -n amazon-cloudwatch ds cloudwatch-agent
Name:           cloudwatch-agent
Selector:       name=cloudwatch-agent
Node-Selector:  kubernetes.io/os=linux
Labels:         &lt;none&gt;
Annotations:    deprecated.daemonset.template.generation: 1
Desired Number of Nodes Scheduled: 3
Current Number of Nodes Scheduled: 3
Number of Nodes Scheduled with Up-to-date Pods: 3
Number of Nodes Scheduled with Available Pods: 3
Number of Nodes Misscheduled: 0
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:           name=cloudwatch-agent
  Service Account:  cloudwatch-agent
  Containers:
   cloudwatch-agent:
    Image:      public.ecr.aws/cloudwatch-agent/cloudwatch-agent:1.247359.0b252558
    Port:       &lt;none&gt;
    Host Port:  &lt;none&gt;
    Limits:
      cpu:     200m
      memory:  200Mi
    Requests:
      cpu:     200m
      memory:  200Mi
    Environment:
      HOST_IP:         (v1:status.hostIP)
      HOST_NAME:       (v1:spec.nodeName)
      K8S_NAMESPACE:   (v1:metadata.namespace)
      CI_VERSION:     k8s/1.3.14
    Mounts:
      /dev/disk from devdisk (ro)
      /etc/cwagentconfig from cwagentconfig (rw)
      /rootfs from rootfs (ro)
      /run/containerd/containerd.sock from containerdsock (ro)
      /sys from sys (ro)
      /var/lib/docker from varlibdocker (ro)
      /var/run/docker.sock from dockersock (ro)
  Volumes:
   cwagentconfig:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      cwagentconfig
    Optional:  false
   rootfs:
    Type:          HostPath (bare host directory volume)
    Path:          /
    HostPathType:
   dockersock:
    Type:          HostPath (bare host directory volume)
    Path:          /var/run/docker.sock
    HostPathType:
   varlibdocker:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/docker
    HostPathType:
   containerdsock:
    Type:          HostPath (bare host directory volume)
    Path:          /run/containerd/containerd.sock
    HostPathType:
   sys:
    Type:          HostPath (bare host directory volume)
    Path:          /sys
    HostPathType:
   devdisk:
    Type:          HostPath (bare host directory volume)
    Path:          /dev/disk/
    HostPathType:
Events:
  Type    Reason            Age    From                  Message
  ----    ------            ----   ----                  -------
  Normal  SuccessfulCreate  7m10s  daemonset-controller  Created pod: cloudwatch-agent-dqxhl
  Normal  SuccessfulCreate  7m10s  daemonset-controller  Created pod: cloudwatch-agent-4s2zn
  Normal  SuccessfulCreate  7m10s  daemonset-controller  Created pod: cloudwatch-agent-trg48
# 직접 노드에 접속하여 로그 확인
$ssh ec2-user@$N1 sudo tree /var/log
/var/log
├── amazon
│   └── ssm
│       ├── amazon-ssm-agent.log
│       └── audits
│           └── amazon-ssm-agent-audit-2023-05-17
├── audit
│   └── audit.log
├── aws-routed-eni
│   ├── egress-v4-plugin.log
│   ├── ipamd.log
│   └── plugin.log
├── boot.log
├── btmp
├── chrony
│   ├── measurements.log
│   ├── statistics.log
│   └── tracking.log
├── cloud-init.log
├── cloud-init-output.log
├── containers
│   ├── aws-node-v4rrj_kube-system_aws-node-3f4dccffced176a063625ee6fc7a6f4660a9044b234f091fb4c6223133673ed1.log -&gt; /var/log/pods/kube-system_aws-node-v4rrj_3255b05a-827d-40d8-a6e3-f5af4a8d2d61/aws-node/0.log
│   ├── aws-node-v4rrj_kube-system_aws-vpc-cni-init-7dbade8cc5816e0b5234e1d04aa534d11020abd8364ae0463ea32f5c5c7d4e21.log -&gt; /var/log/pods/kube-system_aws-node-v4rrj_3255b05a-827d-40d8-a6e3-f5af4a8d2d61/aws-vpc-cni-init/0.log
│   ├── cloudwatch-agent-4s2zn_amazon-cloudwatch_cloudwatch-agent-3f5c992074ee5b07f208c260a0729ea166155d34215187ede71d5008693fcfe1.log -&gt; /var/log/pods/amazon-cloudwatch_cloudwatch-agent-4s2zn_0bcce925-7abd-40c9-b32b-66a3d04ee992/cloudwatch-agent/0.log
│   ├── coredns-6777fcd775-xmbs7_kube-system_coredns-4825cfc8c2fb811cb4b2f69653de2a7e4973eeaca4bbb5ae2d4d1468a88bf271.log -&gt; /var/log/pods/kube-system_coredns-6777fcd775-xmbs7_c7bb0ccd-67ee-446d-811f-48b33bcd0f83/coredns/0.log
│   ├── ebs-csi-node-sz9lr_kube-system_ebs-plugin-4aa278cfd8a59348f1975638d50f6f58229fc924cca599c1c5cffd363f803d8f.log -&gt; /var/log/pods/kube-system_ebs-csi-node-sz9lr_415aea18-7e6c-46f5-96f1-35aa791305ec/ebs-plugin/0.log
│   ├── ebs-csi-node-sz9lr_kube-system_liveness-probe-90d51a14e4df2738fe524f74971bc041786290454ad2c5885ff18c53f9a721e9.log -&gt; /var/log/pods/kube-system_ebs-csi-node-sz9lr_415aea18-7e6c-46f5-96f1-35aa791305ec/liveness-probe/0.log
│   ├── ebs-csi-node-sz9lr_kube-system_node-driver-registrar-8ac1e0be8b994741459e73420186d93b72107d71f3155f57d63772fa1b97db24.log -&gt; /var/log/pods/kube-system_ebs-csi-node-sz9lr_415aea18-7e6c-46f5-96f1-35aa791305ec/node-driver-registrar/0.log
│   ├── fluent-bit-bdgtk_amazon-cloudwatch_fluent-bit-6296393c94936a2abbda7ee84890ff12c66f3cd2951252c0d9c15d9e2d3232a6.log -&gt; /var/log/pods/amazon-cloudwatch_fluent-bit-bdgtk_5fc0e8f8-c220-415f-b199-6643c70c22c4/fluent-bit/0.log
│   └── kube-proxy-fswxw_kube-system_kube-proxy-5b8cc3eb0106c283fd78a0cea3710e148a793a31bf32f3a3176724f414dc6761.log -&gt; /var/log/pods/kube-system_kube-proxy-fswxw_bf3b6818-020e-47c8-9c31-40c260c3d0c2/kube-proxy/0.log
├── cron
├── dmesg
├── dmesg.old
├── grubby
├── grubby_prune_debug
├── journal
│   ├── ec2179c4f3e906eda92ce733733bd5d0
│   │   └── system.journal
│   └── ec2466c41de306c0e40b1ac67c61386f
│       ├── system.journal
│       └── user-1000.journal
├── lastlog
├── maillog
├── messages
├── pods
│   ├── amazon-cloudwatch_cloudwatch-agent-4s2zn_0bcce925-7abd-40c9-b32b-66a3d04ee992
│   │   └── cloudwatch-agent
│   │       └── 0.log
│   ├── amazon-cloudwatch_fluent-bit-bdgtk_5fc0e8f8-c220-415f-b199-6643c70c22c4
│   │   └── fluent-bit
│   │       └── 0.log
│   ├── kube-system_aws-node-v4rrj_3255b05a-827d-40d8-a6e3-f5af4a8d2d61
│   │   ├── aws-node
│   │   │   └── 0.log
│   │   └── aws-vpc-cni-init
│   │       └── 0.log
│   ├── kube-system_coredns-6777fcd775-xmbs7_c7bb0ccd-67ee-446d-811f-48b33bcd0f83
│   │   └── coredns
│   │       └── 0.log
│   ├── kube-system_ebs-csi-node-sz9lr_415aea18-7e6c-46f5-96f1-35aa791305ec
│   │   ├── ebs-plugin
│   │   │   └── 0.log
│   │   ├── liveness-probe
│   │   │   └── 0.log
│   │   └── node-driver-registrar
│   │       └── 0.log
│   └── kube-system_kube-proxy-fswxw_bf3b6818-020e-47c8-9c31-40c260c3d0c2
│       └── kube-proxy
│           └── 0.log
├── sa
│   └── sa17
├── secure
├── spooler
├── tallylog
├── wtmp
└── yum.log</code></pre>
<p>아래는 fluent-bit-config map 확인한 모습이다. 관련된 내용은 콘솔의 로그그룹에도 똑같이 존재한다.</p>
<pre><code class="language-bash">$kubectl describe cm fluent-bit-config -n amazon-cloudwatch
Name:         fluent-bit-config
Namespace:    amazon-cloudwatch
Labels:       k8s-app=fluent-bit
Annotations:  &lt;none&gt;

Data
====
fluent-bit.conf:
----
[SERVICE]
    Flush                     5
    Grace                     30
    Log_Level                 info
    Daemon                    off
    Parsers_File              parsers.conf
    HTTP_Server               ${HTTP_SERVER}
    HTTP_Listen               0.0.0.0
    HTTP_Port                 ${HTTP_PORT}
    storage.path              /var/fluent-bit/state/flb-storage/
    storage.sync              normal
    storage.checksum          off
    storage.backlog.mem_limit 5M
# 아래의 콘솔과 일치
@INCLUDE application-log.conf
@INCLUDE dataplane-log.conf
@INCLUDE host-log.conf
</code></pre>
<p>AWS 콘솔 로그그룹에서 확인가능
<img src="https://velog.velcdn.com/images/han-0315/post/f6c2e1b4-af9e-4ee0-850c-de8eeb71756c/image.png" alt=""></p>
<p>배포한 Nginx에 부하를 주어, 로그확인</p>
<pre><code class="language-bash">$yum install -y httpd
#아래의 명령어는 ApacheBench (ab) 웹사이트 부하테스트 명령어이다.
## -c = 동시유저, -n = 총 트래픽 -&gt; 500명의 동시유저가 트래픽을 날려 총 3만개의 트래픽을 날림
$ab -c 500 -n 30000 https://nginx.$MyDomain/
This is ApacheBench, Version 2.3 &lt;$Revision: 1903618 $&gt;
...
$kubectl logs deploy/nginx -f
nginx 10:42:49.73
nginx 10:42:49.73 Welcome to the Bitnami nginx container
nginx 10:42:49.74 Subscribe to project updates by watching https://github.com/bitnami/containers
nginx 10:42:49.74 Submit issues and feature requests at https://github.com/bitnami/containers/issues
nginx 10:42:49.74
nginx 10:42:49.74 INFO  ==&gt; ** Starting NGINX setup **
...
subject=CN = example.com
Getting Private key
nginx 10:42:50.84 INFO  ==&gt; No custom scripts in /docker-entrypoint-initdb.d
nginx 10:42:50.84 INFO  ==&gt; Initializing NGINX
realpath: /bitnami/nginx/conf/vhosts: No such file or directory
nginx 10:42:50.86 INFO  ==&gt; ** NGINX setup finished! **

nginx 10:42:50.87 INFO  ==&gt; ** Starting NGINX **</code></pre>
<p>nginx 로그 확인</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/8ad700e7-f131-41ee-9f80-d65085555df8/image.png" alt=""></p>
<p>메트릭 확인 : ccontainer map, UI를 통해 전체구조를 한눈에 그려준다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/bb8d5e14-b412-45a6-9037-7bb8d716e3f3/image.png" alt=""></p>
<p>리소스 사용량으로 필터링한 모습이다. 아직까진 리소스를 많이 잡아먹는 애플리케이션이 없다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/531d15f8-1163-413d-8905-f7785d31b785/image.png" alt=""></p>
<h3 id="metrics-server--kwatch--botkube">Metrics-server &amp; kwatch &amp; botkube</h3>
<p>여기에서는 모니터링, 알람과 관련된 툴 3가지에 대해 직접 배포하고 테스트한다.</p>
<p><a href="https://github.com/kubernetes-sigs/metrics-server"><strong>Metrics-server</strong></a></p>
<p>kubelet으로부터 수집한 리소스 메트릭을 수집 및 집계하는 클러스터 애드온이며 아래의 2가지 기능이 가능하다.</p>
<ul>
<li>노드 및 파드의 리소스 사용량을 모니터링</li>
<li>애플리케이션의 성능 저하를 식별</li>
</ul>
<p>이 정보를 받아 다른 툴을 연결하여 오토스케일링과 같이 파드와 노드의 리소스를 제어할 수 있다.</p>
<pre><code class="language-bash"># 배포
$kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
# 배포 확인
$kubectl get apiservices |egrep &#39;(AVAILABLE|metrics)&#39;
NAME                                   SERVICE                      AVAILABLE   AGE
v1beta1.metrics.k8s.io                 kube-system/metrics-server   True        31s

$kubectl api-resources | grep metrics
nodes                                          metrics.k8s.io/v1beta1                 false        NodeMetrics
pods                                           metrics.k8s.io/v1beta1                 true         PodMetrics

# 리소스 사용량 확인 1000m = 1 core
$kubectl top node
NAME                                               CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
ip-192-168-1-37.ap-northeast-2.compute.internal    52m          1%     560Mi           3%
ip-192-168-2-127.ap-northeast-2.compute.internal   51m          1%     632Mi           4%
ip-192-168-3-97.ap-northeast-2.compute.internal    61m          1%     602Mi           4%
$kubectl top pod -A
NAMESPACE           NAME                                  CPU(cores)   MEMORY(bytes)
amazon-cloudwatch   cloudwatch-agent-4s2zn                4m           28Mi
amazon-cloudwatch   cloudwatch-agent-dqxhl                4m           32Mi
amazon-cloudwatch   cloudwatch-agent-trg48                4m           29Mi
amazon-cloudwatch   fluent-bit-bdgtk                      2m           24Mi
amazon-cloudwatch   fluent-bit-tv2jp                      1m           24Mi
amazon-cloudwatch   fluent-bit-vvm4j                      2m           25Mi
default             nginx-685c67bc9-qprxk                 1m           4Mi
kube-system         aws-node-9427n                        3m           38Mi
kube-system         aws-node-q8g52                        3m           37Mi
kube-system         aws-node-v4rrj                        3m           37Mi
kube-system         coredns-6777fcd775-56hnq              2m           13Mi
kube-system         coredns-6777fcd775-xmbs7              2m           13Mi
kube-system         ebs-csi-controller-67658f895c-r6zvw   2m           49Mi
kube-system         ebs-csi-controller-67658f895c-rm4bg   5m           56Mi
kube-system         ebs-csi-node-dc7lw                    2m           21Mi
kube-system         ebs-csi-node-sphnv                    2m           20Mi
kube-system         ebs-csi-node-sz9lr                    2m           21Mi
kube-system         kube-proxy-9tk8k                      3m           10Mi
kube-system         kube-proxy-fswxw                      1m           10Mi
kube-system         kube-proxy-z9669                      1m           9Mi
kube-system         metrics-server-6bf466fbf5-s6ccb       4m           17Mi

$kubectl top pod -n kube-system --sort-by=&#39;cpu&#39;
NAME                                  CPU(cores)   MEMORY(bytes)
ebs-csi-controller-67658f895c-rm4bg   5m           56Mi
metrics-server-6bf466fbf5-s6ccb       4m           17Mi
aws-node-q8g52                        3m           37Mi
aws-node-v4rrj                        3m           37Mi
aws-node-9427n                        3m           38Mi
kube-proxy-9tk8k                      3m           10Mi
coredns-6777fcd775-56hnq              2m           13Mi
ebs-csi-node-dc7lw                    2m           21Mi
ebs-csi-node-sphnv                    2m           20Mi
ebs-csi-node-sz9lr                    2m           21Mi
ebs-csi-controller-67658f895c-r6zvw   2m           49Mi
coredns-6777fcd775-xmbs7              2m           13Mi</code></pre>
<p><strong>kwatch</strong></p>
<p>kwatch는 Kubernetes(K8s) 클러스터의 모든 변경 사항을 모니터링하고, 실행 중인 앱의 충돌을 실시간으로 감지하며, 채널(Slack, Discord 등)에 알림을 즉시 게시할 수 있도록 도와줍니다.</p>
<p>아래는 생성과정이다.</p>
<p>별도의 워크스페이스를 생성한 후, 채널 → 설정 → APP 추가 → webhook 검색 → incoming-webhook 추가 → 생성된 URL을 config yaml 파일에 넣으면 된다.</p>
<pre><code class="language-bash">#config map 생성 
cat kwatch-config.yaml | yh
apiVersion: v1
kind: Namespace
metadata:
  name: kwatch
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: kwatch
  namespace: kwatch
data:
  config.yaml: |
    alert:
      slack:
        webhook: https://hooks.slack.com/services/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
        title: -EKS
        #text:
    pvcMonitor:
      enabled: true
      interval: 5
      threshold: 70
# config map 배포 
$k apply -f kwatch-config.yaml
namespace/kwatch created
configmap/kwatch created

#배포
$kubectl apply -f https://raw.githubusercontent.com/abahmed/kwatch/v0.8.3/deploy/deploy.yaml</code></pre>
<p>정상적으로 배포되었다면, slack에 메시지가 도착한다. </p>
<p>이제 오류 알람이 정상적으로 오는 지 확인한다. </p>
<pre><code class="language-bash"># 잘못된 이미지 정보의 파드 배포
$kubectl apply -f https://raw.githubusercontent.com/junghoon2/kube-books/main/ch05/nginx-error-pod.yml
pod/nginx-19 created
# 이벤트 로그
$k get events -w
LAST SEEN   TYPE      REASON              OBJECT                       MESSAGE
1s          Normal    Scheduled           pod/nginx-19                 Successfully assigned default/nginx-19 to ip-192-168-2-127.ap-northeast-2.compute.internal
1s          Normal    Pulling             pod/nginx-19                 Pulling image &quot;nginx:1.19.19&quot;
29m         Normal    Scheduled           pod/nginx-685c67bc9-qprxk    Successfully assigned default/nginx-685c67bc9-qprxk to ip-192-168-3-97.ap-northeast-2.compute.internal
29m         Normal    Pulled              pod/nginx-685c67bc9-qprxk    Container image &quot;docker.io/bitnami/nginx:1.24.0-debian-11-r0&quot; already present on machine
29m         Normal    Created             pod/nginx-685c67bc9-qprxk    Created container nginx
29m         Normal    Started             pod/nginx-685c67bc9-qprxk    Started container nginx
30m         Normal    Killing             pod/nginx-685c67bc9-vmwh9    Stopping container nginx
30m         Warning   Unhealthy           pod/nginx-685c67bc9-vmwh9    Readiness probe failed: dial tcp 192.168.3.251:8080: connect: connection refused
29m         Normal    SuccessfulCreate    replicaset/nginx-685c67bc9   Created pod: nginx-685c67bc9-qprxk
29m         Normal    ScalingReplicaSet   deployment/nginx             Scaled up replica set nginx-685c67bc9 to 1
0s          Warning   Failed              pod/nginx-19                 Failed to pull image &quot;nginx:1.19.19&quot;: rpc error: code = NotFound desc = failed to pull and unpack image &quot;docker.io/library/nginx:1.19.19&quot;: failed to resolve reference &quot;docker.io/library/nginx:1.19.19&quot;: docker.io/library/nginx:1.19.19: not found
0s          Warning   Failed              pod/nginx-19                 Error: ErrImagePull
0s          Normal    BackOff             pod/nginx-19                 Back-off pulling image &quot;nginx:1.19.19&quot;
0s          Warning   Failed              pod/nginx-19                 Error: ImagePullBackOff

#모니터링 결과 에러가 발생! 이제 알람이 와야함
Every 2.0s: kubectl get pod                                                                             Wed May 17 21:32:48 2023

NAME                    READY   STATUS         RESTARTS   AGE
nginx-19                0/1     ErrImagePull   0          6s
nginx-685c67bc9-qprxk   1/1     Running        0          30m</code></pre>
<p>정상적으로 알람이 온 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/aef8c90c-6863-4dd3-a9fe-aa2efacb7300/image.png" alt=""></p>
<br>

<p><strong>Botkube</strong>위와 유사하게, 클러스터를 모니터링하며 채널(Slack, Discord 등)에 알림을 즉시 게시할 수 있도록 도와준다.</p>
<p>아래의 문서를 따라가서 API 토큰을 받은 뒤 Helm 을 통해 배포하면 된다.</p>
<p>슬랙 앱 설정 : SLACK_API_<strong>BOT_TOKEN</strong> 과 SLACK_API_<strong>APP_TOKEN</strong> 생성 - <a href="https://docs.botkube.io/installation/slack/">Docs</a></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/f545024c-bd85-4840-aaa4-60da0010882e/image.png" alt=""></p>
<pre><code class="language-bash">export SLACK_API_BOT_TOKEN=&#39;xoxb-3546114861781-5244848054375-z6NLuaxuXQCoF2EUtdTrIcCI&#39;
export SLACK_API_APP_TOKEN=&#39;xapp-1-A057PRTU2SG-5261880598308-6b1160cf0c7eabb676bc5a941380455dd8960b6723e0a5487987842c5ae7f701&#39;</code></pre>
<p>아래는 Helm 배포</p>
<pre><code class="language-bash"># repo 추가
helm repo add botkube https://charts.botkube.io
helm repo update
# 변수 지정
export ALLOW_KUBECTL=true
export ALLOW_HELM=true
export SLACK_CHANNEL_NAME=webhook3

#
cat &lt;&lt;EOT &gt; botkube-values.yaml
actions:
  &#39;describe-created-resource&#39;: # kubectl describe
    enabled: true
  &#39;show-logs-on-error&#39;: # kubectl logs
    enabled: true

executors:
  k8s-default-tools:
    botkube/helm:
      enabled: true
    botkube/kubectl:
      enabled: true
EOT

# 설치
helm install --version **v1.0.0** botkube --namespace botkube --create-namespace \
--set communications.default-group.socketSlack.enabled=true \
--set communications.default-group.socketSlack.channels.default.name=${SLACK_CHANNEL_NAME} \
--set communications.default-group.socketSlack.appToken=${SLACK_API_APP_TOKEN} \
--set communications.default-group.socketSlack.botToken=${SLACK_API_BOT_TOKEN} \
--set settings.clusterName=${CLUSTER_NAME} \
--set &#39;executors.k8s-default-tools.botkube/kubectl.enabled&#39;=${ALLOW_KUBECTL} \
--set &#39;executors.k8s-default-tools.botkube/helm.enabled&#39;=${ALLOW_HELM} \
-f **botkube-values.yaml** botkube/botkube

# 삭제
**$helm uninstall botkube --namespace botkube**</code></pre>
<p><a href="https://docs.botkube.io/usage/">Docs</a>에서 사용과 관련된 내용을 자세히 확인할 수 있다. </p>
<p>helm으로 삭제!   <code>helm uninstall botkube --namespace botkube</code></p>
<h3 id="프로메테우스-스택"><strong>프로메테우스-스택</strong></h3>
<p>프로메테우스는 CNCF 프로젝트 중 하나로 오픈소스 모니터링 시스템이다. 다른 모니터링 시스템과 구별되는 특징은 아래와 같다. </p>
<ol>
<li>다차원 데이터 모델</li>
<li>PromQL 쿼리</li>
<li>단일 서버 노드</li>
<li>HTTP Pull model</li>
<li>Pushing time series (push gate way )</li>
<li>타겟 확인(서비스 검색 or 정적)</li>
<li>다양한 모드의 대시보드 및 그래프 지원</li>
<li><strong>Federation 을 사용해서, 현재 프로메테우스 서버로 다른 프로메테우스 서버에서 저장된 시계열을 스크랩할 수 있다.</strong></li>
</ol>
<p><strong>Federation</strong> 을 통해 다중 클러스터 환경에서 여러 클라이언트 프로메테우스 서버에 저장된 데이터를 가져오고 실시간 경고처리가 가능하지만, 이 방식은 대용량 전용 볼륨 필요, 장애시 데이터 복구 힘듬, 데이터증가로 인한 중앙 프로메테우스 서버의 부하가 발생할 수 있다. 그렇기에 Thanos를 사용하여 이런 문제를 처리할 수 있다.</p>
<p>Thanos는 Prometheus 고가용성 및 Long-Term 스토리지 기능을 제공한다. 또한 Thanos를 사용하면 여러 Prometheus 대상의 데이터를 집계하고 단일 쿼리 엔드 포인트에서 쿼리 할 수 있다. Prometheus 에서 발생할 수 있는 메트릭 중복을 자동으로 처리 할 수 있다.</p>
<p>아래는 Thanos에 대한 아키텍처이다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/bbf2dd76-8e5e-407c-9e7f-1bba9e6ca5c5/image.png" alt=""></p>
<p>아래는 프로메테우스 스택과 관련된 아키텍처이며, 프로메테우스에 대한 자세한 정보는 <a href="https://github.com/prometheus/prometheus">GitHub</a>에서 확인할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/68a74c86-3edd-4a80-9068-0969ee1999e6/image.png" alt=""></p>
<p>이제 관련 실습을 진행했다. </p>
<p>아래는 helm으로 배포하는 명령어이다. </p>
<pre><code class="language-bash">
$CERT_ARN=aws acm list-certificates --query &#39;CertificateSummaryList[].CertificateArn[]&#39; --output text
$echo $CERT_ARN
arn:aws:acm:ap-northeast-2:871103481195:certificate/..
$helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
&quot;prometheus-community&quot; has been added to your repositories
cat &lt;&lt;EOT &gt; monitor-values.yaml
**prometheus**:
  prometheusSpec:
    podMonitorSelectorNilUsesHelmValues: false
    serviceMonitorSelectorNilUsesHelmValues: false
    retention: 5d
    retentionSize: &quot;10GiB&quot;

  ingress:
    enabled: true
    ingressClassName: alb
    hosts: 
      - prometheus.$MyDomain
    paths: 
      - /*
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/listen-ports: &#39;[{&quot;HTTPS&quot;:443}, {&quot;HTTP&quot;:80}]&#39;
      alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
      alb.ingress.kubernetes.io/success-codes: 200-399
      **alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
      alb.ingress.kubernetes.io/group.name: study
      alb.ingress.kubernetes.io/ssl-redirect: &#39;443&#39;**

**grafana**:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: prom-operator

  ingress:
    enabled: true
    ingressClassName: alb
    hosts: 
      - grafana.$MyDomain
    paths: 
      - /*
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/listen-ports: &#39;[{&quot;HTTPS&quot;:443}, {&quot;HTTP&quot;:80}]&#39;
      alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
      alb.ingress.kubernetes.io/success-codes: 200-399
      **alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
      alb.ingress.kubernetes.io/group.name: study
      alb.ingress.kubernetes.io/ssl-redirect: &#39;443&#39;

defaultRules:
  create: false
kubeControllerManager:
  enabled: false
kubeEtcd:
  enabled: false
kubeScheduler:
  enabled: false**
alertmanager:
  enabled: false

# alertmanager:
#   ingress:
#     enabled: true
#     ingressClassName: alb
#     hosts: 
#       - alertmanager.$MyDomain
#     paths: 
#       - /*
#     annotations:
#       alb.ingress.kubernetes.io/scheme: internet-facing
#       alb.ingress.kubernetes.io/target-type: ip
#       alb.ingress.kubernetes.io/listen-ports: &#39;[{&quot;HTTPS&quot;:443}, {&quot;HTTP&quot;:80}]&#39;
#       alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
#       alb.ingress.kubernetes.io/success-codes: 200-399
#       alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
#       alb.ingress.kubernetes.io/group.name: study
#       alb.ingress.kubernetes.io/ssl-redirect: &#39;443&#39;
EOT
# 배포
$helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 45.27.2 \
&gt; --set prometheus.prometheusSpec.scrapeInterval=&#39;15s&#39; --set prometheus.prometheusSpec.evaluationInterval=&#39;15s&#39; \
&gt; -f monitor-values.yaml --namespace monitoring

NAME: kube-prometheus-stack
LAST DEPLOYED: Thu May 18 21:23:45 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
NOTES:
kube-prometheus-stack has been installed. Check its status by running:
  kubectl --namespace monitoring get pods -l &quot;release=kube-prometheus-stack&quot;

Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create &amp; configure Alertmanager and Prometheus instances using the Operator.

# 배포 확인
$kubectl get prometheus,servicemonitors -n monitoring
NAME                                                                VERSION   DESIRED   READY   RECONCILED   AVAILABLE   AGE
prometheus.monitoring.coreos.com/kube-prometheus-stack-prometheus   v2.42.0   1         1       True         True        119s

NAME                                                                                  AGE
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-apiserver                  119s
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-coredns                    119s
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-grafana                    119s
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-kube-proxy                 119s
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-kube-state-metrics         119s
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-kubelet                    119s
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-operator                   119s
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-prometheus                 119s
servicemonitor.monitoring.coreos.com/kube-prometheus-stack-prometheus-node-exporter   119s
$kubectl get crd | grep monitoring
alertmanagerconfigs.monitoring.coreos.com    2023-05-18T12:23:42Z
alertmanagers.monitoring.coreos.com          2023-05-18T12:23:43Z
podmonitors.monitoring.coreos.com            2023-05-18T12:23:43Z
probes.monitoring.coreos.com                 2023-05-18T12:23:43Z
prometheuses.monitoring.coreos.com           2023-05-18T12:23:44Z
prometheusrules.monitoring.coreos.com        2023-05-18T12:23:44Z
servicemonitors.monitoring.coreos.com        2023-05-18T12:23:44Z
thanosrulers.monitoring.coreos.com           2023-05-18T12:23:44Z

# 관련 리소스 확인
## 그라파나, 프로메테우스에 대한 리소스를 확인할 수 있다. 
$kubectl get pod,pvc,svc,ingress -n monitoring                                           Thu May 18 21:25:23 2023

NAME                                                            READY   STATUS    RESTARTS   AGE
pod/kube-prometheus-stack-grafana-846b5c46f9-9l86n              3/3     Running   0          79s
pod/kube-prometheus-stack-kube-state-metrics-5d6578867c-phkwd   1/1     Running   0          79s
pod/kube-prometheus-stack-operator-74d474b47b-wk6bg             1/1     Running   0          79s
pod/kube-prometheus-stack-prometheus-node-exporter-78z6k        1/1     Running   0          79s
pod/kube-prometheus-stack-prometheus-node-exporter-dzx2g        1/1     Running   0          79s
pod/kube-prometheus-stack-prometheus-node-exporter-frwsh        1/1     Running   0         79s
pod/prometheus-kube-prometheus-stack-prometheus-0               2/2     Running   0          74s

NAME                                                     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/kube-prometheus-stack-grafana                    ClusterIP   10.100.168.56    &lt;none&gt;        80/TCP     79s
service/kube-prometheus-stack-kube-state-metrics         ClusterIP   10.100.103.103   &lt;none&gt;        8080/TCP   79s
service/kube-prometheus-stack-operator                   ClusterIP   10.100.239.101   &lt;none&gt;        443/TCP    79s
service/kube-prometheus-stack-prometheus                 ClusterIP   10.100.9.3       &lt;none&gt;        9090/TCP   79s
service/kube-prometheus-stack-prometheus-node-exporter   ClusterIP   10.100.244.218   &lt;none&gt;        9100/TCP   79s
service/prometheus-operated                              ClusterIP   None             &lt;none&gt;        9090/TCP   74s

NAME                                                         CLASS   HOSTS                       ADDRESS
                                   PORTS   AGE
ingress.networking.k8s.io/kube-prometheus-stack-grafana      alb     grafana.kaneawsdns.com      myeks-ingress-alb-61132493.
ap-northeast-2.elb.amazonaws.com   80       79s
ingress.networking.k8s.io/kube-prometheus-stack-prometheus   alb     prometheus.kaneawsdns.com   myeks-ingress-alb-61132493.
ap-northeast-2.elb.amazonaws.com   80       79s</code></pre>
<p>HTTPS 규칙 확인</p>
<p>아래의 규칙대로 접속하여 그라파나와 프로메테우스에 접근할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/4a0f1f40-7d6b-4691-a955-3c347a66ca95/image.png" alt=""></p>
<p>그라파나의 경우 위의, <code>values.yaml</code> 파일에서 비밀번호를 확인할 수 있고, 계정은 admin입니다.
아래는 웹사이트 접속화면이다. </p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/648f8e57-020b-4439-8218-e968547ba15b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/9e7f9d7f-8cf4-4fcd-9c9c-9f0abe627074/image.png" alt=""></p>
<p>이후 사진들은 core dns에 대한 정보 모습이다.
<img src="https://velog.velcdn.com/images/han-0315/post/d5d51fe9-00aa-4912-a608-78525963e259/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/bb444916-48c6-4b24-a75b-3d205603afbd/image.png" alt=""></p>
<p>아래는 프로메테우스에 대한 정보</p>
<pre><code class="language-bash">$kubectl get svc,ep -n monitoring kube-prometheus-stack-prometheus-node-exporter
NAME                                                     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/kube-prometheus-stack-prometheus-node-exporter   ClusterIP   10.100.244.218   &lt;none&gt;        9100/TCP   12m
NAME                                                       ENDPOINTS                                                AGE
endpoints/kube-prometheus-stack-prometheus-node-exporter   192.168.1.181:9100,192.168.2.39:9100,192.168.3.43:9100   12m

$kubectl get ingress -n monitoring kube-prometheus-stack-prometheus
NAME                               CLASS   HOSTS                       ADDRESS                                                       PORTS   AGE
kube-prometheus-stack-prometheus   alb     prometheus.kaneawsdns.com   myeks-ingress-alb-61132493.ap-northeast-2.elb.amazonaws.com   80      13m
$kubectl describe ingress -n monitoring kube-prometheus-stack-prometheus
Name:             kube-prometheus-stack-prometheus
Labels:           app=kube-prometheus-stack-prometheus
                  app.kubernetes.io/instance=kube-prometheus-stack
                  app.kubernetes.io/managed-by=Helm
                  app.kubernetes.io/part-of=kube-prometheus-stack
                  app.kubernetes.io/version=45.27.2
                  chart=kube-prometheus-stack-45.27.2
                  heritage=Helm
                  release=kube-prometheus-stack
Namespace:        monitoring
Address:          myeks-ingress-alb-61132493.ap-northeast-2.elb.amazonaws.com
Ingress Class:    alb
Default backend:  &lt;default&gt;
Rules:
  Host                       Path  Backends
  ----                       ----  --------
  prometheus.kaneawsdns.com
                             /*   kube-prometheus-stack-prometheus:9090 (192.168.3.236:9090)
Annotations:                 alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-2:871103481195:certificate/caddaf14-7069-44a8-9fc3-ec13047ef5a1
                             alb.ingress.kubernetes.io/group.name: study
                             alb.ingress.kubernetes.io/listen-ports: [{&quot;HTTPS&quot;:443}, {&quot;HTTP&quot;:80}]
                             alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
                             alb.ingress.kubernetes.io/scheme: internet-facing
                             alb.ingress.kubernetes.io/ssl-redirect: 443
                             alb.ingress.kubernetes.io/success-codes: 200-399
                             alb.ingress.kubernetes.io/target-type: ip
                             meta.helm.sh/release-name: kube-prometheus-stack
                             meta.helm.sh/release-namespace: monitoring
Events:
  Type    Reason                  Age   From     Message
  ----    ------                  ----  ----     -------
  Normal  SuccessfullyReconciled  13m   ingress  Successfully reconciled</code></pre>
<p>아래는 수많은 매트릭들 9100 port에서 확인가능하다. </p>
<p><code>ssh ec2-user@$N1 curl -s localhost:9100/metrics</code></p>
<p>아래는 프로메테우스의 타겟그룹을 확인</p>
<pre><code class="language-bash">#아래는 프로메테우스의 타겟그룹을 확인
$curl -s http://192.168.1.181:10249/metrics | tail -n 5
rest_client_response_size_bytes_bucket{host=&quot;0122641b7b2835e8936049a66cb8332a.gr7.ap-northeast-2.eks.amazonaws.com&quot;,verb=&quot;POST&quot;,le=&quot;4.194304e+06&quot;} 1
rest_client_response_size_bytes_bucket{host=&quot;0122641b7b2835e8936049a66cb8332a.gr7.ap-northeast-2.eks.amazonaws.com&quot;,verb=&quot;POST&quot;,le=&quot;1.6777216e+07&quot;} 1
rest_client_response_size_bytes_bucket{host=&quot;0122641b7b2835e8936049a66cb8332a.gr7.ap-northeast-2.eks.amazonaws.com&quot;,verb=&quot;POST&quot;,le=&quot;+Inf&quot;} 1
rest_client_response_size_bytes_sum{host=&quot;0122641b7b2835e8936049a66cb8332a.gr7.ap-northeast-2.eks.amazonaws.com&quot;,verb=&quot;POST&quot;} 626
rest_client_response_size_bytes_count{host=&quot;0122641b7b2835e8936049a66cb8332a.gr7.ap-northeast-2.eks.amazonaws.com&quot;,verb=&quot;POST&quot;} 1
## 아래의 명령어는 한 단계 아래의 apiserver endpoint를 찾기위한 과정
# 엔드포인트 확인
$k get ep -A | grep stack
kube-system   kube-prometheus-stack-coredns                    192.168.1.171:9153,192.168.3.46:9153                                    20m
kube-system   kube-prometheus-stack-kube-proxy                 192.168.1.181:10249,192.168.2.39:10249,192.168.3.43:10249               20m
kube-system   kube-prometheus-stack-kubelet                    192.168.1.181:10250,192.168.2.39:10250,192.168.3.43:10250 + 6 more...   20m
monitoring    kube-prometheus-stack-grafana                    192.168.1.15:3000                                                       20m
monitoring    kube-prometheus-stack-kube-state-metrics         192.168.2.198:8080                                                      20m
monitoring    kube-prometheus-stack-operator                   192.168.2.244:10250                                                     20m
monitoring    kube-prometheus-stack-prometheus                 192.168.3.236:9090                                                      20m
monitoring    kube-prometheus-stack-prometheus-node-exporter   192.168.1.181:9100,192.168.2.39:9100,192.168.3.43:9100
$k get po -A -o wide | grep 192.168.1
kube-system   aws-node-fbjck                                              1/1     Running   0          112m   192.168.1.181   ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   coredns-6777fcd775-pm8d8                                    1/1     Running   0          110m   192.168.1.171   ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   ebs-csi-node-v7gqf                                          3/3     Running   0          108m   192.168.1.154   ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   efs-csi-controller-6f64dcc5dc-db9j8                         3/3     Running   0          37m    192.168.1.181   ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   efs-csi-node-rlfjg                                          3/3     Running   0          37m    192.168.1.181   ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-ops-view-558d87b798-6l6sf                              1/1     Running   0          39m    192.168.1.47    ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-kwmhh                                            1/1     Running   0          111m   192.168.1.181   ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
monitoring    kube-prometheus-stack-grafana-846b5c46f9-9l86n              3/3     Running   0          25m    192.168.1.15    ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
monitoring    kube-prometheus-stack-prometheus-node-exporter-frwsh        1/1     Running   0          25m    192.168.1.181   ip-192-168-1-181.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/han-0315/post/2c372082-a2f5-4f6f-aba2-bbbe9d9dab8d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/5816b57e-ce96-4d02-a8f7-469be51a25ac/image.png" alt=""></p>
<h3 id="그라파나-grafana">그라파나 Grafana</h3>
<p>그라파나는 데이터를 시각화해주는 툴로, 직접 데이터를 제공해주진 않는다. 보통 프로메테우스와 그라파나를 연동시켜 사용한다.</p>
<p>아래는 그라파나를 배포하는 명령어입니다.</p>
<pre><code class="language-bash">$cat &lt;&lt;EOF | kubectl create -f -
&gt; apiVersion: monitoring.coreos.com/v1
&gt; kind: PodMonitor
&gt; metadata:
&gt;   name: aws-cni-metrics
&gt;   namespace: kube-system
&gt; spec:
&gt;   jobLabel: k8s-app
&gt;   namespaceSelector:
&gt;     matchNames:
&gt;     - kube-system
&gt;   podMetricsEndpoints:
&gt;   - interval: 30s
&gt;     path: /metrics
&gt;     port: metrics
&gt;   selector:
&gt;     matchLabels:
&gt;       k8s-app: aws-node
&gt; EOF
podmonitor.monitoring.coreos.com/aws-cni-metrics created
$kubectl get podmonitor -n kube-system
NAME              AGE
aws-cni-metrics   48s
$cat &lt;&lt;EOT &gt; ~/nginx_metric-values.yaml
&gt; metrics:
&gt;   enabled: true
&gt;
&gt;   service:
&gt;     port: 9113
&gt;
&gt;   serviceMonitor:
&gt;     enabled: true
&gt;     namespace: monitoring
&gt;     interval: 10s
&gt; EOT

# 배포
$helm upgrade nginx bitnami/nginx --reuse-values -f nginx_metric-values.yaml
Release &quot;nginx&quot; has been upgraded. Happy Helming!
NAME: nginx
LAST DEPLOYED: Thu May 18 22:01:32 2023
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
CHART NAME: nginx
CHART VERSION: 14.2.1
APP VERSION: 1.24.0

** Please be patient while the chart is being deployed **
NGINX can be accessed through the following DNS name from within your cluster:

    nginx.default.svc.cluster.local (port 80)

To access NGINX from outside the cluster, follow the steps below:

1. Get the NGINX URL and associate its hostname to your cluster external IP:

   export CLUSTER_IP=$(minikube ip) # On Minikube. Use: `kubectl cluster-info` on others K8s clusters
   echo &quot;NGINX URL: http://nginx.kaneawsdns.com&quot;
   echo &quot;$CLUSTER_IP  nginx.kaneawsdns.com&quot; | sudo tee -a /etc/hosts

# 배포 결과 확인
$kubectl get pod,svc,ep
NAME                         READY   STATUS              RESTARTS   AGE
pod/nginx-685c67bc9-bv9xr    1/1     Running             0          48m
pod/nginx-85fc957979-695lk   0/2     ContainerCreating   0          1s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                       AGE
service/kubernetes   ClusterIP   10.100.0.1      &lt;none&gt;        443/TCP                       135m
service/nginx        NodePort    10.100.192.11   &lt;none&gt;        80:30443/TCP,9113:31223/TCP   48m

NAME                   ENDPOINTS                            AGE
endpoints/kubernetes   192.168.1.175:443,192.168.3.92:443   135m
endpoints/nginx        192.168.2.93:8080                    48m
$kubectl get servicemonitor -n monitoring nginx
NAME    AGE
nginx   2s
$kubectl get servicemonitor -n monitoring nginx -o json | jq
{
  &quot;apiVersion&quot;: &quot;monitoring.coreos.com/v1&quot;,
  &quot;kind&quot;: &quot;ServiceMonitor&quot;,
  &quot;metadata&quot;: {
    &quot;annotations&quot;: {
      &quot;meta.helm.sh/release-name&quot;: &quot;nginx&quot;,
      &quot;meta.helm.sh/release-namespace&quot;: &quot;default&quot;
    },
    &quot;creationTimestamp&quot;: &quot;2023-05-18T13:01:33Z&quot;,
    &quot;generation&quot;: 1,
    &quot;labels&quot;: {
      &quot;app.kubernetes.io/instance&quot;: &quot;nginx&quot;,
      &quot;app.kubernetes.io/managed-by&quot;: &quot;Helm&quot;,
      &quot;app.kubernetes.io/name&quot;: &quot;nginx&quot;,
      &quot;helm.sh/chart&quot;: &quot;nginx-14.2.1&quot;
    },
    &quot;name&quot;: &quot;nginx&quot;,
    &quot;namespace&quot;: &quot;monitoring&quot;,
    &quot;resourceVersion&quot;: &quot;30382&quot;,
    &quot;uid&quot;: &quot;d7da1d26-5f4e-4ab2-b980-2f45f274b667&quot;
  },
  &quot;spec&quot;: {
    &quot;endpoints&quot;: [
      {
        &quot;interval&quot;: &quot;10s&quot;,
        &quot;path&quot;: &quot;/metrics&quot;,
        &quot;port&quot;: &quot;metrics&quot;
      }
    ],
    &quot;jobLabel&quot;: &quot;&quot;,
    &quot;namespaceSelector&quot;: {
      &quot;matchNames&quot;: [
        &quot;default&quot;
      ]
    },
    &quot;selector&quot;: {
      &quot;matchLabels&quot;: {
        &quot;app.kubernetes.io/instance&quot;: &quot;nginx&quot;,
        &quot;app.kubernetes.io/name&quot;: &quot;nginx&quot;
      }
    }
  }
}</code></pre>
<p>이제 관련 메트릭을 확인해보는 실습이다. </p>
<pre><code class="language-bash"># 메트릭 확인 &gt;&gt; 프로메테우스에서 Target 확인
$NGINXIP=$(kubectl get pod -l app.kubernetes.io/instance=nginx -o jsonpath={.items[0].status.podIP})
$curl -s http://$NGINXIP:9113/metrics # nginx_connections_active Y 값 확인해보기
$curl -s http://$NGINXIP:9113/metrics | grep ^nginx_connections_active
$NGINXIP=$(kubectl get pod -l app.kubernetes.io/instance=nginx -o jsonpath={.items[0].status.podIP})
$curl -s http://$NGINXIP:9113/metrics | grep ^nginx_connections_active
nginx_connections_active 1
$NGINXIP=$(kubectl get pod -l app.kubernetes.io/instance=nginx -o jsonpath={.items[0].status.podIP})
$curl -s http://$NGINXIP:9113/metrics # nginx_connections_active Y 값 확인해보기
# HELP nginx_connections_accepted Accepted client connections
# TYPE nginx_connections_accepted counter
nginx_connections_accepted 32
# HELP nginx_connections_active Active client connections
# TYPE nginx_connections_active gauge
nginx_connections_active 1
# HELP nginx_connections_handled Handled client connections
# TYPE nginx_connections_handled counter
nginx_connections_handled 32
# HELP nginx_connections_reading Connections where NGINX is reading the request header
# TYPE nginx_connections_reading gauge
nginx_connections_reading 0
# HELP nginx_connections_waiting Idle client connections
# TYPE nginx_connections_waiting gauge
nginx_connections_waiting 0
# HELP nginx_connections_writing Connections where NGINX is writing the response back to the client
# TYPE nginx_connections_writing gauge
nginx_connections_writing 1
# HELP nginx_http_requests_total Total http requests
# TYPE nginx_http_requests_total counter
nginx_http_requests_total 28
# HELP nginx_up Status of the last metric scrape
# TYPE nginx_up gauge
nginx_up 1
# HELP nginxexporter_build_info Exporter build information
# TYPE nginxexporter_build_info gauge
nginxexporter_build_info{arch=&quot;linux/amd64&quot;,commit=&quot;e4a6810d4f0b776f7fde37fea1d84e4c7284b72a&quot;,date=&quot;2022-09-07T21:09:51Z&quot;,dirty=&quot;false&quot;,go=&quot;go1.19&quot;,version=&quot;0.11.0&quot;} 1

# 파드 개수 확인
$kubectl get pod -l app.kubernetes.io/instance=nginx
NAME                     READY   STATUS    RESTARTS   AGE
nginx-85fc957979-695lk   2/2     Running   0          111s
$kubectl describe pod -l app.kubernetes.io/instance=nginx
Name:             nginx-85fc957979-695lk
Namespace:        default
Priority:         0
Service Account:  default
Node:             ip-192-168-1-181.ap-northeast-2.compute.internal/192.168.1.181
Start Time:       Thu, 18 May 2023 22:01:33 +0900
Labels:           app.kubernetes.io/instance=nginx
                  app.kubernetes.io/managed-by=Helm
                  app.kubernetes.io/name=nginx
                  helm.sh/chart=nginx-14.2.1
                  pod-template-hash=85fc957979
Annotations:      kubernetes.io/psp: eks.privileged
Status:           Running
IP:               192.168.1.232
IPs:
  IP:           192.168.1.232
Controlled By:  ReplicaSet/nginx-85fc957979
Containers:
  nginx:
    Container ID:   containerd://11ddc2b099deb1833034d0fe5e71c311f9aaaaa8919e11f7a181692df8131838
    Image:          docker.io/bitnami/nginx:1.24.0-debian-11-r0
    Image ID:       docker.io/bitnami/nginx@sha256:002741bc8e88e7758001dfecffd05fcc5dc182db850e08161c488de27404f5be
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Thu, 18 May 2023 22:01:39 +0900
    Ready:          True
    Restart Count:  0
    Liveness:       tcp-socket :http delay=30s timeout=5s period=10s #success=1 #failure=6
    Readiness:      tcp-socket :http delay=5s timeout=3s period=5s #success=1 #failure=3
    Environment:
      BITNAMI_DEBUG:           false
      NGINX_HTTP_PORT_NUMBER:  8080
    Mounts:                    &lt;none&gt;
  metrics:
    Container ID:  containerd://e42daca6fc8edcd7410b44c1343c366df92d536235465d5522f3f7a5f3347d74
    Image:         docker.io/bitnami/nginx-exporter:0.11.0-debian-11-r74
    Image ID:      docker.io/bitnami/nginx-exporter@sha256:ec05a98e16d8b04f554d02ed87033dd99596ac827ce9ad793bbe570f2372ce5e
    Port:          9113/TCP
    Host Port:     0/TCP
    Command:
      /usr/bin/exporter
      -nginx.scrape-uri
      http://127.0.0.1:8080/status
    State:          Running
      Started:      Thu, 18 May 2023 22:01:44 +0900
    Ready:          True
    Restart Count:  0
    Liveness:       http-get http://:metrics/metrics delay=15s timeout=5s period=10s #success=1 #failure=3
    Readiness:      http-get http://:metrics/metrics delay=5s timeout=1s period=10s #success=1 #failure=3
    Environment:    &lt;none&gt;
    Mounts:         &lt;none&gt;
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:            &lt;none&gt;
QoS Class:          BestEffort
Node-Selectors:     &lt;none&gt;
Tolerations:        node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                    node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  112s  default-scheduler  Successfully assigned default/nginx-85fc957979-695lk to ip-192-168-1-181.ap-northeast-2.compute.internal
  Normal  Pulling    112s  kubelet            Pulling image &quot;docker.io/bitnami/nginx:1.24.0-debian-11-r0&quot;
  Normal  Pulled     106s  kubelet            Successfully pulled image &quot;docker.io/bitnami/nginx:1.24.0-debian-11-r0&quot; in 5.357183997s
  Normal  Created    106s  kubelet            Created container nginx
  Normal  Started    106s  kubelet            Started container nginx
  Normal  Pulling    106s  kubelet            Pulling image &quot;docker.io/bitnami/nginx-exporter:0.11.0-debian-11-r74&quot;
  Normal  Pulled     101s  kubelet            Successfully pulled image &quot;docker.io/bitnami/nginx-exporter:0.11.0-debian-11-r74&quot; in 5.400426806s
  Normal  Created    101s  kubelet            Created container metrics
  Normal  Started    101s  kubelet            Started container metrics</code></pre>
<p><img src="https://velog.velcdn.com/images/han-0315/post/8ba017a4-1b13-4844-b754-fbefb2b6c9e3/image.png" alt=""></p>
<p>아래는 대시보드와 관련된 사진이다. 가시다님이 추천해주신 <a href="https://grafana.com/orgs/imrtfm/dashboards">대시보드</a>는 링크를 따라서 확인할 수 있다. </p>
<p>아래는 프로메테우스에 대한 대시보드</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/f6cd2d02-a99d-4429-baaa-10c94594a08b/image.png" alt=""></p>
<p>오래는 NGINX에 대한 대시보드</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/b5c47b3b-de2c-41da-9d66-95f13cad49f7/image.png" alt=""></p>
<h3 id="kubecost">kubecost</h3>
<p>kubecost는 <a href="https://www.opencost.io/">OpenCost</a>를 기반으로 구축되었으며 쿠버네티스 용량과 비용에 대한 모니터링 및 시각화를 제공해준다. AWS에서 적극지원한다고 하니, 호환성이 높을 듯 하다.</p>
<p>아래는 설치와 관련된 실습이다. </p>
<pre><code class="language-bash">$cat cost-values.yaml
global:
  grafana:
    enabled: true
    proxy: false

priority:
  enabled: false
networkPolicy:
  enabled: false
podSecurityPolicy:
  enabled: false

persistentVolume:
    storageClass: &quot;gp3&quot;

prometheus:
  kube-state-metrics:
    disabled: false
  nodeExporter:
    enabled: true

reporting:
  productAnalytics: true
$helm uninstall -n monitoring kube-prometheus-stack

$kubectl create ns kubecost

$helm install kubecost oci://public.ecr.aws/kubecost/cost-analyzer --version 1.103.2 --namespace kubecost -f cost-values.yaml
Pulled: public.ecr.aws/kubecost/cost-analyzer:1.103.2
Digest: sha256:26d0d364d8763a142a6d52c8e5fc0ceecb1131862e7405d71d2273d8ddb45a9e
W0518 22:14:24.188018   18612 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W0518 22:14:24.603042   18612 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
NAME: kubecost
LAST DEPLOYED: Thu May 18 22:14:22 2023
NAMESPACE: kubecost
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
--------------------------------------------------Kubecost has been successfully installed.

WARNING: ON EKS v1.23+ INSTALLATION OF EBS-CSI DRIVER IS REQUIRED TO MANAGE PERSISTENT VOLUMES. LEARN MORE HERE: https://docs.kubecost.com/install-and-configure/install/provider-installations/aws-eks-cost-monitoring#prerequisites

Please allow 5-10 minutes for Kubecost to gather metrics.

If you have configured cloud-integrations, it can take up to 48 hours for cost reconciliation to occur.

When using Durable storage (Enterprise Edition), please allow up to 4 hours for data to be collected and the UI to be healthy.

When pods are Ready, you can enable port-forwarding with the following command:

    kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090

Next, navigate to http://localhost:9090 in a web browser.

Having installation issues? View our Troubleshooting Guide at http://docs.kubecost.com/troubleshoot-install

$curl http://localhost:9090

$kubectl get all -n kubecost
NAME                                              READY   STATUS    RESTARTS   AGE
pod/kubecost-cost-analyzer-996544d88-vktft        2/2     Running   0          69s
pod/kubecost-grafana-867bbf59c7-n7978             2/2     Running   0          69s
pod/kubecost-kube-state-metrics-d6d9b7594-5k6wj   1/1     Running   0          69s
pod/kubecost-prometheus-node-exporter-74vxw       1/1     Running   0          69s
pod/kubecost-prometheus-node-exporter-bf22q       1/1     Running   0          69s
pod/kubecost-prometheus-node-exporter-vwj72       1/1     Running   0          69s
pod/kubecost-prometheus-server-77bd8b8d6f-dpn62   2/2     Running   0          69s

NAME                                        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
service/kubecost-cost-analyzer              ClusterIP   10.100.135.82   &lt;none&gt;        9003/TCP,9090/TCP   69s
service/kubecost-grafana                    ClusterIP   10.100.2.134    &lt;none&gt;        80/TCP              69s
service/kubecost-kube-state-metrics         ClusterIP   10.100.86.120   &lt;none&gt;        8080/TCP            69s
service/kubecost-prometheus-node-exporter   ClusterIP   None            &lt;none&gt;        9100/TCP            69s
service/kubecost-prometheus-server          ClusterIP   10.100.72.137   &lt;none&gt;        80/TCP              69s

NAME                                               DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/kubecost-prometheus-node-exporter   3         3         3       3            3           &lt;none&gt;          69s

NAME                                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kubecost-cost-analyzer        1/1     1            1           69s
deployment.apps/kubecost-grafana              1/1     1            1           69s
deployment.apps/kubecost-kube-state-metrics   1/1     1            1           69s
deployment.apps/kubecost-prometheus-server    1/1     1            1           69s

NAME                                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/kubecost-cost-analyzer-996544d88        1         1         1       69s
replicaset.apps/kubecost-grafana-867bbf59c7             1         1         1       69s
replicaset.apps/kubecost-kube-state-metrics-d6d9b7594   1         1         1       69s
replicaset.apps/kubecost-prometheus-server-77bd8b8d6f   1         1         1       69s

# 테스트
CAIP=$(kubectl get pod -n kubecost -l app=cost-analyzer -o jsonpath={.items[0].status.podIP})

$echo $CAIP
192.168.2.167

$curl -s $CAIP:9090
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;meta
      name=&quot;description&quot;
      content=&quot;Monitor and reduce Kubernetes spend&quot;
    /&gt;
    &lt;link rel=&quot;icon&quot; href=&quot;./favicon.ico&quot; /&gt;
    &lt;link rel=&quot;apple-touch-icon&quot; href=&quot;./logo196.png&quot; /&gt;
    &lt;!--
      manifest.json provides metadata used when your web app is installed on a
      user&#39;s mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    --&gt;
    &lt;link rel=&quot;manifest&quot; href=&quot;./manifest.json&quot; /&gt;
    &lt;script type=&quot;text/javascript&quot;&gt;window.global ||= window&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;./jquery.min.3.6.0.js&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;./helper.js&quot;&gt;&lt;/script&gt;
    &lt;title&gt;Kubecost&lt;/title&gt;
    &lt;script type=&quot;module&quot; crossorigin src=&quot;./static/index-04613a22.js&quot;&gt;&lt;/script&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;./static/index-d798bf65.css&quot;&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;div id=&quot;portal-root&quot;&gt;&lt;/div&gt;

  &lt;/body&gt;
&lt;/html&gt;

$socat TCP-LISTEN:80,fork TCP:$CAIP:9090</code></pre>
<p>아래는 실제 접속한 사이트</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/92d39810-6702-4deb-8f5b-ea9cacbe39a8/image.png" alt=""></p>
<h3 id="마치며">마치며</h3>
<p>이번 주차에서는 개념적인 내용보다, 배포와 확인하는 내용이 많았다. 실제 옵저버빌리티를 구축을 하면서 다양한 툴을 직접 사용해봤다. 최근에 핫하다는 opentelmetry도 추후 한번 배포해볼 생각이다. </p>
<!-- ### AWS 관리형 서비스 AMP & AMG -->

<!-- ### OpenTelemetry(OTel) : 추가 예정 -->

<!-- **opentelemetry는 해당 분야에서 최근에** **핫한 도구입니다.** 

한번 해보기

[https://github.com/awslabs/eks-node-viewer](https://github.com/awslabs/eks-node-viewer)
 -->
<!-- ### 궁금한 점

- **터미널 로그를 자동으로 저장하는 기능이 없나? → 로컬에 저장.**
    - 이를 통해, 일정수준 지나면 로그가 안보이는 것을 해결

추후 진행

## 경험발표

### k8s 데이터베이스 운영

**운영한 이유?**

- 5가지(elastic search, scylla, gridgain, confluent)운영
- Nosql에 대한 니즈, 쿠버네티스에 대한 성숙도가 높음, POC작업을 쿠버네티스 상에서 진행.

장점 : 모니터링, 로깅 등 따로 설정이 필요없음, 빠른 배포 + 쉽게 POC

단점 : 두가지 모두 이해도가 필요, 초기설정, 관련 사례가 적었음

**클러스터 구성**

각 DB 혹은 프로그램마다, 맞는 CPU 아키텍처가 존재. (amd vs X86)

`kafka to kafka` 기능

[https://strimzi.io](https://strimzi.io/)

인스턴스 스토어에서, Scylla 제공

[https://aws.amazon.com/ko/ec2/instance-types/i4i/](https://aws.amazon.com/ko/ec2/instance-types/i4i/)

[https://strimzi.io/blog/2020/06/15/cruise-control/](https://strimzi.io/blog/2020/06/15/cruise-control/) : 억대로 돈을 아낀다? -->

<!-- ### EKS 파드, S3 계정간 업로드 테스트

[https://linuxer.name/2023/05/eks-nodeless-01-coredns/](https://linuxer.name/2023/05/eks-nodeless-01-coredns/) -->
]]></description>
        </item>
        <item>
            <title><![CDATA[EKS 스터디 3주차]]></title>
            <link>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 13 May 2023 14:41:53 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<p>이번주에는 스토리지와 관련된 부분을 배웠습니다. 순서는 EKS 배포 환경, LB, External DNS 등 2주차 내용에 이어 추가적으로 진행하는 부분, 스토리지에 대한 이해와 각 방식에 대한 실습을 진행한 뒤 EBS, EFS 를 적용해보는 것으로 마무리됩니다. </p>
<h3 id="환경-배포">환경 배포</h3>
<p>환경배포파일이 저번주와 달라졌습니다. 기존에 것에서 저번주차와 이번주차 진행할 내용들이 추가되었습니다. 
앞으로 실습내용을 작업용 EC2에서도 확인하기 위해 EFS를 마운트합니다. </p>
<p><code>mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport ***&lt;자신의 EFS FS ID&gt;***.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs</code></p>
<p><code>kubectl config rename-context admin@myeks.ap-northeast-2.eksctl.io [원하는 이름]@myeks</code>
실습환경에서  AWS LB/ExternalDNS, kube-ops-view를 추가적으로 설치합니다. 설치과정은 아래에서 확인할 수 있습니다.</p>
<h3 id="배포설정">배포설정</h3>
<p>먼저, 기본적인 환경세팅을 진행합니다. 아까 위에서 나왔던 context 변경부터 진행합니다. </p>
<p><strong>context 변경</strong></p>
<pre><code class="language-bash"># context 변경 
(EKS-study@myeks:N/A) [root@myeks-bastion-EC2 ~] # kubectl config rename-context EKS-study@myeks.ap-northeast-2.eksctl.io $NICK@myeks
Context &quot;EKS-study@myeks.ap-northeast-2.eksctl.io&quot; renamed to &quot;kane@myeks&quot;.
(kane@myeks:N/A) [root@myeks-bastion-EC2 ~] #</code></pre>
<p><strong>storage class 확인</strong></p>
<pre><code class="language-bash">kubectl get sc gp2 -o yaml | yh
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {&quot;apiVersion&quot;:&quot;storage.k8s.io/v1&quot;,&quot;kind&quot;:&quot;StorageClass&quot;,&quot;metadata&quot;:{&quot;annotations&quot;:{&quot;storageclass.kubernetes.io/is-default-class&quot;:&quot;true&quot;},&quot;name&quot;:&quot;gp2&quot;},&quot;parameters&quot;:{&quot;fsType&quot;:&quot;ext4&quot;,&quot;type&quot;:&quot;gp2&quot;},&quot;provisioner&quot;:&quot;kubernetes.io/aws-ebs&quot;,&quot;volumeBindingMode&quot;:&quot;WaitForFirstConsumer&quot;}
    storageclass.kubernetes.io/is-default-class: &quot;true&quot;
  creationTimestamp: &quot;2023-05-07T14:02:40Z&quot;
  name: gp2
  resourceVersion: &quot;266&quot;
  uid: b8040117-dce8-4173-b8d4-2a77d7a8124f
parameters:
  fsType: ext4
  type: gp2
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
</code></pre>
<p>이제 eks의 모든 노드들이 csinode인지 확인합니다.
<strong>csinodes</strong>
&quot;kubectl get csinodes&quot; 명령은 Kubernetes 클러스터에서 사용 가능한 모든 CSINode(CSI Node)을 나열합니다. CSINode은 Container Storage Interface(CSI) 스펙을 준수하는 노드입니다. CSI 스펙은 Kubernetes 클러스터에서 다양한 스토리지 시스템과의 통합을 위한 표준 인터페이스를 제공합니다.</p>
<pre><code class="language-bash">$k get csinodes
NAME                                               DRIVERS   AGE
ip-192-168-1-137.ap-northeast-2.compute.internal   0         26m
ip-192-168-2-116.ap-northeast-2.compute.internal   0         26m
ip-192-168-3-151.ap-northeast-2.compute.internal   0         26m </code></pre>
<p><code>--label-columns</code> 옵션을 통해 eks node를 조회한 모습입니다. </p>
<pre><code class="language-bash">$kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
NAME                                               STATUS   ROLES    AGE   VERSION                INSTANCE-TYPE   CAPACITYTYPE   ZONE
ip-192-168-1-137.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   29m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2a
ip-192-168-2-116.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   29m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2b
ip-192-168-3-151.ap-northeast-2.compute.internal   Ready    &lt;none&gt;   29m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2c

$eksctl get iamidentitymapping --cluster myeks
ARN                                            USERNAME                GROUPS                    ACCOUNT
arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-WBR1ZJTQNHTR    system:node:{{EC2PrivateDNSName}}    system:bootstrappers,system:nodes</code></pre>
<h3 id="2주차-추가진행">2주차 추가진행</h3>
<p><strong>보안그룹 추가</strong></p>
<p>&quot;--protocol &#39;-1&#39;&quot;은 모든 프로토콜을 허용하도록 설정하는 것을 의미합니다. 이 플래그를 사용하여 TCP, UDP 및 ICMP를 비롯한 모든 프로토콜에 대한 트래픽을 허용할 수 있습니다.</p>
<p>&quot;--cidr&quot;은 허용할 IP 대역을 지정합니다. 여기서는 &quot;192.168.1.100/32&quot;라는 단일 IP 주소를 허용하도록 설정하였습니다.</p>
<pre><code class="language-bash">$aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol &#39;-1&#39; --cidr 192.168.1.100/32
{
    &quot;Return&quot;: true,
    &quot;SecurityGroupRules&quot;: [
        {
            &quot;SecurityGroupRuleId&quot;: &quot;sgr-0bb4b08586cfb50c5&quot;,
            &quot;GroupId&quot;: &quot;sg-0c78cd151603fbd80&quot;,
            &quot;GroupOwnerId&quot;: &quot;871103481195&quot;,
            &quot;IsEgress&quot;: false,
            &quot;IpProtocol&quot;: &quot;-1&quot;,
            &quot;FromPort&quot;: -1,
            &quot;ToPort&quot;: -1,
            &quot;CidrIpv4&quot;: &quot;192.168.1.100/32&quot;
        }
    ]
}</code></pre>
<!-- 각 노드에  tcp dump 추가

```bash
# 워커 노드 SSH 접속
ssh ec2-user@$N1 hostname
ssh ec2-user@$N2 hostname
ssh ec2-user@$N3 hostname

# 노드에 툴 설치
ssh ec2-user@$N1 sudo yum install links tree jq tcpdump sysstat -y
ssh ec2-user@$N2 sudo yum install links tree jq tcpdump sysstat -y
ssh ec2-user@$N3 sudo yum install links tree jq tcpdump sysstat -y
``` -->

<p><strong>aws-load-balancer-controller 설치한다.</strong></p>
<pre><code class="language-bash">$helm repo add eks https://aws.github.io/eks-charts
&quot;eks&quot; has been added to your repositories

$helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the &quot;eks&quot; chart repository
Update Complete. ⎈Happy Helming!⎈

$helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
&gt;   --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

NAME: aws-load-balancer-controller
LAST DEPLOYED: Sun May  7 23:47:07 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!</code></pre>
<p><strong>External DNS 적용 : 개인 도메인은 사전에 준비필요!</strong></p>
<pre><code class="language-bash">$echo $MyDomain, $MyDnzHostedZoneId
kaneawsdns.com, /hostedzone/Z06702063E7RRITLLMJRM
#사전에 관련 매니패스트 파일을 준비해주셨다.

$curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml

# External DNS 배포
$MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst &lt; externaldns.yaml | kubectl apply -f -</code></pre>
<p>위의 <code>envsubst</code> 명령어에 대한 간단한 설명</p>
<p><code>envsubst</code> : 현재 환경변수를 수레에서 사용가능한 변수로 치한 → 앞의 MyDomain, MyDnzHostedZoneId를 알맞게 externaldns.yaml 파일에 적용킨 후 해당 yaml 파일을 <code>kubectl apply</code> 합니다.</p>
<p>매니패스트 파일은 service account, cluster role, cluster role binding, deployment, service, configmap 등이 포함되어 있습니다. 토글에는 자세한 내용이 담겨있습니다. 자세한 내용은 <a href="https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml">링크</a>를 통해서 확인할 수 있습니다.</p>
<p>이제 UI 툴인 kube-ops-view를 설치해보겠습니다.</p>
<p><strong>kubeops 설치</strong></p>
<pre><code class="language-bash">#repo 추가 
$helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
&quot;geek-cookbook&quot; has been added to your repositories
#kube-ops-view 설치
$helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ=&quot;Asia/Seoul&quot; --namespace kube-system
NAME: kube-ops-view
LAST DEPLOYED: Sun May  7 23:53:13 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace kube-system -l &quot;app.kubernetes.io/name=kube-ops-view,app.kubernetes.io/instance=kube-ops-view&quot; -o jsonpath=&quot;{.items[0].metadata.name}&quot;)
  echo &quot;Visit http://127.0.0.1:8080 to use your application&quot;
  kubectl port-forward $POD_NAME 8080:8080

#kube-ops-view 서비스의 타입을 &quot;loadBalancer&quot;로 변경하는 모습, patch는 수정할 때 쓰는 명령어
#`-p` 옵션은 json 형태로 수정할 때
$kubectl patch svc -n kube-system kube-ops-view -p &#39;{&quot;spec&quot;:{&quot;type&quot;:&quot;LoadBalancer&quot;}}&#39;
service/kube-ops-view patched
#서비스에 대한 외부 도메인을 주석으로 달아줌(annotate 주석)
$kubectl annotate service kube-ops-view -n kube-system &quot;external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain&quot;
service/kube-ops-view annotated
$echo -e &quot;Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5&quot;
Kube Ops View URL = http://kubeopsview.kaneawsdns.com:8080/#scale=1.5</code></pre>
<p>아래는 kubeops 실제 화면입니다. 큰 클러스터안에 노드별로 구분되어있는 것을 확인할 수 있습니다.</p>
<ul>
<li>파드의 개수를 늘린 모습, 가져다가 되면 구체적으로 정보가 나옵니다.</li>
</ul>
<p>위의 <code>DEFAULT</code>로 나와있는 부분은 출력형식을 지정하는 것입니다. 
파드는 네임스페이스에 따라 구분됩니다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/ddf4ee4b-a9d7-411e-87ff-53d5e2833810/image.png" alt=""></p>
<h2 id="스토리지">스토리지</h2>
<p>이제 이번주차의 핵심인 스터리지에 대해서 실습을 진행합니다. 파드 내부의 데이터는 파드와 생명주기가 동일하여 파드가 삭제되면 데이터도 사라집니다. 만약, DB 애플리케이션과 같은 경우 데이터는 보존이 필요합니다. 이럴 경우  persistent volume 을 연결하여 사용합니다. </p>
<p>PV와 PVC에 대한 내용은 <a href="https://aws.amazon.com/ko/blogs/tech/persistent-storage-for-kubernetes/">AWS Blog</a> 문서를 참고했습니다.</p>
<p>Persistent Volume(PV)은 실제 스토리지 볼륨을 나타냅니다. 쿠버네티스는 PV 위에 추가 추상화 계층인 PersistentVolumeClaim(PVC)을 가지고 있습니다. 구분하는 이유는 아래의 문서에서 확인할 수 있습니다. </p>
<blockquote>
<p>PV와 PVC의 구분은 Kubernetes 환경에서 두 가지 유형의 사용자가 있다는 개념과 관련이 있습니다.</p>
<ul>
<li><strong>Kubernetes 관리자</strong>: 이 사용자는 클러스터를 유지 관리하고 운영하며 영구 스토리지와 같은 계산 리소스를 추가합니다.</li>
<li><strong>Kubernetes 애플리케이션</strong> <strong>개발자</strong>: 이 사용자는 애플리케이션을 개발하고 배포합니다.</li>
</ul>
</blockquote>
<p>CSI는 Container Storage Interface로 다양한 스토리지 솔류션에 대한 인터페이스입니다. CSI를 통해 다음과 같은 2가지 방법으로 스토리지를 연결할 수 있습니다.</p>
<p><strong>정적</strong> <strong>프로비저닝(Static provisioning)</strong>
먼저 관리자가 하나 이상의 PV를 생성하고 애플리케이션 개발자는 PVC를 생성합니다.
<strong>동적</strong> <strong>프로비저닝(Dynamic provisioning)</strong>
동적 프로비저닝을 사용하면 PV객체를 생성할 필요가 없습니다. 대신에, PVC를 생성할 때 내부적으로 자동으로 생성됩니다. Kubernetes는 Storage Class라는 다른 객체를 사용하여 이를 수행합니다.</p>
<ul>
<li>volume 방식
<img src="https://velog.velcdn.com/images/han-0315/post/fad0c72d-a552-4546-b340-212977855671/image.png" alt=""></li>
</ul>
<p>방식은 위의 그림과 같이 3가지가 있습니다. 첫번째 방식은 파드의 생명주기와 데이터의 생명주기가 같습니다. 데이터를 보존해야할 경우 2~3번째 방식을 이용해야 합니다. 2번째 방식은 아래의 local-path에서 설명할 예정입니다.</p>
<h3 id="emptydir">emptyDir</h3>
<p>먼저, 가시다님의 이미지를 통해 파드의 생명주기와 데이터의 보존주기가 같은 것을 확인한 결과입니다.</p>
<pre><code class="language-bash">$curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/date-busybox-pod.yaml
# 10초 마다 /home/pod-out.txt 에 로그를 남기는 파드 생성
$cat date-busybox-pod.yaml | yh
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  terminationGracePeriodSeconds: 3
  containers:
  - name: busybox
    image: busybox
    command:
    - &quot;/bin/sh&quot;
    - &quot;-c&quot;
    - &quot;while true; do date &gt;&gt; /home/pod-out.txt; cd /home; sync; sync; sleep 10; done&quot;

$kubectl apply -f date-busybox-pod.yaml
pod/busybox created
# 로그 확인 
$kubectl get pod
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          11s
kubectl exec busybox -- tail -f /home/pod-out.txt
Sun May  7 15:26:19 UTC 2023
Sun May  7 15:26:29 UTC 2023
^C
$kubectl delete pod busybox
pod &quot;busybox&quot; deleted
$kubectl apply -f date-busybox-pod.yaml
pod/busybox created
# 삭제 후 실행하니, 기존의 로그는 사라진 것을 확인할 수 있음 -&gt; 파드와 데이터의 생명주기가 같음을 확인
$kubectl exec busybox -- tail -f /home/pod-out.txt
Sun May  7 15:26:45 UTC 2023
Sun May  7 15:26:55 UTC 2023
Sun May  7 15:27:05 UTC 2023
Sun May  7 15:27:15 UTC 2023

$kubectl delete pod busybox
pod &quot;busybox&quot; deleted</code></pre>
<h3 id="local">Local</h3>
<p>HostPath는 Kubernetes 노드의 호스트 파일 시스템에 볼륨을 프로비저닝하는 방법입니다. 이것은 가장 간단한 방법이지만 제한적입니다. hostPath 볼륨은 단일 노드에서만 사용할 수 있으며 노드가 중단되면 데이터가 손실됩니다.
Local Path는 노드의 로컬 파일 시스템에 볼륨을 프로비저닝하는 방법입니다. HostPath와 유사하지만 더 유연합니다. 프로비저닝 볼륨은 여러 노드에서 사용할 수 있으며 노드가 중단되어도 데이터가 손실되지 않습니다. local-path에 대한 자세한 내용은 <a href="https://github.com/rancher/local-path-provisioner">GitHub</a>에서 확인할 수 있습니다.
아래는 Local-Path 방식을 이용하는 PV,PVC에 대한 실습입니다. 
local path 관련 백업은 한승호님이 Velero <a href="https://hanhorang31.github.io/post/pkos2-2-localstorage/">blog</a>에 올려주셨다.</p>
<pre><code class="language-bash"># strage class 다운로드
$curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml

# 배포
$kubectl apply -f local-path-storage.yaml
namespace/local-path-storage created
serviceaccount/local-path-provisioner-service-account created
clusterrole.rbac.authorization.k8s.io/local-path-provisioner-role created
clusterrolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
deployment.apps/local-path-provisioner created
storageclass.storage.k8s.io/local-path created
configmap/local-path-config created

$kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path      rancher.io/local-path   Delete          WaitForFirstConsumer   false                  25s
</code></pre>
<p>Local Path를 통한 동적 프로비저닝에 대한 실습내용입니다.</p>
<pre><code class="language-bash">$cat localpath1.yaml | yh
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: localpath-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: &quot;local-path&quot;

#PVC 생성
$kubectl apply -f localpath1.yaml
persistentvolumeclaim/localpath-claim created
$kubectl get pvc
NAME              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
localpath-claim   Pending                                      local-path     4s

# [data -u]로그를 남기는 파드 생성
$kubectl apply -f localpath2.yaml
pod/app created

# 자동으로 PV가 생성되는 모습
$kubectl get pod,pv,pvc
NAME      READY   STATUS              RESTARTS   AGE
pod/app   0/1     ContainerCreating   0          6s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS   REASON   AGE
persistentvolume/pvc-88293146-ce9b-4dd0-b473-1ceb489301f3   1Gi        RWO            Delete           Bound    default/localpath-claim   local-path              2s

NAME                                    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/localpath-claim   Bound    pvc-88293146-ce9b-4dd0-b473-1ceb489301f3   1Gi        RWO            local-path     48s</code></pre>
<p>위의 내용을 통해 동적프로비저닝을 확인했습니다. 현재와 같은 방식은 노드의 볼륨을 사용하기에, PV는 파드가 존재하는 노드에 생성됩니다. </p>
<pre><code class="language-bash">$kubectl describe pv    # Node Affinity 확인 -&gt; 자신의 워커노드에서만 생성하도록
Name:              pvc-88293146-ce9b-4dd0-b473-1ceb489301f3
...
Node Affinity:
  Required Terms:
    Term 0:        kubernetes.io/hostname in [ip-192-168-2-116.ap-northeast-2.compute.internal]
...

# 파드가 동작중인 노드 확인
$k get po -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE                                               NOMINATED NODE   READINESS GATES
app    1/1     Running   0          42s   192.168.2.185   ip-192-168-2-116.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;

# 접속해서 파드가 남긴 로그 확인
ssh ec2-user@$N2 tree /opt/local-path-provisioner
/opt/local-path-provisioner
└── pvc-88293146-ce9b-4dd0-b473-1ceb489301f3_default_localpath-claim
    └── out.txt

ssh ec2-user@$N2 tail -f /opt/local-path-provisioner/pvc-88293146-ce9b-4dd0-b473-1ceb489301f3_default_localpath-claim/out.txt
Sun May 7 15:30:47 UTC 2023
Sun May 7 15:30:52 UTC 2023
Sun May 7 15:30:57 UTC 2023
Sun May 7 15:31:02 UTC 2023
Sun May 7 15:31:07 UTC 2023
Sun May 7 15:31:12 UTC 2023

# 파드 삭제 
$kubectl delete pod app
pod &quot;app&quot; deleted

# 파드가 삭제되어도 데이터는 유지됨
$ssh ec2-user@$N2 tree /opt/local-path-provisioner
/opt/local-path-provisioner
└── pvc-88293146-ce9b-4dd0-b473-1ceb489301f3_default_localpath-claim
    └── out.txt

1 directory, 1 file

# 파드를 재생성하여 데이터 보존을 한번 더 확인
$kubectl apply -f localpath2.yaml
pod/app created
$kubectl exec -it app -- head /data/out.txt
...
Sun May 7 15:30:02 UTC 2023
Sun May 7 15:30:07 UTC 2023

# 이전과는 다르게 로그가 그대로 유지됨
# 중간에 시간이 짤리는 것은 파드가 삭제 후 재시작하는 사이 시간
$kubectl exec -it app -- tail -f /data/out.txt
..
Sun May 7 15:31:27 UTC 2023
Sun May 7 15:31:32 UTC 2023
Sun May 7 15:31:37 UTC 2023
**Sun May 7 15:31:42 UTC 2023
Sun May 7 15:32:01 UTC 2023**

# 자원을 모두 제거
$kubectl delete pod app
pod &quot;app&quot; deleted

$kubectl delete pvc localpath-claim
persistentvolumeclaim &quot;localpath-claim&quot; deleted

# pv 삭제 후, 관련 볼륨이 사라진 것을 확인할 수 있음.
$ssh ec2-user@$N2 tree /opt/local-path-provisioner
/opt/local-path-provisioner

0 directories, 0 files</code></pre>
<h3 id="aws-ebs-controller">AWS EBS Controller</h3>
<p>EBS-CSI를 사용하기 위한 절차입니다.
EBS는 동적 프로비저닝이 가능하기에, PVC 와 파드를 생성하면 자동적으로 PV가 할당됩니다.</p>
<ol>
<li><p>ISRA 설정 : AWS관리형 정책 AmazonEBSCSIDriverPolicy 사용</p>
</li>
<li><p>add-on 추가: <code>eksctl</code> 이용</p>
<p> <code>eksctl create addon --name aws-ebs-csi-driver --cluster ${CLUSTER_NAME} --service-account-role-arn arn:aws:iam::${ACCOUNT_ID}:role/AmazonEKS_EBS_CSI_DriverRole --force</code></p>
</li>
</ol>
<p><strong>ebs-csi-driver 설정과정</strong></p>
<pre><code class="language-bash"># ISRA 설정 : AWS관리형 정책 AmazonEBSCSIDriverPolicy 사용
$eksctl create iamserviceaccount \
&gt;   --name ebs-csi-controller-sa \
&gt;   --namespace kube-system \
&gt;   --cluster ${CLUSTER_NAME} \
&gt;   --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
&gt;   --approve \
&gt;   --role-only \
&gt;   --role-name AmazonEKS_EBS_CSI_DriverRole
2023-05-11 09:37:52 [ℹ]  building iamserviceaccount stack &quot;eksctl-myeks-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa&quot;
2023-05-11 09:37:53 [ℹ]  deploying stack &quot;eksctl-myeks-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa&quot;

$eksctl get iamserviceaccount --cluster myeks
NAMESPACE    NAME                ROLE ARN
kube-system    aws-load-balancer-controller    arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-RK7A5DTQNNSW
kube-system    ebs-csi-controller-sa        arn:aws:iam::871103481195:role/AmazonEKS_EBS_CSI_DriverRole

# eksctl을 이용해서 ebs-csi-driver 를 설치
$eksctl create addon --name aws-ebs-csi-driver --cluster ${CLUSTER_NAME} --service-account-role-arn arn:aws:iam::${ACCOUNT_ID}:role/AmazonEKS_EBS_CSI_DriverRole --force
2023-05-11 09:41:40 [ℹ]  Kubernetes version &quot;1.24&quot; in use by cluster &quot;myeks&quot;
2023-05-11 09:41:40 [ℹ]  using provided ServiceAccountRoleARN &quot;arn:aws:iam::871103481195:role/AmazonEKS_EBS_CSI_DriverRole&quot;
2023-05-11 09:41:40 [ℹ]  creating addon

$eksctl get addon --cluster ${CLUSTER_NAME}
NAME            VERSION            STATUS        ISSUES    IAMROLE                                UPDATE AVAILABLE    CONFIGURATION VALUES
aws-ebs-csi-driver    v1.18.0-eksbuild.1    CREATING    0    arn:aws:iam::871103481195:role/AmazonEKS_EBS_CSI_DriverRole

$k get sa -n kube-system | grep ebs
ebs-csi-controller-sa                0         33s
ebs-csi-node-sa                      0         33s

# 스토리지 클래스 배포
cat &lt;&lt;EOT &gt; gp3-sc.yaml
&gt; kind: StorageClass
&gt; apiVersion: storage.k8s.io/v1
&gt; metadata:
&gt;   name: gp3
&gt; allowVolumeExpansion: true # 자동 볼륨 확장
&gt; provisioner: ebs.csi.aws.com
&gt; volumeBindingMode: WaitForFirstConsumer
&gt; parameters:
&gt;   type: gp3
&gt;   allowAutoIOPSPerGBIncrease: &#39;true&#39;
&gt;   encrypted: &#39;true&#39;
&gt; EOT

$kubectl apply -f gp3-sc.yaml
storageclass.storage.k8s.io/gp3 created

$kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  27m
gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   5s

$kubectl describe sc gp3 | grep Parameters
Parameters:            allowAutoIOPSPerGBIncrease=true,encrypted=true,type=gp3
$kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  28m
gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true       
</code></pre>
<p><strong>PV와 PVC, 파드를 생성하여 동적 프로비저닝을 확인하는 과정입니다.</strong></p>
<pre><code class="language-bash">$cat &lt;&lt;EOT &gt; awsebs-pvc.yaml
&gt; apiVersion: v1
&gt; kind: PersistentVolumeClaim
&gt; metadata:
&gt;   name: ebs-claim
&gt; spec:
&gt;   accessModes:
&gt;     - ReadWriteOnce #### EBS는 여러 노드와 동시에 연결이 가능하기에, Once로 세팅
&gt;   resources:
&gt;     requests:
&gt;       storage: 4Gi
&gt;   storageClassName: gp3
&gt; EOT

$kubectl apply -f awsebs-pvc.yaml
persistentvolumeclaim/ebs-claim created

$kubectl get pvc,pv
NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/ebs-claim   Pending                                      gp3            2s
$cat &lt;&lt;EOT &gt; awsebs-pod.yaml
&gt; apiVersion: v1
&gt; kind: Pod
&gt; metadata:
&gt;   name: app
&gt; spec:
&gt;   terminationGracePeriodSeconds: 3
&gt;   containers:
&gt;   - name: app
&gt;     image: centos
&gt;     command: [&quot;/bin/sh&quot;] 
&gt;     args: [&quot;-c&quot;, &quot;while true; do echo \$(date -u) &gt;&gt; /data/out.txt; sleep 5; done&quot;]
&gt;     volumeMounts:
&gt;     - name: persistent-storage
&gt;       mountPath: /data
&gt;   volumes:
&gt;   - name: persistent-storage
&gt;     persistentVolumeClaim:
&gt;       claimName: ebs-claim
&gt; EOT
$kubectl apply -f awsebs-pod.yaml
pod/app created
$kubectl get pvc,pv,pod
NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/ebs-claim   Bound    pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7   4Gi        RWO            gp3            34s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
persistentvolume/pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7   4Gi        RWO            Delete           Bound    default/ebs-claim   gp3                     12s

NAME      READY   STATUS              RESTARTS   AGE
pod/app   0/1     ContainerCreating   0          16s

# 추가된 EBS Volume(PV) 확인
$kubectl get VolumeAttachment
NAME                                                                   ATTACHER          PV                                         NODE                                              ATTACHED   AGE
csi-58bd6537e51d9961539e7d5eda35c6d08ab98b890c83e527325b84f7d562eba3   ebs.csi.aws.com   pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7   ip-192-168-2-93.ap-northeast-2.compute.internal   true       14s

# 추가된 EBS Volume(PV) 확인
$aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath=&quot;{.items[0].spec.csi.volumeHandle}&quot;) | jq

{
  &quot;Volumes&quot;: [
    {
      &quot;Attachments&quot;: [
        {
          &quot;AttachTime&quot;: &quot;2023-05-11T00:48:04+00:00&quot;,
          &quot;Device&quot;: &quot;/dev/xvdaa&quot;,
          &quot;InstanceId&quot;: &quot;i-0039e023911746fbf&quot;,
          &quot;State&quot;: &quot;attached&quot;,
          &quot;VolumeId&quot;: &quot;vol-02735906e67899e1b&quot;,
          &quot;DeleteOnTermination&quot;: false
        }
      ],
      &quot;AvailabilityZone&quot;: &quot;ap-northeast-2b&quot;,
      &quot;CreateTime&quot;: &quot;2023-05-11T00:48:00.023000+00:00&quot;,
      &quot;Encrypted&quot;: true,
      &quot;KmsKeyId&quot;: &quot;arn:aws:kms:ap-northeast-2:871103481195:key/e92fb931-6368-48c5-9896-43acdaa5f663&quot;,
      &quot;Size&quot;: 4,
      &quot;SnapshotId&quot;: &quot;&quot;,
      &quot;State&quot;: &quot;in-use&quot;,
      &quot;VolumeId&quot;: &quot;vol-02735906e67899e1b&quot;,
      &quot;Iops&quot;: 3000,
      &quot;Tags&quot;: [
        {
          &quot;Key&quot;: &quot;kubernetes.io/created-for/pvc/namespace&quot;,
          &quot;Value&quot;: &quot;default&quot;
        },
        {
          &quot;Key&quot;: &quot;kubernetes.io/created-for/pvc/name&quot;,
          &quot;Value&quot;: &quot;ebs-claim&quot;
        },
        {
          &quot;Key&quot;: &quot;Name&quot;,
          &quot;Value&quot;: &quot;myeks-dynamic-pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7&quot;
        },
        {
          &quot;Key&quot;: &quot;KubernetesCluster&quot;,
          &quot;Value&quot;: &quot;myeks&quot;
        },
        {
          &quot;Key&quot;: &quot;CSIVolumeName&quot;,
          &quot;Value&quot;: &quot;pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7&quot;
        },
        {
          &quot;Key&quot;: &quot;kubernetes.io/cluster/myeks&quot;,
          &quot;Value&quot;: &quot;owned&quot;
        },
        {
          &quot;Key&quot;: &quot;kubernetes.io/created-for/pv/name&quot;,
          &quot;Value&quot;: &quot;pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7&quot;
        },
        {
          &quot;Key&quot;: &quot;ebs.csi.aws.com/cluster&quot;,
          &quot;Value&quot;: &quot;true&quot;
        }
      ],
      &quot;VolumeType&quot;: &quot;gp3&quot;,
      &quot;MultiAttachEnabled&quot;: false,
      &quot;Throughput&quot;: 125
    }
  ]
}</code></pre>
<p>PersistentVolumeClaim을 생성할 때, AWS EBS는 다중연결이 가능하기에 access모드를 ReadWriteOnce로 설정해야 합니다. EBS의 다중 연결은 GlusterFS와 같은 공유 볼륨을 사용하는 경우와 유사합니다. </p>
<blockquote>
<p><code>EBS의 다중 연결을 사용하면 특정 볼륨을 여러 대의 EC2 인스턴스에 연결할 수 있습니다. 각 EBS 볼륨은 Multi-Attach 설정시 해당 볼륨이 위치한 가용영역에서 최대 16개의 EC2에 연결할 수 있게 됩니다.</code></p>
</blockquote>
<p>또한, EBS는 같은 AZ에 있어야 접근이 가능하므로 nodeaffinity를 통해 같은 AZ로 PV를 생성합니다.</p>
<p><strong>아래는 EBS 볼륨을 확장하는 실습입니다.</strong></p>
<pre><code class="language-bash">$kubectl exec app -- tail -f /data/out.txt
Thu May 11 00:48:40 UTC 2023
Thu May 11 00:48:45 UTC 2023
Thu May 11 00:48:50 UTC 2023
Thu May 11 00:48:55 UTC 2023
Thu May 11 00:49:00 UTC 2023
Thu May 11 00:49:05 UTC 2023
Thu May 11 00:49:10 UTC 2023
Thu May 11 00:49:15 UTC 2023
Thu May 11 00:49:20 UTC 2023
Thu May 11 00:49:25 UTC 2023
Thu May 11 00:49:30 UTC 2023

$kubectl df-pv
 PV NAME                                   PVC NAME   NAMESPACE  NODE NAME                                        POD NAME  VOLUME MOUNT NAME   SIZE  USED  AVAILABLE  %USED  IUSED  IFREE   %IUSED
 pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7  ebs-claim  default    ip-192-168-2-93.ap-northeast-2.compute.internal  app       persistent-storage  3Gi   28Ki  3Gi        0.00   12     262132  0.00

$kubectl exec -it app -- sh -c &#39;df -hT --type=overlay&#39;
Filesystem     Type     Size  Used Avail Use% Mounted on
overlay        overlay   30G  3.6G   27G  12% /

# ebs volume 확장
$kubectl patch pvc ebs-claim -p &#39;{&quot;spec&quot;:{&quot;resources&quot;:{&quot;requests&quot;:{&quot;storage&quot;:&quot;10Gi&quot;}}}}&#39;
persistentvolumeclaim/ebs-claim patched

# 자동으로 확장되는 모습
$kubectl exec -it app -- sh -c &#39;df -hT --type=ext4&#39;
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme1n1   ext4  9.8G   28K  9.7G   1% /data
$kubectl delete pod app &amp; kubectl delete pvc ebs-claim
[1] 7288
pod &quot;app&quot; deleted
persistentvolumeclaim &quot;ebs-claim&quot; deleted
[1]+  Done                    kubectl delete pod app</code></pre>
<ul>
<li>EBS controller 에 의해 하나의 PV가 생성되고, 용량이 바뀐 모습
<img src="https://velog.velcdn.com/images/han-0315/post/1daf8c29-fa8c-4d78-bf3e-c6b33dbbd6b7/image.png" alt=""></li>
</ul>
<h3 id="aws-volume-snapshots-controller">AWS Volume SnapShots Controller</h3>
<p>아래는 위와 동일하게, PVC, PV를 생성해서 파드와 연결합니다. 파드는  <code>data -u</code> 로그를 남기며 강제로 파드와 PVC를 삭제한 후,  snapshots을 통해 복구하는 실습을 진행합니다.</p>
<p><strong>snapshot 생성</strong></p>
<pre><code class="language-bash">$kubectl apply -f ebs-volume-snapshot.yaml
volumesnapshot.snapshot.storage.k8s.io/ebs-volume-snapshot created

$kubectl get volumesnapshot
NAME                  READYTOUSE   SOURCEPVC   SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS   SNAPSHOTCONTENT                                    CREATIONTIME   AGE
ebs-volume-snapshot   false        ebs-claim                           4Gi           csi-aws-vsc     snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad   3s             3s

#아래에 나와있는 snapshot id로 확인하면, 모두 잘 설정되어있다.
$kubectl get volumesnapshot ebs-volume-snapshot -o jsonpath={.status.boundVolumeSnapshotContentName} ; echo
snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad

$kubectl describe volumesnapshot.snapshot.storage.k8s.io ebs-volume-snapshot
Name:         ebs-volume-snapshot
Namespace:    default
...
Metadata:
...
    Time:            2023-05-13T05:58:36Z
  Resource Version:  11187
  UID:               ff3a9b11-d8a5-4806-80eb-9c062c4062ad
Spec:
  Source:
    Persistent Volume Claim Name:  ebs-claim
  Volume Snapshot Class Name:      csi-aws-vsc
Status:
  Bound Volume Snapshot Content Name:  snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad
  Creation Time:                       2023-05-13T05:58:35Z
  Ready To Use:                        false
  Restore Size:                        4Gi
Events:
  Type    Reason            Age   From                 Message
  ----    ------            ----  ----                 -------
  Normal  CreatingSnapshot  19s   snapshot-controller  Waiting for a snapshot default/ebs-volume-snapshot to be created by the CSI driver.
  Normal  SnapshotCreated   18s   snapshot-controller  Snapshot default/ebs-volume-snapshot was successfully created by the CSI driver.

###
# 블로그에서는 중복된 조회 제거
$kubectl get volumesnapshotcontents
NAME                                               READYTOUSE   RESTORESIZE   DELETIONPOLICY   DRIVER            VOLUMESNAPSHOTCLASS   VOLUMESNAPSHOT        VOLUMESNAPSHOTNAMESPACE   AGE
snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad   false        4294967296    Delete           ebs.csi.aws.com   csi-aws-vsc           ebs-volume-snapshot   default                   38s

# 파드와 pvc를 제거하여, pv가 제거된 모습
$kubectl delete pod app &amp;&amp; kubectl delete pvc ebs-claim
pod &quot;app&quot; deleted
persistentvolumeclaim &quot;ebs-claim&quot; deleted

$kubectl get pvc,pv
No resources found

$k get volumesnapshot
NAME                  READYTOUSE   SOURCEPVC   SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS   SNAPSHOTCONTENT                                    CREATIONTIME   AGE
ebs-volume-snapshot   true         ebs-claim                           4Gi           csi-aws-vsc     snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad   7m13s          7m13s

$cat &lt;&lt;EOT &gt; ebs-snapshot-restored-claim.yaml
&gt; apiVersion: v1
&gt; kind: PersistentVolumeClaim
&gt; metadata:
&gt;   name: ebs-snapshot-restored-claim
&gt; spec:
&gt;   storageClassName: gp3
&gt;   accessModes:
&gt;     - ReadWriteOnce
&gt;   resources:
&gt;     requests:
&gt;       storage: 4Gi
&gt;   dataSource:
&gt;     name: ebs-volume-snapshot
&gt;     kind: VolumeSnapshot
&gt;     apiGroup: snapshot.storage.k8s.io
&gt; EOT

$kubectl apply -f ebs-snapshot-restored-claim.yaml
persistentvolumeclaim/ebs-snapshot-restored-claim created

$kubectl get pvc,pv
NAME                                                STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/ebs-snapshot-restored-claim   Pending                                      gp3            3s

## 블로그에서는 curl 또한 제거
$curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ebs-snapshot-restored-pod.yaml
$cat ebs-snapshot-restored-pod.yaml | yh
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: centos
    command: [&quot;/bin/sh&quot;]
    args: [&quot;-c&quot;, &quot;while true; do echo $(date -u) &gt;&gt; /data/out.txt; sleep 5; done&quot;]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-snapshot-restored-claim

$kubectl apply -f ebs-snapshot-restored-pod.yaml
pod/app created

$k get po
NAME   READY   STATUS    RESTARTS   AGE
app    1/1     Running   0          13s

#로그가 이어지는 것을 확인할 수 있다.
$k exec app -- cat /data/out.txt
...
Sat May 13 05:58:23 UTC 2023
Sat May 13 05:58:28 UTC 2023
Sat May 13 06:07:12 UTC 2023
Sat May 13 06:07:17 UTC 2023

$kubectl delete pod app &amp;&amp; kubectl delete pvc ebs-snapshot-restored-claim &amp;&amp; kubectl delete volumesnapshots ebs-volume-snapshot
pod &quot;app&quot; deleted
persistentvolumeclaim &quot;ebs-snapshot-restored-claim&quot; deleted
volumesnapshot.snapshot.storage.k8s.io &quot;ebs-volume-snapshot&quot; deleted</code></pre>
<p>아래는 스냅샷을 AWS 콘솔을 통해 확인한 모습이다. 
<img src="https://velog.velcdn.com/images/han-0315/post/e36acb87-bd1a-4df7-843e-b1533dc07607/image.png" alt=""></p>
<h3 id="aws-efs-controller">AWS EFS Controller</h3>
<p>먼저 EFS(<a href="https://aws.amazon.com/efs/">Amazon Elastic File System</a>)는 그림과 같이 여러 가용영역과 호환가능한 파일시스템입니다.</p>
<p>아래는 EFS를 통해 PV,PVC를 생성하는 실습내용입니다.</p>
<p>먼저 작업용 EC2에도 efs mount를 진행합니다.</p>
<pre><code class="language-bash">$mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-0b2bc433bc72b5727.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs
$df -hT --type nfs4
Filesystem                                              Type  Size  Used Avail Use% Mounted on
fs-0b2bc433bc72b5727.efs.ap-northeast-2.amazonaws.com:/ nfs4  8.0E     0  8.0E   0% /mnt/myefs</code></pre>
<p><strong>EFS-CSI 설치과정</strong></p>
<pre><code class="language-bash"># 정보 확인
$aws efs describe-file-systems --query &quot;FileSystems[*].FileSystemId&quot; --output textfs-0f70f92a10cc5af1a
# json 다운로드
$curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json

$aws iam create-policy --policy-name AmazonEKS_EFS_CSI_Driver_Policy --policy-document file://iam-policy-example.json
{
    &quot;Policy&quot;: {
        &quot;PolicyName&quot;: &quot;AmazonEKS_EFS_CSI_Driver_Policy&quot;,
        &quot;PolicyId&quot;: &quot;ANPA4VUOQIVV6AGY3ABHQ&quot;,
        &quot;Arn&quot;: &quot;arn:aws:iam::871103481195:policy/AmazonEKS_EFS_CSI_Driver_Policy&quot;,
        &quot;Path&quot;: &quot;/&quot;,
        &quot;DefaultVersionId&quot;: &quot;v1&quot;,
        &quot;AttachmentCount&quot;: 0,
        &quot;PermissionsBoundaryUsageCount&quot;: 0,
        &quot;IsAttachable&quot;: true,
        &quot;CreateDate&quot;: &quot;2023-05-13T06:21:41+00:00&quot;,
        &quot;UpdateDate&quot;: &quot;2023-05-13T06:21:41+00:00&quot;
    }
}
# efs-csi 생성
$eksctl create iamserviceaccount \
&gt;   --name efs-csi-controller-sa \
&gt;   --namespace kube-system \
&gt;   --cluster ${CLUSTER_NAME} \
&gt;   --attach-policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AmazonEKS_EFS_CSI_Driver_Policy \
&gt;   --approve
2023-05-13 15:22:26 [ℹ]  created serviceaccount &quot;kube-system/efs-csi-controller-sa&quot;

## 불필요
$kubectl get sa -n kube-system efs-csi-controller-sa -o yaml | head -5
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-183ANMNPO7E68

$eksctl get iamserviceaccount --cluster myeks
NAMESPACE    NAME                ROLE ARN
kube-system    aws-load-balancer-controller    arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-1OWZIVWLLX9JM
kube-system    ebs-csi-controller-sa        arn:aws:iam::871103481195:role/AmazonEKS_EBS_CSI_DriverRole
kube-system    efs-csi-controller-sa        arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-183ANMNPO7E68

#efs 구성요소를 helm을 통해 배포
$helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
&quot;aws-efs-csi-driver&quot; has been added to your repositories
$helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the &quot;aws-efs-csi-driver&quot; chart repository
Update Complete. ⎈Happy Helming!⎈
$helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
&gt;     --namespace kube-system \
&gt;     --set image.repository=602401143452.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/eks/aws-efs-csi-driver \
&gt;     --set controller.serviceAccount.create=false \
&gt;     --set controller.serviceAccount.name=efs-csi-controller-sa
Release &quot;aws-efs-csi-driver&quot; does not exist. Installing it now.
NAME: aws-efs-csi-driver
LAST DEPLOYED: Sat May 13 16:18:05 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
To verify that aws-efs-csi-driver has started, run:

$helm list -n kube-system
NAME                  NAMESPACE      REVISION    UPDATED                                    STATUS      CHART                       APP VERSION
aws-efs-csi-driver    kube-system    1           2023-05-13 16:18:05.885009102 +0900 KST    deployed    aws-efs-csi-driver-2.4.3    1.5.5

$kubectl get pod -n kube-system -l &quot;app.kubernetes.io/name=aws-efs-csi-driver,app.kubernetes.io/instance=aws-efs-csi-driver&quot;
NAME                                  READY   STATUS              RESTARTS   AGE
efs-csi-controller-6f64dcc5dc-6hbkc   0/3     ContainerCreating   0          9s
efs-csi-controller-6f64dcc5dc-wf4ks   0/3     ContainerCreating   0          9s
efs-csi-node-qs7sr                    0/3     ContainerCreating   0          9s
efs-csi-node-xtvvm                    0/3     ContainerCreating   0          9s
efs-csi-node-zvzcs                    0/3     ContainerCreating   0          9s</code></pre>
<p>이제 K8S에서 제공해준 샘플 코드를 다운받아, 실습을 진행합니다.</p>
<pre><code class="language-bash">$git clone https://github.com/kubernetes-sigs/aws-efs- qq-driver.git /root/efs-csiCloning into &#39;/root/efs-csi&#39;...
remote: Enumerating objects: 16052, done.
remote: Counting objects: 100% (269/269), done.
remote: Compressing objects: 100% (185/185), done.
remote: Total 16052 (delta 65), reused 236 (delta 57), pack-reused 15783
Receiving objects: 100% (16052/16052), 16.90 MiB | 20.51 MiB/s, done.
Resolving deltas: 100% (7777/7777), done.

$cd /root/efs-csi/examples/kubernetes/multiple_pods/specs &amp;&amp; tree
.
├── claim.yaml
├── pod1.yaml
├── pod2.yaml
├── pv.yaml
└── storageclass.yaml
0 directories, 5 files

$cat storageclass.yaml | yh
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com

$kubectl apply -f storageclass.yaml
storageclass.storage.k8s.io/efs-sc created

$k get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
efs-sc          efs.csi.aws.com         Delete          Immediate              false                  10s
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  139m
gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   109m

# 현재 EFS ID를 지정하는 모습
$EfsFsId=$(aws efs describe-file-systems --query &quot;FileSystems[*].FileSystemId&quot; --output text)
$sed -i &quot;s/fs-4af69aab/$EfsFsId/g&quot; pv.yaml
$echo $EfsFsId
fs-0f70f92a10cc5af1a

$cat pv.yaml | yh
apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: efs-sc
  csi:
    driver: efs.csi.aws.com
    volumeHandle: fs-0f70f92a10cc5af1a
# 정적 프로비저닝이라 pv를 먼저 생성하는 모습
$kubectl apply -f pv.yaml
persistentvolume/efs-pv created

$cat claim.yaml | yh
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi

$kubectl apply -f claim.yaml
persistentvolumeclaim/efs-claim created

$k get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
efs-pv   5Gi        RWX            Retain           Bound    default/efs-claim   efs-sc                  57s

# 이전과 유사한 data -u 로그를 찍는 파드를 2개 생성
$kubectl apply -f pod1.yaml,pod2.yaml
pod/app1 created
pod/app2 created

$k get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS        CLAIM               STORAGECLASS   REASON   AGE
efs-pv   5Gi        RWX            Retain           Terminating   default/efs-claim   efs-sc                  3m32s

$cat pod1.yaml pod2.yaml | yh
apiVersion: v1
kind: Pod
metadata:
  name: app1
...
    args: [&quot;-c&quot;, &quot;while true; do echo $(date -u) &gt;&gt; /data/out1.txt; sleep 5; done&quot;]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: efs-claim

apiVersion: v1
kind: Pod
metadata:
  name: app2
..
    args: [&quot;-c&quot;, &quot;while true; do echo $(date -u) &gt;&gt; /data/out2.txt; sleep 5; done&quot;]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: efs-claim

$k exec -it app1 -- sh -c &quot;df -hT -t nfs4&quot;
Filesystem           Type            Size      Used Available Use% Mounted on
127.0.0.1:/          nfs4            8.0E         0      8.0E   0% /data
$k exec -it app1 -- cat /data/out1.txt
...
Sat May 13 07:25:50 UTC 2023
Sat May 13 07:25:55 UTC 2023

# 작업용 EC2에도 공유되는 모습.
$tree /mnt/myefs/
/mnt/myefs/
├── out1.txt
└── out2.txt

0 directories, 2 files

$kubectl delete pvc efs-claim &amp;&amp; kubectl delete pv efs-pv &amp;&amp; kubectl delete sc efs-sc
persistentvolumeclaim &quot;efs-claim&quot; deleted
persistentvolume &quot;efs-pv&quot; deleted
storageclass.storage.k8s.io &quot;efs-sc&quot; deleted</code></pre>
<p>아래는 EFS의 네트워크 모습입니다. 현재 노드가 있는 3개의 가용영역이 모두 있는 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/han-0315/post/cba5e06e-1ef5-4d6c-8ea9-86861b47675d/image.png" alt=""></p>
<p>위에서는 정적 프로비저닝으로 테스트를 진행했지만, EFS에는 동적 프로비저닝 기능이 <code>1.2</code> 버전부터 지원하여 동적 프로비저닝도 진행해봤다. 위의 내용에서 동적프로비저닝을 테스트하면 다음과 같이 자동으로 PV가 할당되지 않아 PVC와 파드가 생성되지 못한다.</p>
<pre><code class="language-bash"># 모니터링 
Every 2.0s: kubectl get sc efs-sc; echo; kubectl get pv,pvc,pod                                            Sat May 13 16:30:46 2023

NAME     PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
efs-sc   efs.csi.aws.com   Delete          Immediate           false                  10m

NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/efs-claim   Pending                                      efs-sc         42s

NAME       READY   STATUS    RESTARTS   AGE
pod/app1   0/1     Pending   0          33s
pod/app2   0/1     Pending   0          33s</code></pre>
<p>파드의 로그</p>
<pre><code class="language-bash">Events:
  Type     Reason            Age                 From               Message
  ----     ------            ----                ----               -------
  Warning  FailedScheduling  15s (x4 over 100s)  default-scheduler  0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
  Normal   Scheduled         7s                  default-scheduler  Successfully assigned default/app1 to ip-192-168-2-52.ap-northeast-2.compute.internal</code></pre>
<p>PVC 로그</p>
<pre><code class="language-bash">Events:
  Type     Reason                Age                  From                                                                                                   Message
  ----     ------                ----                 ----                                                                                                   -------
  Normal   Provisioning          96s (x7 over 2m39s)  efs.csi.aws.com_ip-192-168-3-190.ap-northeast-2.compute.internal_d0fa0b24-c916-44ba-befc-922b6eb49470  External provisioner is provisioning volume for claim &quot;default/efs-claim&quot;
  Warning  ProvisioningFailed    96s (x7 over 2m39s)  efs.csi.aws.com_ip-192-168-3-190.ap-northeast-2.compute.internal_d0fa0b24-c916-44ba-befc-922b6eb49470  failed to provision volume with StorageClass &quot;efs-sc&quot;: rpc error: code = InvalidArgument desc = Missing provisioningMode parameter
  Normal   Provisioning          96s (x7 over 2m39s)  efs.csi.aws.com_ip-192-168-1-203.ap-northeast-2.compute.internal_74db5f2e-b1e1-4f54-b9b2-cede890dddf7  External provisioner is provisioning volume for claim &quot;default/efs-claim&quot;
  Warning  ProvisioningFailed    96s (x7 over 2m39s)  efs.csi.aws.com_ip-192-168-1-203.ap-northeast-2.compute.internal_74db5f2e-b1e1-4f54-b9b2-cede890dddf7  failed to provision volume with StorageClass &quot;efs-sc&quot;: rpc error: code = InvalidArgument desc = Missing provisioningMode parameter
  Normal   ExternalProvisioning  80s (x8 over 2m39s)  persistentvolume-controller                                                                            waiting for a volume to be created, either by external provisioner &quot;efs.csi.aws.com&quot; or manually created by system administrator</code></pre>
<h4 id="efs-동적-프로비저닝">EFS 동적 프로비저닝</h4>
<p>EFS에 대한 동적프로비저닝에 대한 설명은 <a href="https://aws.amazon.com/blogs/containers/introducing-efs-csi-dynamic-provisioning/">AWS Blog</a>과 <a href="https://github.com/kubernetes-sigs/aws-efs-csi-driver/blob/master/examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml">GitHub</a>에서 자세하게 확인할 수 있습니다.</p>
<p>다른 과정은 앞에서와 똑같고, 스토리지 클래스와 파드만 달라진다.</p>
<pre><code class="language-bash">cat storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  fileSystemId: fs-0b2bc433bc72b5727 # 자신의 EFS ID
  directoryPerms: &quot;700&quot;
cat pod.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: efs-app
spec:
  containers:
    - name: app
      image: centos
      command: [&quot;/bin/sh&quot;]
      args: [&quot;-c&quot;, &quot;while true; do echo $(date -u) &gt;&gt; /data/out; sleep 5; done&quot;]
      volumeMounts:
        - name: persistent-storage
          mountPath: /data
  volumes:
    - name: persistent-storage
      persistentVolumeClaim:
        claimName: efs-claim</code></pre>
<p>이유를 모르겠으나, 동적프로비저닝은 오류가 발생했다. 문서 그대로 진행하고, 오류를 찾아봤으나 아직 해결하지 못했다.</p>
<pre><code class="language-bash">Every 2.0s: kubectl get sc efs-sc; echo; kubectl get pv,pvc,pod                                            Sat May 13 22:45:27 2023

NAME     PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
efs-sc   efs.csi.aws.com   Delete          Immediate           false                  7m39s

NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/efs-claim   Pending                                      efs-sc         7m18s

NAME          READY   STATUS    RESTARTS   AGE
pod/efs-app   0/1     Pending   0          7m18s</code></pre>
<p>PVC에서 발생한 로그</p>
<pre><code class="language-css">Warning  ProvisioningFailed  26s                efs.csi.aws.com_ip-192-168-3-97.ap-northeast-2.compute.internal_6f23dce4-74d1-4453-b25c-013fcb47be73  failed to provision volume with StorageClass &quot;efs-sc&quot;: rpc error: code = Internal desc = Failed to fetch File System info: Describe File System failed: WebIdentityErr: failed to retrieve credentials
caused by: AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
          status code: 403, request id: 92132222-c11b-4a7c-b546-ddf80b933c1c
  Normal  ExternalProvisioning  14s (x5 over 57s)  persistentvolume-controller  waiting for a volume to be created, either by external provisioner &quot;efs.csi.aws.com&quot; or manually created by system administrator</code></pre>
<h3 id="기타">기타</h3>
<p>추가적으로 실습을 진행하다가, 에러가 났던 부분입니다.
아래와 같이 PV 삭제할 때, terminating 단계에서 멈추며 진행이 안된 적이 있다. 관련 자료를 찾아보니 파이널라이저에 의해 삭제가 안되는 것을 알 수 있었다.</p>
<pre><code class="language-bash">k get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS        CLAIM               STORAGECLASS   REASON   AGE
efs-pv                                     5Gi        RWX            Retain           Available                         efs-sc                  3m49s
pvc-5f762d6c-aff6-4a3a-8ff4-e947563c792a   5Gi        RWX            Delete           Terminating   default/efs-claim   efs-sc                  27m</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[EKS 스터디 2주차]]></title>
            <link>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 06 May 2023 06:23:55 GMT</pubDate>
            <description><![CDATA[<h3 id="요약"><strong>요약</strong></h3>
<p>2주차의 주제는 네트워크이다. EKS에서 사용하는 CNI인 ENI의 장점과 실습을 통한 원리를 중점적으로 파악하였다. 이후 서비스, 로드밸런서, Ingress, External DNS, Istio로 이어지나 도메인을 구매하는 데 실패하여 External DNS 실습은 진행하지 못했다. (~5.8 진행예정) 화요일날 블로그 포스팅을 작성할 예정이었으나 컨디션이 안좋아서 속도가 안나와 토요일에 와서 포스팅을 했다. 한번에 진행한 실습이 아니다 보니, 자원을 재생성하는 과정에서 IP 등 여러 결과값이 앞과 뒤가 살짝 다를 수 있다. </p>
<p>이번 블로그에서 사용되는 용어를 정리해보면 다음과 같다.</p>
<ul>
<li>CNI(Container Network Interface)</li>
<li>ENI(Elastic Network Interface)
  : ec2 내부의 가상 네트워크 인터페이스, ec2에서 여러 개의 IP주소 사용가능</li>
<li>Prefix Delegation
  : 접두사 위임으로, 192.168.1.0/24와 같이 네트워크 대역을 확인하는 상위 비트 개수를 위임한다.</li>
<li>CIDR(Classless Inter-Domain Routing)
  : 기존 클래스인 A,B,C 와 같이 고정적인 방법으로 네트워크 대역을 설정하는 것이 아닌 유연하게 네트워크 대역을 설정하는 방법</li>
</ul>
<h3 id="배포">배포</h3>
<p>배포는 <a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">CloudNet</a> 팀에서 준비해주신 원클릭 CloudFormation을 통해 진행했다. 관련 자료는 <a href="https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick.yaml">YAML</a>부분을 참고하면 되고, ec2 type은 상황에 맞게 변경해주면 된다. 현재 기본값은 <code>t3.medium</code> 이나 추후 grafana 등 리소스가 많이 필요하면 타입을 상향하면 된다. 배포한 아키텍처는 다음과 같다. 가시다님이 친절하게 아래와 같이 설명까지 써주셨다.</p>
<p><code>사전 준비</code> : <strong>AWS 계정</strong>, <strong>SSH 키 페어, IAM 계정 생성 후 키</strong></p>
<p><code>전체 구성도</code> : <strong>VPC</strong> 1개(퍼블릭 서브넷 3개, 프라이빗 서브넷 3개), <strong>EKS</strong> 클러스터(Control Plane), 관리형 <strong>노드 그룹</strong>(EC2 3대), <strong>Add-on</strong>
<img src="https://velog.velcdn.com/images/han-0315/post/a7b75e1b-664a-4da3-b5de-bd221b1ead30/image.png" alt=""></p>
<p>실습이 종료된 이후 아래의 명령어를 입력하면, 모든 자원을 삭제할 수 있다. EKS는 스탑기능이 없으니, 진행한 실습이 끝났다면 바로 삭제해줘야 비용을 최소화할 수 있다.</p>
<p><strong><code>eksctl delete cluster --name $CLUSTER_NAME &amp;&amp; aws cloudformation delete-stack --stack-name $CLUSTER_NAME</code></strong></p>
<h2 id="aws-vpc-cni">AWS VPC CNI</h2>
<h3 id="eni-장점">ENI 장점</h3>
<p>파드의 ip를 할당해준다. 해당 ip 대역은 노드의 ip 대역과 같아서 직접 통신이 가능하다는 장점이 있다. 직접 통신이 가능하다면, 중간에 IP를 적절하게 변경하는 연산이 필요없다. 이는 컴퓨팅 리소스를 아낄 수 있고 성능을 향상시킨다. 
<img src="https://velog.velcdn.com/images/han-0315/post/c4913839-7c40-450b-826a-862e25a53407/image.png" alt="">
파드와 노드의 네트워크 대역이 같으면 오버레이과정을 안거쳐도 된다. 기존 패킷에 대한 업데이트 없이(연산) 그대로 전달할 수 있다.
<img src="https://velog.velcdn.com/images/han-0315/post/8c62d211-f4a8-49d0-bc28-120d6b4ecd75/image.png" alt=""> </p>
<h3 id="최대-파드-개수">최대 파드 개수</h3>
<p>Amazon CNI에 대한 자세한 내용은 <a href="https://github.com/aws/amazon-vpc-cni-k8s">GitHub</a>에서 확인할 수 있다. 문서를 확인하면 ENI의 기본주소를 노드의 IP로 설정하고, <code>--max-pods</code> 의 값을 <strong><code>the number of ENIs for the instance type × (the number of IPs per ENI - 1)) + 2</code></strong> 로 권장한다. 관련 이유를 살펴보면 네트워크의 안정성을 위해 제한을 두었다는 설명을 확인할 수 있고, 아래의 토글에서 각 인스턴스별 제한을 확인할 수 있다.</p>
<pre><code class="language-bash">#현재 배포된 EKS의 max pods의 값 확인
$k describe node ip-192-168-1-139.ap-northeast-2.compute.internal
Name:               ip-192-168-1-139.ap-northeast-2.compute.internal
...
Allocatable:
  attachable-volumes-aws-ebs:  25
  cpu:                         1930m
  ephemeral-storage:           27905944324
  hugepages-1Gi:               0
  hugepages-2Mi:               0
  memory:                      3388364Ki
  #17개임을 확인할 수 있다.
  pods:                        17
...</code></pre>
<p>현재의 인스턴스 타입은 <code>t3.medium</code> 이다. 연산은 <code>3 * (6 - 1) + 2 = 17</code> 이니 권장사항을 만족한 것을 확인할 수 있다.</p>
<h3 id="최대-파드-개수-1">최대 파드 개수</h3>
<p>Amazon ENI에 대한 자세한 내용은 <a href="https://github.com/aws/amazon-vpc-cni-k8s">GitHub</a>에서 확인할 수 있다. 문서를 확인하면 ENI의 기본주소를 노드의 IP로 설정하고, <code>--max-pods</code> 의 값을 <strong><em>the number of ENIs for the instance type × (the number of IPs per ENI - 1)) + 2</em></strong> 로 권장한다. 관련 이유를 살펴보면 네트워크의 안정성을 위해 제한을 두었다는 설명을 확인할 수 있다. 실습 코드 아래에 각 인스턴스별 제한을 확인할 수 있다. 
계산식에 대해 구체적으로 알아보면, 2개의 파드를 추가하는 것은 기본적으로 node ip 와 동일한 <code>aws-node</code> , <code>kube-proxy</code> 파드를 의미한다. 또, ENI당 사용할 수 있는 IP에서 1을 빼는 것은 컨트롤플레인과 소통하기 위한 primary private IPv4 addresses 를 파드에서 사용할 수 없기 때문이다. 또한, 30vCPU미만은 110, 이외 모든 인스턴스는 250이 최대값으로 상한선이 정해져있다.</p>
<p><strong>t3.medium 의 경우 ENI 마다 최대 6개의 IP를 가질 수 있다</strong>
    - <code>**&quot;t3.medium&quot;:     {ENILimit: 3, IPv4Limit: 6, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},**</code>
현재의 인스턴스 타입은 <code>t3.medium</code> 이다. 연산은 <code>3 * (6 - 1) + 2 = 17</code> 이니 권장사항을 만족한 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/han-0315/post/18f33573-d2a3-40a6-90cc-690afdf54f1e/image.png" alt="">
직접 클러스터에서 관련된 정보를 확인해보면 다음과 같이 연산한 값과 일치하는 것을 볼 수 있다.</p>
<pre><code class="language-bash">#현재 배포된 EKS의 max pods의 값 확인
$k describe node ip-192-168-1-139.ap-northeast-2.compute.internal
Name:               ip-192-168-1-139.ap-northeast-2.compute.internal
...
Allocatable:
  attachable-volumes-aws-ebs:  25
  cpu:                         1930m
  ephemeral-storage:           27905944324
  hugepages-1Gi:               0
  hugepages-2Mi:               0
  memory:                      3388364Ki
  pods:                        17
...
#17개임을 확인할 수 있다.</code></pre>
<p>아래는 AWS에서 소개한 ENI, IPv4의 최대개수와, 최대 파드의 수를 계산한 값이다.</p>
<pre><code class="language-go">var InstanceNetworkingLimits = map[string]InstanceTypeLimits{
    ...
    &quot;t1.micro&quot;:      {ENILimit: 2, IPv4Limit: 2, HypervisorType:&quot;xen&quot;, IsBareMetal:false},
    &quot;t2.2xlarge&quot;:    {ENILimit: 3, IPv4Limit: 15, HypervisorType:&quot;xen&quot;, IsBareMetal:false},
    &quot;t2.large&quot;:      {ENILimit: 3, IPv4Limit: 12, HypervisorType:&quot;xen&quot;, IsBareMetal:false},
    &quot;t2.medium&quot;:     {ENILimit: 3, IPv4Limit: 6, HypervisorType:&quot;xen&quot;, IsBareMetal:false},
    &quot;t2.micro&quot;:      {ENILimit: 2, IPv4Limit: 2, HypervisorType:&quot;xen&quot;, IsBareMetal:false},
    &quot;t2.nano&quot;:       {ENILimit: 2, IPv4Limit: 2, HypervisorType:&quot;xen&quot;, IsBareMetal:false},
    &quot;t2.small&quot;:      {ENILimit: 3, IPv4Limit: 4, HypervisorType:&quot;xen&quot;, IsBareMetal:false},
    &quot;t2.xlarge&quot;:     {ENILimit: 3, IPv4Limit: 15, HypervisorType:&quot;xen&quot;, IsBareMetal:false},
    &quot;t3.2xlarge&quot;:    {ENILimit: 4, IPv4Limit: 15, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3.large&quot;:      {ENILimit: 3, IPv4Limit: 12, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3.medium&quot;:     {ENILimit: 3, IPv4Limit: 6, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3.micro&quot;:      {ENILimit: 2, IPv4Limit: 2, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3.nano&quot;:       {ENILimit: 2, IPv4Limit: 2, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3.small&quot;:      {ENILimit: 3, IPv4Limit: 4, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3.xlarge&quot;:     {ENILimit: 4, IPv4Limit: 15, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3a.2xlarge&quot;:   {ENILimit: 4, IPv4Limit: 15, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3a.large&quot;:     {ENILimit: 3, IPv4Limit: 12, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3a.medium&quot;:    {ENILimit: 3, IPv4Limit: 6, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3a.micro&quot;:     {ENILimit: 2, IPv4Limit: 2, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3a.nano&quot;:      {ENILimit: 2, IPv4Limit: 2, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3a.small&quot;:     {ENILimit: 2, IPv4Limit: 4, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    &quot;t3a.xlarge&quot;:    {ENILimit: 4, IPv4Limit: 15, HypervisorType:&quot;nitro&quot;, IsBareMetal:false},
    ...</code></pre>
<ul>
<li>아래는 최대 파드의 개수를 계산한 결과이다.<pre><code class="language-css">...
t1.micro 4
t2.2xlarge 44
t2.large 35
t2.medium 17
t2.micro 4
t2.nano 4
t2.small 11
t2.xlarge 44
t3.2xlarge 58
t3.large 35
t3.medium 17
t3.micro 4
t3.nano 4
t3.small 11
t3.xlarge 58
t3a.2xlarge 58
t3a.large 35
t3a.medium 17
t3a.micro 4
t3a.nano 4
t3a.small 8
t3a.xlarge 58
...</code></pre>
</li>
</ul>
<h3 id="l-ipam"><strong>L-IPAM</strong></h3>
<p>L-IPAM은 노드별로 보조 IP 주소의 warm-pool을 유지한다. 파드를 하나 추가할 때마다, warm-pool에서 사용가능한 IP를 가져와 파드에 할당한다. <strong><a href="https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/cni-proposal.md">GitHub</a>에서 자세한 내용을 확인할 수 있다.</strong></p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/40df0503-d9a3-41d1-a8e2-e9b5939a08bb/image.png" alt=""></p>
<p>아래의 그림을 확인해보면, 파드를 생성하면 ENI의 남아있는 IP를 할당한다. 만약 IP가 부족하다면 새로운 ENI를 생성하고, IP를 부여한다. 모든 ENI와 IP를 소모하면, 파드는 생성되지 않고 <code>Pending</code> 상태가 된다. (아래의 실습에서 확인할 수 있다.)</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/192ea03e-2f50-42ee-b00c-fd1c19adcd73/image.png" alt=""></p>
<p>ifconfig 명령을 통해 eth(이더넷)을 확인해보면 cni는 <code>aws-node</code> <code>kube-proxy</code> 를 제외한 다른 파드를 배포하면 두번째 ENI를 생성하는 것을 확인할 수 있다. 또, 추가적으로 발생한 파드에 대해서는 모든 라이팅 테이블이 eni로 향하고 있다. 파드에 대한 접근이 들어오면 eni에게 할당되고 이를 pod에게 보낸다. 아래의 실습코드에서 coredns의 Ip : <code>192.168.1.29</code>를 따라가보면 위의 내용을 확인할 수 있다.</p>
<pre><code class="language-bash">#coredns private ip 확인
$k get po -A -o wide | grep coredns
...
coredns-6777fcd775-zg7dh   1/1     Running   0          73m   **192.168.1.29**    ip-192-168-1-197.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;

#node1 route 확인 - core dns 확인
$ssh ec2-user@$N1 sudo ip -c route
default via 192.168.1.1 dev eth0
169.254.169.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.197
**192.168.1.29** dev enic94a7bde5cd scope link

#eth0, eth1 확인
$ssh ec2-user@$N1 sudo ifconfig
eth0: flags=4163&lt;UP,BROADCAST,RUNNING,MULTICAST&gt;  mtu 9001
        inet 192.168.1.197  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::29:7bff:fe12:5208  prefixlen 64  scopeid 0x20&lt;link&gt;
        ether 02:29:7b:12:52:08  txqueuelen 1000  (Ethernet)
                ...
eth1: flags=4163&lt;UP,BROADCAST,RUNNING,MULTICAST&gt;  mtu 9001
        inet 192.168.1.154  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::60:2fff:fe79:3eb0  prefixlen 64  scopeid 0x20&lt;link&gt;
        ether 02:60:2f:79:3e:b0  txqueuelen 1000  (Ethernet)
                ...
</code></pre>
<h3 id="cni-확인-및-kube-proxy-정보-확인">CNI 확인 및 kube-proxy 정보 확인</h3>
<h4 id="iptables-vs-ipvs">(<code>iptables</code> vs <code>ipvs</code>)</h4>
<p>kube-proxy에서 <code>iptables</code> 모드를 사용하고 <code>ipvs</code> 모드를 사용하지 않는 이유는 다음과 같다. ipvs는 일부 CNI 플러그인, 네트워크 정책과 호환되지 않는다. 또, <code>iptables</code> 모드는 오랜 기간 쿠버네티스에서 사용되어 안정성을 인정받았다.</p>
<ul>
<li>iptables 는 kube-proxy가 테이블만 관리하고, 직접 트래픽을 전달하지 않는다. 테이블을 통해 트래픽이 전달된다.</li>
<li>ipvs는 table과 비슷하지만 자동으로 LB가 적용된다.</li>
</ul>
<pre><code class="language-bash"># CNI 확인
$kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d &quot;/&quot; -f 2
amazon-k8s-cni-init:v1.12.6-eksbuild.1
amazon-k8s-cni:v1.12.6-eksbuild.1
# kube-proxy config 확인: iptables 모드 사용!(가장 하단 참고)
$kubectl describe cm -n kube-system kube-proxy-configData
====
config:
----
apiVersion: kubeproxy.config.k8s.io/v1alpha1
...
iptables:
  masqueradeAll: false
  masqueradeBit: 14
  minSyncPeriod: 0s
  syncPeriod: 30s
ipvs:
  excludeCIDRs: null
  minSyncPeriod: 0s
  scheduler: &quot;&quot;
  syncPeriod: 30s
kind: KubeProxyConfiguration
metricsBindAddress: 0.0.0.0:10249
mode: &quot;iptables&quot;
...</code></pre>
<h3 id="eni-통신-확인">ENI 통신 확인</h3>
<p>이제 ENI의 작동방식에 대해 실습을 해봤다.</p>
<h4 id="노드에-파드를-배포시키고-모니터링-해보기">노드에 파드를 배포시키고 모니터링 해보기</h4>
<pre><code class="language-bash">#노드에 접속
$ssh ec2-user@$N1
#모니터링 시작(처음에는 2번과 route table의 첫번째줄이 없었으나 파드 배포 후 형성 
$watch -d &quot;ip link | egrep &#39;eth|eni&#39; ;echo;echo &quot;[ROUTE TABLE]&quot;; route -n | grep eni&quot;
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 02:29:7b:12:52:08 brd ff:ff:ff:ff:ff:ff
3: enic94a7bde5cd@if3: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 9001 qdisc noqueue state UP mode DEFAULT group default
    link/ether 9a:0f:49:8e:c9:2a brd ff:ff:ff:ff:ff:ff link-netns cni-d14dc09b-32af-693a-8b24-9cda5de41e29
4: eth1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 02:60:2f:79:3e:b0 brd ff:ff:ff:ff:ff:ff

[ROUTE TABLE]
192.168.1.29    0.0.0.0         255.255.255.255 UH    0      0        0 enic94a7bde5cd
## ==================================배포 후============================================================================================
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 02:29:7b:12:52:08 brd ff:ff:ff:ff:ff:ff
3: enic94a7bde5cd@if3: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 9001 qdisc noqueue state UP mode DEFAULT group default
    link/ether 9a:0f:49:8e:c9:2a brd ff:ff:ff:ff:ff:ff link-netns cni-d14dc09b-32af-693a-8b24-9cda5de41e29
4: eth1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 02:60:2f:79:3e:b0 brd ff:ff:ff:ff:ff:ff
5: eni49834a7b0d4@if3: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 9001 qdisc noqueue state UP mode DEFAULT group default
    link/ether 86:cd:5e:33:6f:52 brd ff:ff:ff:ff:ff:ff link-netns cni-9f690975-044d-b0d3-2307-1b8f380ead02

[ROUTE TABLE]
192.168.1.10    0.0.0.0         255.255.255.255 UH    0      0        0 eni49834a7b0d4
192.168.1.29    0.0.0.0         255.255.255.255 UH    0      0        0 enic94a7bde5cd

# 핑을 보낼 파드의 IP 확인 
$echo $PODIP1, $PODIP2
192.168.3.186, 192.168.2.79

#파드 통신 확인(첫번째 파드에서 두번째 파드로 핑 테스트)
$kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2
PING 192.168.2.79 (192.168.2.79) 56(84) bytes of data.
64 bytes from 192.168.2.79: icmp_seq=1 ttl=62 time=1.78 ms
64 bytes from 192.168.2.79: icmp_seq=2 ttl=62 time=1.28 ms

--- 192.168.2.79 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 1.275/1.529/1.784/0.254 ms

#워커노드에 접근
$ssh ec2-user@$N2

#아래의 결과를 통해 파드끼리 통신이 잘 이뤄짐을 확인할 수 있음
$sudo tcpdump -i any -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
17:49:33.973818 IP 192.168.3.186 &gt; 192.168.2.79: ICMP echo request, id 24336, seq 1, length 64
17:49:33.973867 IP 192.168.3.186 &gt; 192.168.2.79: ICMP echo request, id 24336, seq 1, length 64
17:49:33.973886 IP 192.168.2.79 &gt; 192.168.3.186: ICMP echo reply, id 24336, seq 1, length 64
17:49:33.973897 IP 192.168.2.79 &gt; 192.168.3.186: ICMP echo reply, id 24336, seq 1, length 64
17:49:34.975715 IP 192.168.3.186 &gt; 192.168.2.79: ICMP echo request, id 24336, seq 2, length 64
17:49:34.975755 IP 192.168.3.186 &gt; 192.168.2.79: ICMP echo request, id 24336, seq 2, length 64
17:49:34.975775 IP 192.168.2.79 &gt; 192.168.3.186: ICMP echo reply, id 24336, seq 2, length 64
17:49:34.975785 IP 192.168.2.79 &gt; 192.168.3.186: ICMP echo reply, id 24336, seq 2, length 64

#아래의 명령어를 통해 나가는 패킷만 확인할 수 있다. 관련 파드는 cni1이 아니지만 eth0을 통해 빠져나간다.
$sudo tcpdump -i eth0 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
17:51:55.080619 IP 192.168.3.186 &gt; 192.168.2.79: ICMP echo request, id 831, seq 1, length 64
17:51:55.080805 IP 192.168.2.79 &gt; 192.168.3.186: ICMP echo reply, id 831, seq 1, length 64
17:51:56.082608 IP 192.168.3.186 &gt; 192.168.2.79: ICMP echo request, id 831, seq 2, length 64
17:51:56.082658 IP 192.168.2.79 &gt; 192.168.3.186: ICMP echo reply, id 831, seq 2, length 64

$ip route show table main
default via 192.168.2.1 dev eth0
169.254.169.254 dev eth0
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.251
192.168.2.79 dev eni96ea53d0c48 scope link
192.168.2.172 dev eni7c48f16dc91 scope link

#파드와 외부의 통신
$kubectl exec -it $PODNAME2 -- ping -c 1 www.google.com
PING www.google.com (142.250.198.4) 56(84) bytes of data.
64 bytes from nrt12s58-in-f4.1e100.net (142.250.198.4): icmp_seq=1 ttl=104 time=33.0 ms
--- www.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 32.989/32.989/32.989/0.000 ms

$sudo tcpdump -i any -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
17:56:18.262417 IP 192.168.2.79 &gt; 142.250.198.4: ICMP echo request, id 1979, seq 1, length 64
17:56:18.262441 IP 192.168.2.251 &gt; 142.250.198.4: ICMP echo request, id 43010, seq 1, length 64
17:56:18.295356 IP 142.250.198.4 &gt; 192.168.2.251: ICMP echo reply, id 43010, seq 1, length 64
17:56:18.295398 IP 142.250.198.4 &gt; 192.168.2.79: ICMP echo reply, id 1979, seq 1, length 64

#파드가 외부와의 통신에서 사용하는 IP추적
$kubectl exec -it $PODNAME2 -- curl -s ipinfo.io/ip ; echo
15.164.179.105
#아래는 워커노드에 접속하여 실행한 명령어다. 현재의 Public ip값을 받으며 위와 동일한 것을 확인할 수 있다.
$curl -s ipinfo.io/ip ; echo
15.164.179.105

# 핑을 외부로 보낼 때는 아래와 같은 룰에 의해 SNAT 192.168.2.143(Worker node IP)변경되어 나간다!
sudo iptables -t nat -S | grep &#39;A AWS-SNAT-CHAIN&#39;
#첫번째 규칙은 목적지 주소가 192.168.0.0/16 : 즉 같은 VPC가 아닌경우 AWS-SNAT-CHAIN-1로 점프하도록 한다.
-A AWS-SNAT-CHAIN-0 ! -d 192.168.0.0/16 -m comment --comment &quot;AWS SNAT CHAIN&quot; -j AWS-SNAT-CHAIN-1
#두번째 규칙은 외부로 향하는 트래픽을 노드의 IP로 변경시키는 것이다. 
-A AWS-SNAT-CHAIN-1 ! -o vlan+ -m comment --comment &quot;AWS, SNAT&quot; -m addrtype ! --dst-type LOCAL -j SNAT --to-source **192.168.2.143** --random-fully
</code></pre>
<h4 id="노드의-최대-파드개수-생성">노드의 최대 파드개수 생성</h4>
<p>해당 실습은 이전에 계산한 최대 파드의 수보다 많은 파드를 배포했을 때, 클러스터 내부에서 어떤 일이 발생하는 지 확인해봤다.</p>
<pre><code class="language-bash">#파드모니터링
$watch -d &#39;kubectl get pods -o wide&#39;
NAME                                READY   STATUS    RESTARTS   AGE   IP              NODE  NOMINATED NODE   READINESS GATES
nginx-deployment-6fb79bc456-4sjqf   1/1     Running   0          47s   192.168.1.238   ip-192-168-1-197.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
nginx-deployment-6fb79bc456-wq4qc   1/1     Running   0          47s   192.168.3.122   ip-192-168-3-163.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
#파드의 개수를 50개로 늘리자. 생성되지 못하는 파드가 보인다.(최대 개수 초과)
$kubectl get pods | grep Pending
nginx-deployment-6fb79bc456-2lvh9   0/1     Pending   0          8s
nginx-deployment-6fb79bc456-6sdq8   0/1     Pending   0          8s
nginx-deployment-6fb79bc456-9ctj5   0/1     Pending   0          8s
nginx-deployment-6fb79bc456-jcqvg   0/1     Pending   0          8s
nginx-deployment-6fb79bc456-lr7sl   0/1     Pending   0          8s
nginx-deployment-6fb79bc456-rmnw7   0/1     Pending   0          8s
nginx-deployment-6fb79bc456-xsfpx   0/1     Pending   0          8s</code></pre>
<p>관련 워커노드의 ip addr 모니터링한 결과는 아래와 같다. 15개의 eni가 존재하는 것을 확인할 수 있다. aws-node, kube-proxy를 포함하면 총 17개의 파드이며 이는 최대 개수와 동일함을 확인할 수 있다.</p>
<pre><code class="language-bash">$while true; do ip -br -c addr show &amp;&amp; echo &quot;--------------&quot; ; date &quot;+%Y-%m-%d %H:%M:%S&quot; ; sleep 1; done
2023-05-05 18:09:01
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             192.168.1.197/24 fe80::29:7bff:fe12:5208/64
enic94a7bde5cd@if3 UP             fe80::980f:49ff:fe8e:c92a/64
eth1             UP             192.168.1.154/24 fe80::60:2fff:fe79:3eb0/64
enib263aee3e3a@if3 UP             fe80::9801:86ff:fee0:94fe/64
enid3487d875d4@if3 UP             fe80::c889:d4ff:fe33:a24d/64
eni9030418a3ff@if3 UP             fe80::249c:ceff:fe41:3180/64
enib68b149b26a@if3 UP             fe80::689b:ecff:fee3:8f58/64
enie5050516bf9@if3 UP             fe80::98f2:83ff:fe1d:33ea/64
eth2             UP             192.168.1.84/24 fe80::d6:b0ff:fe9f:74e/64
eni161f9a2e0c7@if3 UP             fe80::cc2d:24ff:fe37:aa90/64
eni70a8a75bf6a@if3 UP             fe80::6084:5ff:fe5a:5bd6/64
eni34a852be90e@if3 UP             fe80::40d9:f6ff:fe23:3635/64
eni04149b80833@if3 UP             fe80::283f:6eff:feb3:3c8f/64
eni36442fb5871@if3 UP             fe80::4c83:fdff:fe07:621e/64
enia7a957f74d7@if3 UP             fe80::98c1:4ff:fe7b:c1/64
eni4952364d6e6@if3 UP             fe80::b81a:ecff:fe34:9a01/64
enid486e2f5d72@if3 UP             fe80::acf4:5dff:fe1a:a6b9/64
enib05a29aaa90@if3 UP             fe80::3055:93ff:feb4:ae3a/64

#파드의 오류 메세지 ( Too many pods.) 를 확인할 수 있다.
$k describe po nginx-deployment-6fb79bc456-2lvh9
...
Events:
  Type     Reason            Age    From               Message
  ----     ------            ----   ----               -------
  Warning  FailedScheduling  4m20s  default-scheduler  0/3 nodes are available: 3 Too many pods. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.</code></pre>
<h3 id="추가적으로-최대-파드-허용">추가적으로 최대 파드 허용</h3>
<p>네트워크 인터페이스에 접두사 할당 기능이 생김으로, 더 많은 IP주소를 활용하여 최대 파드의 개수를 늘릴 수 있다. 관련된 내용은 <strong><a href="https://trans.yonghochoi.com/translations/aws_vpc_cni_increase_pods_per_node_limits.ko">YongTrans</a></strong>에서 확인할 수 있다.
<img src="https://velog.velcdn.com/images/han-0315/post/c1fc2f82-6d3d-498e-9d75-56dfada4a301/image.png" alt="">
관련 내용 또한 추후에 실습할 예정이다..</p>
<p>실습을 진행하기 위해 관련 예제를 물어봤다. chat gpt의 답변이다. CIDR과 다른 네트워크 대역을 추가로 할당하여 노드에게 더 많은 IP주소를 부여하는 방식이다. 위의 사이트에서는 네트워크 인터페이스에 할당하지만, 여기서는 CIDR의 범위자체를 늘리는 방법을 소개해준다.</p>
<p><img src="https://velog.velcdn.com/images/han-0315/post/3c3eb7b9-d89e-4909-9acb-303198066f1a/image.png" alt=""></p>
<h2 id="서비스"><strong>서비스</strong></h2>
<p>이제 ENI가 아닌 서비스에 대한 내용을 진행한다. 서비스는 파드의 IP가 바뀔 확률이 높기 때문에 고정적인 virtual IP를 만들어주는 역할을 한다. Clutser IP, Node Port 등 다양한 종류가 있지만 여기서는 로드밸런서에 대해 구체적으로 알아봤다.
<img src="https://velog.velcdn.com/images/han-0315/post/42a5cfda-2a26-4594-9e3f-bbcf53292994/image.png" alt="">
그림과 같이 EKS에서는 바로 연결이 가능하다.contrack &amp; iptable 등 여러 작업을 건너뛸 수 있다. 이제 관련 실습을 통해 더 자세히 알아보자.</p>
<h4 id="load-balancer-controller">Load Balancer Controller</h4>
<p>먼저 LB 연결하는 방법은 다음과 같다. 자세한 내용은 <a href="https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/aws-load-balancer-controller.html">Docs</a>에서 확인할 수 있다.
방법은 다음과 같은 순서로 이뤄진다.</p>
<ol>
<li>IAM<ul>
<li>정책 생성 :  AWS Load Balancer Controller에 대한 IAM 정책 다운로드</li>
<li>역할 생성 : <code>eksctl create iamserviceaccount ~</code></li>
</ul>
</li>
<li>배포<ul>
<li>helm 차트 : <code>public.ecr.aws/eks/aws-load-balancer-controller:v2.4.7</code> ecr 이용<pre><code class="language-bash">#OIDC(OpenID Connect) 이슈어 정보를 가져오는 명령어로, 얻은 URL을 통해 클러스터 인증 및 권한 부여,
#AWS IAM에 대한 제어를 할 수 있다.
$aws eks describe-cluster --name $CLUSTER_NAME --query &quot;cluster.identity.oidc.issuer&quot; --output text
https://oidc.eks.ap-northeast-2.amazonaws.com/id/73F304C1D2..F6B0692B444F
#로브드밸런서 컨트롤러에 대한 IAM 정책을 다운받는다.
$curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
</code></pre>
</li>
</ul>
</li>
</ol>
<p>#정책 생성 및 연결
$eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller \</p>
<blockquote>
<p>--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve
...
2023-05-06 11:33:48 [ℹ]  1 task: {
    2 sequential sub-tasks: {
        create IAM role for serviceaccount &quot;kube-system/aws-load-balancer-controller&quot;,
        create serviceaccount &quot;kube-system/aws-load-balancer-controller&quot;,
    } }2023-05-06 11:33:48 [ℹ]  building iamserviceaccount stack &quot;eksctl-myeks-addon-iamserviceaccount-kube-system-aws-load-balancer-controller&quot;
...</p>
</blockquote>
<p>#생성 결과 확인
$eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE    NAME                ROLE ARN
kube-system    aws-load-balancer-controller    arn:aws:iam::1234567890:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-RZW</p>
<h1 id="eks-lb-helm-차트-배포">EKS LB helm 차트 배포</h1>
<p>$helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \</p>
<blockquote>
<p>  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
NAME: aws-load-balancer-controller
LAST DEPLOYED: Sat May  6 11:34:52 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!
#CustomResourceDefinition CRD 조회
$k get crd
NAME                                         CREATED AT
eniconfigs.crd.k8s.amazonaws.com             2023-05-06T01:24:19Z
ingressclassparams.elbv2.k8s.aws             2023-05-06T02:34:50Z
securitygrouppolicies.vpcresources.k8s.aws   2023-05-06T01:24:22Z
targetgroupbindings.elbv2.k8s.aws            2023-05-06T02:34:50Z</p>
</blockquote>
<h1 id="saservice-account-조회">SA(service account) 조회</h1>
<p>$k get sa -A | grep load-balancer
kube-system       aws-load-balancer-controller         0         42m</p>
<pre><code>
이제 관련 서비스를 배포하고 이를 모니터링하는 과정을 진행했다. yaml 파일 등은 모두 스터디에서 제공해주었다. 먼저, 배포를 진행하고, 로드밸런서의 호스트네임을 파악하여 접근해본다. 또 노드의 tcp dump 를 확인하여 통신방식을 점검한다.

```bash
# LB 서비스를 배포
$cat echo-service-nlb.yaml | yh 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-echo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: deploy-websrv
  template:
    metadata:
      labels:
        app: deploy-websrv
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - name: akos-websrv
        image: k8s.gcr.io/echoserver:1.5
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc-nlb-ip-type
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: &quot;8080&quot;
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: &quot;true&quot;
spec:
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb
  selector:
    app: deploy-websrv

#모니터링 결과 배포된 것을 확인할 수 있음.
Every 2.0s: kubectl get pod,svc,ep                                                                           Sat May  6 12:21:46 2023

NAME                               READY   STATUS    RESTARTS   AGE
pod/deploy-echo-5c4856dfd6-jg9hp   1/1     Running   0          4m4s
pod/deploy-echo-5c4856dfd6-pq889   1/1     Running   0          4m4s

NAME                      TYPE           CLUSTER-IP      EXTERNAL-IP
           PORT(S)        AGE
service/kubernetes        ClusterIP      10.100.0.1      &lt;none&gt;
        443/TCP        117m
service/svc-nlb-ip-type   LoadBalancer   10.100.75.117   k8s-default-svcnlbip-2a2f74cdb7-c4a4d94160e4fb86.elb.ap-northeast-2.amazonaw
s.com   80:30432/TCP   4m4s

NAME                        ENDPOINTS                               AGE
endpoints/kubernetes        192.168.2.54:443,192.168.3.42:443       117m
endpoints/svc-nlb-ip-type   192.168.1.175:8080,192.168.2.212:8080   4m4s

#LB 서비스 호스트 네임 파악
$kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk &#39;{ print &quot;Pod Web URL = http://&quot;$1 }&#39;
Pod Web URL = http://k8s-default-svcnlbip-2a2f74cdb7-c4a4d94160e4fb86.elb.ap-northeast-2.amazonaws.com
#로드 밸런싱이 정상적으로 이뤄지고 있는 지 확인
$for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
     51 Hostname: deploy-echo-5c4856dfd6-jg9hp
     49 Hostname: deploy-echo-5c4856dfd6-pq889
#노드의 TCP dump 에는 기록이 안나옴 : NLB에서 파드로 직접 통신하기 때문에
$sudo tcpdump -i any -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes</code></pre><p>아래는 LB Hostname으로 접근한 결과이다. 보는 것과 같이, 로드밸런싱이 잘 이뤄지는 것을 확인할 수 있고, 파드의 개수를 늘리거나 줄여도 스스로 잘 찾아서 이뤄진다. 트래픽이 들어오면 가용영역에 있는 파드 중에 이용가능한 파드를 찾아 부하를 분산한다.
<img src="https://velog.velcdn.com/images/han-0315/post/80bc02f8-189b-40d2-a8e0-da273b2b58bf/image.png" alt="">
아래의 그림에서 가용영역을 확인해볼 수 있다. 1개의 파드만 존재해도 아래의 로그와 같이, 지속적으로 가능한 가용영역에서 가능한 파드가 있는 지 찾는 것을 확인할 수 있었다.</p>
<pre><code class="language-bash">----------
2023-05-06 12:50:39
Hostname: deploy-echo-5c4856dfd6-jg9hp
    client_address=192.168.1.95
----------
2023-05-06 12:50:40
Hostname: deploy-echo-5c4856dfd6-jg9hp
    client_address=192.168.3.132
----------
2023-05-06 12:50:41
Hostname: deploy-echo-5c4856dfd6-jg9hp
    client_address=192.168.2.61
</code></pre>
<p><img src="https://velog.velcdn.com/images/han-0315/post/3beb9c5c-8e01-4849-8723-4ecbf282a47e/image.png" alt=""></p>
<p>이제 관련 룰을 확인해보자. 어떤 리소스를 접근할 수 있는 지, verbs를 통해 허락된 연산을 확인할 수 있다.</p>
<pre><code class="language-bash">##롤 확인
$kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
Name:         aws-load-balancer-controller-role
Labels:       app.kubernetes.io/instance=aws-load-balancer-controller
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=aws-load-balancer-controller
              app.kubernetes.io/version=v2.5.1
              helm.sh/chart=aws-load-balancer-controller-1.5.2
Annotations:  meta.helm.sh/release-name: aws-load-balancer-controller
              meta.helm.sh/release-namespace: kube-system
PolicyRule:
  Resources                                     Non-Resource URLs  Resource Names  Verbs
  ---------                                     -----------------  --------------  -----
  targetgroupbindings.elbv2.k8s.aws             []                 []              [create delete get list patch update watch]
  events                                        []                 []              [create patch]
  ingresses                                     []                 []              [get list patch update watch]
  services                                      []                 []              [get list patch update watch]
  ingresses.extensions                          []                 []              [get list patch update watch]
  services.extensions                           []                 []              [get list patch update watch]
  ingresses.networking.k8s.io                   []                 []              [get list patch update watch]
  services.networking.k8s.io                    []                 []              [get list patch update watch]
  endpoints                                     []                 []              [get list watch]
  namespaces                                    []                 []              [get list watch]
  nodes                                         []                 []              [get list watch]
  pods                                          []                 []              [get list watch]
  endpointslices.discovery.k8s.io               []                 []              [get list watch]
  ingressclassparams.elbv2.k8s.aws              []                 []              [get list watch]
  ingressclasses.networking.k8s.io              []                 []              [get list watch]
  ingresses/status                              []                 []              [update patch]
  pods/status                                   []                 []              [update patch]
  services/status                               []                 []              [update patch]
  targetgroupbindings/status                    []                 []              [update patch]
  ingresses.elbv2.k8s.aws/status                []                 []              [update patch]
  pods.elbv2.k8s.aws/status                     []                 []              [update patch]
  services.elbv2.k8s.aws/status                 []                 []              [update patch]
  targetgroupbindings.elbv2.k8s.aws/status      []                 []              [update patch]
  ingresses.extensions/status                   []                 []              [update patch]
  pods.extensions/status                        []                 []              [update patch]
  services.extensions/status                    []                 []              [update patch]
  targetgroupbindings.extensions/status         []                 []              [update patch]
  ingresses.networking.k8s.io/status            []                 []              [update patch]
  pods.networking.k8s.io/status                 []                 []              [update patch]
  services.networking.k8s.io/status             []                 []              [update patch]
  targetgroupbindings.networking.k8s.io/status  []                 []              [update patch]
#롤 바인딩 확인
$kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
Name:         aws-load-balancer-controller-rolebinding
Labels:       app.kubernetes.io/instance=aws-load-balancer-controller
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=aws-load-balancer-controller
              app.kubernetes.io/version=v2.5.1
              helm.sh/chart=aws-load-balancer-controller-1.5.2
Annotations:  meta.helm.sh/release-name: aws-load-balancer-controller
              meta.helm.sh/release-namespace: kube-system
Role:
  Kind:  ClusterRole
  Name:  aws-load-balancer-controller-role
Subjects:
  Kind            Name                          Namespace
  ----            ----                          ---------
  ServiceAccount  aws-load-balancer-controller  kube-system</code></pre>
<h2 id="ingress">Ingress</h2>
<p>이제 Ingress를 통한 게임배포를 진행한다. Ingress는 <strong>클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할</strong>이다. </p>
<pre><code class="language-bash"># 배포
$kubectl apply -f ingress1.yaml
namespace/game-2048 created
deployment.apps/deployment-2048 created
service/service-2048 created
ingress.networking.k8s.io/ingress-2048 created
#관련 yaml 파일의 내용을 확인하면
cat ingress1.yaml | yh
...
---
apiVersion: apps/v1
kind: Deployment
...
    spec:
      containers:
      # 2048에 대한 이미지로 파드를 생성한다.
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: app-2048
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
...
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  **type: NodePort**
  selector:
    app.kubernetes.io/name: **app-2048**
#기본 path = &quot;/&quot;, LB 관련 도메인에 접속하면 `service-2048`에 연결한다.
#이 서비스는 노드 포트로, 2048게임이 있는 파드에 연결해준다. 그렇게 2048게임의 화면이 뜨게된다.
apiVersion: networking.k8s.io/v1
kind: Ingress
...
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
        **- path: /**
          pathType: Prefix
          backend:
            service:
              **name: service-2048**
              port:
                number: 80</code></pre>
<p>아래의 그림은 로드밸런서 호스트네임으로 접근한 결과이다. Ingress를 통해 서비스로 연결되고, 이후 서비스를 통해 파드에게 연결되어 파드의 이미지인 2048게임 화면을 볼 수 있다.
<img src="https://velog.velcdn.com/images/han-0315/post/1522a085-8d9f-426e-bdcb-6590c316db71/image.png" alt=""></p>
<ul>
<li>도메인과 관련된 부분은 추후 업데이트 예정..!<ul>
<li>스터디 시작전에 관련 도메인을 미리 등록해뒀는데 결제가 안된모양이다. 아마 일전에 재발급을 받은 적이 있는 데, 업데이트를 안했나보다..</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/han-0315/post/4ba588b5-8028-4a31-a2d5-9ce6df879aa7/image.png" alt=""></p>
<h2 id="경험-발표">경험 발표</h2>
<p>경험발표는 두 분이 진행해주셨다. 한분은 테라폼과 관련된 경험을 발표해주셨고, 한분은 VPC Lattice + EKS에 대해 알려주셨다. 관련 자료중에 공개해주신 자료 중 해당 <a href="https://catalog.us-east-1.prod.workshops.aws/workshops/9e543f60-e409-43d4-b37f-78ff3e1a07f5/ko-KR">WorkShop</a>을 따라가면 데모로 보여주신 실습을 진행할 수 있다고 한다.</p>
<h2 id="추가">추가</h2>
<p>파드의 노드와 같은 네트워크 대역대를 부여해서 오버레이 연산이 없어지기에 성능이 향상된다. ENI와 다른 CNI에 대해 눈으로 시간차이를 확인하고 싶어 관련 내용을 찾아봤다.</p>
<p> 2개의 클러스터를 준비하고 각각 다른 CNI(ENI, 그 외)를 설치한 뒤 아래와 같이 네트워크 밴치마킹 테스트를 통해 확인해볼 수 있다. (이것도.. 추후 진행 예정)</p>
<p>직접 시간차이를 측정하고 싶다면 아래와 같은 과정을 따라가면 된다. 아래의 자료는 ChatGPT, 구글링을 통해 얻었다.</p>
<h4 id="iperf3을-이용한-성능비교">iperf3을 이용한 성능비교</h4>
<ol>
<li>2개의 EKS 클러스터를 준비하고 하나는 Calico, 하나는 ENI 사용</li>
<li><code>iperf3</code> 배포</li>
<li><strong><code>iperf3</code></strong> 테스트 진행(서버와 클라이언트), 관련 내용 기록</li>
<li>테스트 결과 비교</li>
</ol>
<p>간단한 <strong><code>iperf3</code></strong> deployment YAML 파일</p>
<pre><code class="language-css">apiVersion: apps/v1
kind: Deployment
metadata:
  name: iperf3-server
spec:
  selector:
    matchLabels:
      app: iperf3-server
  replicas: 1
  template:
    metadata:
      labels:
        app: iperf3-server
    spec:
      containers:
      - name: iperf3-server
        image: networkstatic/iperf3
        args: [&quot;-s&quot;]
        ports:
        - containerPort: 5201
---
apiVersion: v1
kind: Service
metadata:
  name: iperf3-server
spec:
  selector:
    app: iperf3-server
  ports:
    - protocol: TCP
      port: 5201
      targetPort: 5201
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: iperf3-client
spec:
  selector:
    matchLabels:
      app: iperf3-client
  replicas: 1
  template:
    metadata:
      labels:
        app: iperf3-client
    spec:
      containers:
      - name: iperf3-client
        image: networkstatic/iperf3
        command: [&quot;sleep&quot;]
        args: [&quot;infinity&quot;]</code></pre>
<p>배포 후 아래의 명령어를 통해 실행하면 된다.</p>
<pre><code class="language-bash">kubectl exec -it [iperf3-client-pod-name] -- iperf3 -c iperf3-server -t 30</code></pre>
<h3 id="마치며">마치며</h3>
<p>2주차 블로그 포스팅을 완료했다. 목표는 화, 수요일날 포스팅을 할 계획이었으나 예비군이후로 비염과 함께 컨디션이 좋지 않아 결국 토요일날 마무리?지었다. 한번에 전체적인 실습을 하는 것이 좋은 데, 그렇지 못해 실습 결과(Node Ip 등)들이 조금씩은 다르고 계속 끊기다보니 시간이 더 소요된 것 같다.! 이번주 연휴동안 푹쉬고 조금씩 보완하려한다. EKS는 중지기능이 없어서 게속 생성하고 제거하는 것했는데 조금 아쉬운 것 같다...!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EKS 스터디 1주차]]></title>
            <link>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@han-0315/EKS-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 27 Apr 2023 15:27:19 GMT</pubDate>
            <description><![CDATA[<h2 id="참여-계기">참여 계기</h2>
<p>현재 데브옵스 신입 엔지니어로 회사에서 업무를 진행하는 중이지만, 쿠버네티스 환경에 대한 부족한 실력으로 어려움이 많았다. CKA 자격증을 통해 쿠버네티스 공부를 시작했지만, 실무에 대한 부족함을 느끼던 와중에 회사 동료에게 추천을 받아  <a href="https://www.notion.so/c9dfa44a27ff431dafdd2edacc8a1863">CloudNet</a> 팀에서 주관하는 EKS 스터디에 지원했다. 스터디는 1시간 30분가량 가시다님의 강의와 두 분의 경험발표로 구성되었다. 아직 신입 엔지니어이기에 잘 따라가지 못할까 걱정스럽지만 퀄리티 높은 자료와 스터디원분들이기에 배울 것이 많아보여 설렌다. </p>
<h2 id="요약">요약</h2>
<p>매번 시작해야지 생각만 했던 블로그를 시작해본다. .!  부족한 점이 많지만 앞으로 수정하며 수준을 높이려 한다. 전체적으로 스터디 내용을 기반으로 정리했고, 쿠버네티스에 대해 부족한 것이 많아 중간중간 기본적인 내용도 추가적으로 정리했다. 1주차의 주제는 <strong>Amzaon EKS 설치 및 기본 사용</strong>이다. 사전에 제공해주신 <a href="https://docs.aws.amazon.com/cloudformation/index.html">AWS CloudFormation</a>을 통해 초기인프라를 형성하고 <code>eksctl</code> 을 통해 클러스터를 배포한다. 배포 후 클러스터에 접근하여 여러 명령어를 통해 시스템을 이해해보며 마무리 되었다.</p>
<p>포스트는 스터디와 같은 순서로 진행된다. 먼저, 아키텍처를 살펴보고 실습을 진행한다.</p>
<h2 id="eks-아키텍처">EKS 아키텍처</h2>
<p>먼저, EKS는 <a href="https://aws.amazon.com/eks/">Amazon Elastic Kubernetes Service</a> 의 약어이며 AWS에서 제공해주는 관리형 쿠버네티스 서비스이다. 뇌의 역할을 하는 컨트롤 플레인을 자동으로 관리해주고 다른 AWS 서비스와 통합하여 활용가능하다. 편하지만, 금액적으로는 부담스럽다. 상황에 맞게 서비스를 선택해서 이용하면 된다. <a href="https://www.finops.org/introduction/what-is-finops/">FinOps</a>의 중요성도 점점 커지는 것 같다. 1주차 경험발표도 관련 사례에 대해 소개해주셨는데, 최적화에 따른 연간 절약 금액이 어마어마했다.</p>
<p>이제 EKS에 아키텍처에 대해 알아보자. 아래의 그림은 EKS의 아키텍처 그림이다.
<img src="https://velog.velcdn.com/images/han-0315/post/c56e80fe-fe01-407b-ba0f-e786a9a79be2/image.png" alt="">
아키텍처에서 가장 중요한 점은 컨트롤 플레인이다. EKS에서는 AWS가  요소를 직접 관리해준다. ENI를 통해 노드와 API 서버는 통신한다. 위에 그림에서는 클러스터 엔드포인트가  퍼블릭이지만, 제약사항에 따라 프라이빗하게 접근하도록 설정할 수 있다. 실제 프로덕션에서는 아래의 그림과 같이 주로 ENI를 통해 프라이빗 환경에서 통신한다.
<img src="https://velog.velcdn.com/images/han-0315/post/c0fdb83e-5614-4ca1-b3ac-2837c4d9d13b/image.png" alt=""></p>
<h2 id="실습">실습</h2>
<p>실습 아키텍처는 아래와 같다. 비용을 고려해 최소한의 사양으로 준비해주셨다. CloudFormation 을 통해
작업용 EC2를 만든 후 접속하여 EKS를 배포한다. 
<img src="https://velog.velcdn.com/images/han-0315/post/407e568c-deca-43a7-a162-7de0e44c0c63/image.png" alt="">
EKS 배포는 웹 관리 콘솔, <strong>eksctl</strong>, IaC(CDK, CloudFormation, Terraform ..) 등이 있지만, EKS에서 K8S 클러스터를 생성하고 관리하기 위한 간단한 유틸리티 CLI 도구인 <strong><code>eksctl</code></strong>을 사용했다.</p>
<h3 id="배포">배포</h3>
<p>CloudFormation Stack의 출력값으로 EC2의 퍼블릭 아이피를 확인한 후 접속한다. 접속하고 <code>aws configure</code>로 기본적인 인증절차를 거친다.</p>
<p>아래는 작업용 ec2에서 <code>eksctl</code>을 통해 EKS를 배포하는 소스코드이다. 배포하는 데 약 10분정도의 시간이 필요하다.</p>
<pre><code class="language-bash">$eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium \
--node-volume-size=30 --vpc-public-subnets &quot;$PubSubnet1,$PubSubnet2&quot; --version 1.24 --ssh-access --external-dns-access --verbose 4**
...
2023-04-27 21:57:09 [ℹ]  will create 2 separate CloudFormation stacks for cluster itself and the initial managed nodegroup
2023-04-27 21:57:09 [ℹ]  if you encounter any issues, check CloudFormation console or try &#39;eksctl utils describe-stacks --region=ap-northeast-2 --cluster=myeks&#39;
2023-04-27 21:57:09 [ℹ]  Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster &quot;myeks&quot; in &quot;ap-northeast-2&quot;
2023-04-27 21:57:09 [ℹ]  CloudWatch logging will not be enabled for cluster &quot;myeks&quot; in &quot;ap-northeast-2&quot;
2023-04-27 21:57:09 [ℹ]  you can enable it with &#39;eksctl utils update-cluster-logging --enable-types={SPECIFY-YOUR-LOG-TYPES-HERE (e.g. all)} --region=ap-northeast-2 --cluster=myeks&#39;
2023-04-27 21:57:09 [ℹ]
2 sequential tasks: { create cluster control plane &quot;myeks&quot;,
    2 sequential sub-tasks: {
        wait for control plane to become ready,
        create managed nodegroup &quot;myeks-nodegroup&quot;,
    }
}</code></pre>
<p>배포가 완료되면 아래의 그림과 같이 AWS 콘솔을 통해 쿠버네티스 버전, 엔드포인트 등 상태를 확인하여 클러스터가 정상적으로 배포되었는 지 확인할 수 있다.
<img src="https://velog.velcdn.com/images/han-0315/post/e82d95ec-daec-45ba-9556-fbc048c7661a/image.png" alt=""></p>
<p>만약 아래의 그림과 같이 콘솔에서 EKS 리소스를 보는 것이 제한된다면 User 계정으로 로그인하여 문제를 해결할 수 있다. 자세한 내용은<a href="https://docs.aws.amazon.com/eks/latest/userguide/security_iam_troubleshoot.html#security-iam-troubleshoot-cannot-view-nodes-or-workloads">Docs</a>에서 확인할 수 있다. 
<img src="https://velog.velcdn.com/images/han-0315/post/dcc54e48-1e67-4c2a-ba75-d919811b5745/image.png" alt="">
나는 chatgpt, 구글링에서 먼저 확인한 <code>configmap</code> 에 아래와 같이 Users를 추가했지만 변화는 없었다. 이후 스터디에서 제공해주신 포스팅의 계정이 다르면 관리자 계정이 있어도 확인할 수 없다는 내용을 보고, iam user 계정으로 로그인했고 정상적으로 EKS를 확인할 수 있었다.
(아래는 IAM USER: EKS-Study로 로그인했을 때 확인한 모습이다.)
<img src="https://velog.velcdn.com/images/han-0315/post/0444e3d2-6138-4386-a3fc-915e8f11e71a/image.png" alt=""></p>
<h3 id="eks-알아보기">EKS 알아보기</h3>
<p>이제 배포된 EKS에 접근하여 여러 명령어를 실행하며 EKS에 대해 더 알아보자.
먼저, 클러스터 엔드포인트를 확인한다. 클러스터 엔드포인트의 끝을 확인하면 AWS에서 관리해주는 컨트롤 플래인의 API 서버의 퍼블릭 아이피를 확인할 수 있다. 이후 노드에 접근해서 통신상태를 확인해보면 kubelet과 kubeproxy가 api server와 통신하는 것이 확인된다.</p>
<ul>
<li>클러스터 엔드포인트 확인
```bash
#클러스터 엔드포인트 확인
$aws eks describe-cluster --name $CLUSTER_NAME | jq -r .cluster.endpoint
<a href="https://50E14FE698DE0E5CA2055F72AB086163.gr7.ap-northeast-2.eks.amazonaws.com">https://50E14FE698DE0E5CA2055F72AB086163.gr7.ap-northeast-2.eks.amazonaws.com</a>
APIDNS=$(aws eks describe-cluster --name $CLUSTER_NAME | jq -r .cluster.endpoint | cut -d &#39;/&#39; -f 3)<h1 id="dig-명령어를-통해-dns-서버-조회">dig 명령어를 통해 DNS 서버 조회</h1>
$dig +short $APIDNS</li>
</ul>
<p>3.38.85.93
3.34.237.160</p>
<h1 id="노드의-통신">노드의 통신</h1>
<h2 id="node-ip52789882-해당-파드의-ip">node ip(52.78.98.82) 해당 파드의 ip</h2>
<p>ESTAB 0      0       192.168.1.23:32816 52.95.195.109:443   users:((&quot;ssm-agent-worke&quot;,pid=2453,fd=15))</p>
<h2 id="control-plane-ip">control plane ip</h2>
<p>ESTAB 0      0       192.168.1.23:39834    3.38.85.93:443   users:((&quot;kube-proxy&quot;,pid=3104,fd=11))</p>
<h2 id="node-ip">node ip</h2>
<p>ESTAB 0      0       192.168.1.23:47340  52.95.194.61:443   users:((&quot;ssm-agent-worke&quot;,pid=2453,fd=10))</p>
<h2 id="node-ip-1">node ip</h2>
<p>ESTAB 0      0       192.168.1.23:42598    10.100.0.1:443   users:((&quot;aws-k8s-agent&quot;,pid=3426,fd=7))</p>
<h2 id="작업용-ec2-private-ip-현재-작업용-ec2에서-ssh-통신-중">작업용 ec2 private ip (현재 작업용 ec2에서 ssh 통신 중)</h2>
<p>ESTAB 0      56      192.168.1.23:22    192.168.1.100:53084 users:((&quot;sshd&quot;,pid=13342,fd=3),(&quot;sshd&quot;,pid=13310,fd=3))</p>
<h2 id="control-plane-ip-1">control plane ip</h2>
<p>ESTAB 0      0       192.168.1.23:37022  3.34.237.160:443   users:((&quot;kubelet&quot;,pid=2842,fd=39))</p>
<pre><code>이제 보안 그룹을 확인해보자. 아래의 코드를 보면 아웃바운드 트래픽은 모두 허용하고, 인바운드 트래픽은 그룹 내 트래픽에 대해 허용하는 것을 알 수 있다.
```bash
$aws ec2 describe-security-groups --group-ids sg-0866ebfa10533a0b9 --output yaml | yh
SecurityGroups:
- Description: EKS created security group applied to ENI that is attached to EKS Control
    Plane master nodes, as well as any managed workloads.
  GroupId: sg-0866ebfa10533a0b9
  GroupName: eks-cluster-sg-myeks-104368993
  IpPermissions:
  - IpProtocol: &#39;-1&#39;
    IpRanges: []
    Ipv6Ranges: []
    PrefixListIds: []
    UserIdGroupPairs:
    - GroupId: sg-0866ebfa10533a0b9
      UserId: &#39;871103481195&#39;
    - Description: Allow unmanaged nodes to communicate with control plane (all ports)
      GroupId: sg-07536909f8ca01eee
      UserId: &#39;871103481195&#39;
  IpPermissionsEgress:
  - IpProtocol: &#39;-1&#39;
    IpRanges:
    - CidrIp: 0.0.0.0/0
    Ipv6Ranges: []
    PrefixListIds: []
    UserIdGroupPairs: []
  OwnerId: &#39;871103481195&#39;
  ...</code></pre><p>이제 배포된 파드를 살펴보자, AWS에서 컨트롤 플레인을 관리하니 우리가 접근할 수 있는 파드는 워커노드에만 존재한다. 
또 다른 특이점은 daemonset으로 aws-node가 존재한다. 이것은 모든 노드에 필수적인 네트워크 요소를 설치하기 위해 생성된다. 자세한 내용은 <a href="https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/managing-vpc-cni.html">Docs</a>에서 확인할 수 있다.</p>
<pre><code class="language-bash">$k get po -A -o wide
NAMESPACE     NAME                      READY   STATUS    RESTARTS   AGE   IP              NODE                                               NOMINATED NODE   READINESS GATES
kube-system   aws-node-9bfxp            1/1     Running   0          83m   192.168.1.93    ip-192-168-1-93.ap-northeast-2.compute.internal    &lt;none&gt;           &lt;none&gt;
kube-system   aws-node-wp867            1/1     Running   0          83m   192.168.2.247   ip-192-168-2-247.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   coredns-dc4979556-n769l   1/1     Running   0          90m   192.168.1.201   ip-192-168-1-93.ap-northeast-2.compute.internal    &lt;none&gt;           &lt;none&gt;
kube-system   coredns-dc4979556-xtmxc   1/1     Running   0          90m   192.168.1.253   ip-192-168-1-93.ap-northeast-2.compute.internal    &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-dbfpz          1/1     Running   0          83m   192.168.2.247   ip-192-168-2-247.ap-northeast-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-s44d9          1/1     Running   0          83m   192.168.1.93    ip-192-168-1-93.ap-northeast-2.compute.internal    &lt;none&gt;           &lt;none&gt;
#아래는 데몬셋을 확인하는 명령어다.
$k get ds -A
NAMESPACE     NAME         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-system   aws-node     2         2         2       2            2           &lt;none&gt;          124m
kube-system   kube-proxy   2         2         2       2            2           &lt;none&gt;          124m</code></pre>
<p>이제, 선언형을 알아보기 위해 파드를 3개 생성하는 디플로이를 배포한다. 파드를 증가시키고 감소시키는 명령어를 실행시키면서 모니터링을 통해 실제 동작을 확인한다. </p>
<pre><code class="language-bash">$kubectl create deployment my-webs --image=gcr.io/google-samples/kubernetes-bootcamp:v1 --replicas=3
deployment.apps/my-webs created
$kubectl get pod -w
NAME                     READY   STATUS    RESTARTS   AGE
my-webs-8dd6b4db-8ddbz   1/1     Running   0          3s
my-webs-8dd6b4db-kwgts   1/1     Running   0          3s
my-webs-8dd6b4db-xfdbl   1/1     Running   0          3s</code></pre>
<p>아래의 그림은 강제로 파드를 삭제했을 때 나오는 결과이다. 강제로 삭제해서 파드가 일부 삭제되었지만, 이후 다시 재생성되어 파드의 수는 유지가 된다. 쿠버네티스에서는 현재의 상태와 요구되는 상태를 계속 비교하여 요구되는 상태로 지속적으로 노력하기에 그렇다.
<img src="https://velog.velcdn.com/images/han-0315/post/05930dd3-17f7-4cab-82a3-b1877b855d03/image.png" alt="">
이제 관리형 노드를 추가 및 삭제해보는 실습을 진행해봤다.</p>
<pre><code class="language-bash">#아래는 노드의 상태를 확인하기 위해 모니터링 코드
while true; do aws ec2 describe-instances --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key==&#39;Name&#39;]|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output text ; echo &quot;------------------------------&quot; ; sleep 1; done
myeks-myeks-nodegroup-Node    192.168.2.247    3.34.130.220    running
myeks-host    192.168.1.100    43.201.60.122    running
myeks-myeks-nodegroup-Node    192.168.1.93    13.124.119.49    running
------------------------------
myeks-myeks-nodegroup-Node    192.168.2.247    3.34.130.220    running
myeks-host    192.168.1.100    43.201.60.122    running
myeks-myeks-nodegroup-Node    192.168.1.93    13.124.119.49    running
...
# 노드 개수 증가
$eksctl scale nodegroup --cluster $CLUSTER_NAME --name $CLUSTER_NAME-nodegroup --nodes 3 --nodes-min 3 --nodes-max 6
2023-04-28 00:16:29 [ℹ]  scaling nodegroup &quot;myeks-nodegroup&quot; in cluster myeks
2023-04-28 00:16:30 [ℹ]  initiated scaling of nodegroup
2023-04-28 00:16:30 [ℹ]  to see the status of the scaling run `eksctl get nodegroup --cluster myeks --region ap-northeast-2 --name myeks-nodegroup
# 모니터링에서도 노드의 증가가 확인된다.
myeks-myeks-nodegroup-Node    192.168.2.247    3.34.130.220    running
myeks-host    192.168.1.100    43.201.60.122    running
myeks-myeks-nodegroup-Node    192.168.1.93    13.124.119.49    running
myeks-myeks-nodegroup-Node    192.168.1.188    3.36.51.90    running
# 노드 개수 감소
$aws eks update-nodegroup-config --cluster-name $CLUSTER_NAME --nodegroup-name $CLUSTER_NAME-nodegroup --scaling-config minSize=2,maxSize=2,desiredSize=2</code></pre>
<p>순서가 조금 다르지만, 마리오 게임에 대한 배포를 진행해봤다. 배포 <code>yaml</code> 파일을 확인하면 준비해주신 마리오 이미지를 사용하여 하나의 파드를 배포하고, 서비스를 같이 배포하여 외부에서 접근가능하도록 파드를 노출시킨다.</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: mario
  labels:
    app: mario
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mario
  template:
    metadata:
      labels:
        app: mario
    spec:
      containers:
      - name: mario
        image: pengbai/docker-supermario
---
apiVersion: v1
kind: Service
metadata:
   name: mario
spec:
  selector:
    app: mario
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080</code></pre>
<p><code>k create -f mario.yaml</code> 명령어를 통해 배포를 수행하고, 서비스에 IP를 통해 사이트에 접근하면 
<img src="https://velog.velcdn.com/images/han-0315/post/020ea2bd-0fcd-466a-937d-25ac6aae7fc2/image.png" alt=""> 마리오 게임을 확인할 수 있다.!</p>
<h3 id="ecr-사용해보기">ECR 사용해보기</h3>
<p>ECR을 이용해서 컨테이너 이미지를 관리해보는 실습을 진행했는 데, 작업은 간단하다. 
docker 로그인을 진행하고 ecr-public에 하나의 리포지토리를 만든 후 docker push 명령어를 통해 이미지를 추가하면 된다.
관련된 소스코드는 아래와 같다.</p>
<pre><code class="language-bash">#ecr login
$aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
$cat /root/.docker/config.json | jq
{
  &quot;auths&quot;: {
    &quot;public.ecr.aws&quot;: {
      &quot;auth&quot;: &quot;QVdTOm...VFY5&quot;
    }
  }
}
aws ecr-public describe-registries --region us-east-1 | jq

{
  &quot;registries&quot;: [
    {
      &quot;registryId&quot;: &quot;1234567890&quot;,
      &quot;registryArn&quot;: &quot;arn:aws:ecr-public::1234567890:registry/1234567890&quot;,
      &quot;registryUri&quot;: &quot;public.ecr.aws/~~&quot;,
      &quot;verified&quot;: false,
      &quot;aliases&quot;: [
        {
          &quot;name&quot;: &quot;~~&quot;,
          &quot;status&quot;: &quot;ACTIVE&quot;,
          &quot;primaryRegistryAlias&quot;: true,
          &quot;defaultRegistryAlias&quot;: true
        }
      ]
    }
  ]
}
aws ecr-public create-repository --repository-name $NICKNAME/nginx --region us-east-1
{
    &quot;repository&quot;: {
        &quot;repositoryArn&quot;: &quot;arn:aws:ecr-public::1234567890:repository/dongmin/nginx&quot;,
        &quot;registryId&quot;: &quot;1234567890&quot;,
        &quot;repositoryName&quot;: &quot;dongmin/nginx&quot;,
        &quot;repositoryUri&quot;: &quot;public.ecr.aws/~~/dongmin/nginx&quot;,
        &quot;createdAt&quot;: &quot;2023-05-03T00:29:28.487000+09:00&quot;
    },
    &quot;catalogData&quot;: {}
}
public.ecr.aws/g3q7n2p4/dongmin/nginx


$docker push $REPOURI:latest
The push refers to repository [public.ecr.aws/~~/dongmin/nginx]

31531248c7cb: Pushed
f9cb3f1f1d3d: Pushed
f0fb842dea41: Pushed
c1cd5c8c68ef: Pushed
1d54586a1706: Pushed
1003ff723696: Pushed
f1417ff83b31: Pushed
latest: digest: sha256:01ccf4035840dd6c25042b2b5f6b09dd265b4ed5aa7b93ccc4714027c0ce5685 size: 1781
kubectl run mynginx --image $REPOURI
pod/mynginx created</code></pre>
<p>콘솔에서 확인해봐도 정상적으로 이미지가 업로드된 것을 확인할 수 있다. 
<img src="https://velog.velcdn.com/images/han-0315/post/e1fda421-4817-43ef-b3f0-7f0420b6a167/image.png" alt=""></p>
<h3 id="엔드포인트-변경">엔드포인트 변경</h3>
<p>아래는 엔드포인트를 퍼블릭에서 public &amp; private 로 변경한 모습이다. 액세스 소스 허용목록이 작업용 ec2의 public ip로만 변경되었다.!
<img src="https://velog.velcdn.com/images/han-0315/post/b5917459-e697-441b-a002-4c7a1b7e90f9/image.png" alt="">
이제 public &amp; private 에서 private로 변경해봤다. </p>
<pre><code class="language-bash">$echo $APIDNS
7955229C1055798D3183C30048A1A1FA.gr7.ap-northeast-2.eks.amazonaws.com
$dig +short $APIDNS
192.168.1.143
192.168.2.246
### 이전의 결과값은
3.38.85.93
3.34.237.160</code></pre>
<p>public Ip를 주는 것이 아닌 private 아이피를 돌려준다. 외부를 통해 API서버로 접근하는 것이 아닌 VPC 내부에서만 컨트롤 플래인의 접근이 가능하다. 아래의 아키텍처로 구현됨을 확인할 수 있었다.
<img src="https://velog.velcdn.com/images/han-0315/post/ea5bc01b-6c5f-4b48-8fcd-f26c6646bbab/image.png" alt=""></p>
<p>스터디 중에 진행한 실습은 이렇게 마무리 되었다. 아래에는 추가적으로 진행한 실습 및 자료이다.</p>
<h2 id="추가-정리">추가 정리</h2>
<h3 id="eks-workshop">EKS Workshop</h3>
<p><strong>Pod Affinity and Anti-Affinity</strong></p>
<p><code>nodeselector</code>: 라벨(key - value)을 통해 스케줄링을 지정할 수 있다. (선호도기능은 하지 못한다.)</p>
<ul>
<li><code>nodeaffinity</code> : 특정 노드에 스케줄링되도록 유도한다.(특정 노드 배치 제안)<ul>
<li><code>requiredDuringSchedulingIgnoredDuringExecution</code> : 해당 규칙이 만족되야, 파드를 스케줄링하며 규칙이 만족되지 않으면 스케줄링할 수 없다.</li>
<li><code>preferredDuringSchedulingIgnoredDuringExecution</code> : 해당 조건을 만족하는 노드를 찾으려고 노력한다. 조건에 맞는 노드가 없으면 일반적으로 스케줄링 된다.</li>
<li>Available(조건 만족) : 스케줄링할 때만 규정을 지킨다.<ul>
<li><strong>required</strong>DuringSchedulingIgnoredDuringExecution : 해당 조건이 아니면, 스케줄링 불가능하다.</li>
<li><strong>preferred</strong>DuringSchedulingIgnoredDuringExecution  : 조건을 선호, 조건에 부합하는 노드가 없어도 가능하다.</li>
</ul>
</li>
<li>Planned(선호도) : 실행하면서도 규정을 지킴<ul>
<li><strong>required</strong>DuringSchedulingRequiredDuringExecution</li>
<li><strong>preferred</strong>DuringSchedulingRequiredDuringExecution</li>
</ul>
</li>
<li>연산자(operator)<ul>
<li><code>In</code> : 조건에 맞는 노드에 배치</li>
<li><code>NotIn</code> : 조건과 다른 노드에 배치</li>
<li><code>Exists</code> : 조건이 있는 노드에 배치</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>Taints</strong></p>
<ul>
<li>Taint : 해당 노드에 Taint가 있기에, Taint를 상쇄하는 Toleration 이 없으면 해당 노드에 배치될 수 없다.</li>
<li>기본 문법 : <code>kubectl taint nodes &lt;node-name&gt; key=value:taint-effect</code><ul>
<li>ex) <code>kubectl taint nodes node1 key1=value2:NoSchedule</code></li>
<li>effect는 아래의 3가지 가능하다.<ul>
<li><strong><code>NoSchedule</code></strong></li>
<li><strong><code>PreferNoSchedule</code></strong></li>
<li><strong><code>NoExecute</code></strong></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="kubernetes-tools">Kubernetes Tools</h3>
<p>많은 플로그인이 개발되어 활용되고 있다. <code>kubectl krew</code> 는 쿠버네티스트 cli 도구 kubect의 플로그인을 관리하는 패키지 매니저이다. krew를 통해 손쉽게 k8s 관련 플로그인을 설치할 수 있다. 폐쇄망과 같은 제약조건이 있을 때는 krew가 아닌 수동으로 설치한다.
<strong>스터디에서는 사전에 준비해주셔서 대부분 설치되어있다.</strong></p>
<pre><code class="language-bash"># krew
##intsall
(
  set -x; cd &quot;$(mktemp -d)&quot; &amp;&amp;
  OS=&quot;$(uname | tr &#39;[:upper:]&#39; &#39;[:lower:]&#39;)&quot; &amp;&amp;
  ARCH=&quot;$(uname -m | sed -e &#39;s/x86_64/amd64/&#39; -e &#39;s/\(arm\)\(64\)\?.*/\1\2/&#39; -e &#39;s/aarch64$/arm64/&#39;)&quot; &amp;&amp;
  KREW=&quot;krew-${OS}_${ARCH}&quot; &amp;&amp;
  curl -fsSLO &quot;https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz&quot; &amp;&amp;
  tar zxvf &quot;${KREW}.tar.gz&quot; &amp;&amp;
  ./&quot;${KREW}&quot; install krew
)
##환경변수 적용
export PATH=&quot;${KREW_ROOT:-$HOME/.krew}/bin:$PATH&quot;
### bash, /etc/profile 에도 업데이트하여 일치시키면 된다.
**echo &quot;export PATH=**${KREW_ROOT:-$HOME/.krew}/bin:$PATH**&quot; &gt;&gt; /etc/profile

# 플러그인 설치
$kubectl krew install ctx
$kubectl krew install ns**</code></pre>
<p>하단의 krew를 통해 대표적인 2가지 플러그인을 설치한다. 먼저, Kubectx는 kubeconfig로 연동되어 있는 context 목록을 확인할 수 있다. kubens는 현재 context의 네임스페이스를 확인할 수 있다.</p>
<p><strong>kubectx</strong>
    <code>kubectx [context_name]</code> : 특정 context로 변경
    <code>-c</code> : current context
    <code>-d</code> : context 삭제
    <code>-</code>: 이전 context로 복구</p>
<p><strong>kubens</strong>
<code>kubens [NAMESPACE_NAME]</code> : 특정 ns로 변경
<code>-c</code>: current ns
<code>-</code> :이전 name space로 복구 </p>
<pre><code class="language-bash"># kubectx 활용
**$kubectl config get-contexts
CURRENT   NAME                                                          CLUSTER                                      AUTHINFO                                                      NAMESPACE
          eks-default                                                   NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io   iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io   kube-system
*         iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io   NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io   iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io   kube-system
          minikube
$kubectx
eks-default
iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io
minikube
$kubectx -c
iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io
$kubectx minikube
Switched to context &quot;minikube&quot;.
$kubectx -c
minikube**

# kubens 활용
$k ns
default
kube-node-lease
kube-public
kube-system
$k ns kube-public
Context &quot;EKS-study@KANE.ap-northeast-2.eksctl.io&quot; modified.
Active namespace is &quot;kube-public&quot;.
$k ns
default
kube-node-lease
kube-public
kube-system
****</code></pre>
<p><strong>kube_ps1(prompt output)</strong></p>
<p>CurrentContext / CurrentNamespace를 표시하는 도구이다. </p>
<pre><code class="language-bash">#install
git clone https://github.com/jonmosco/kube-ps1.git
#.bashrc 에 추가
---
KUBE_PS1_SYMBOL_ENABLE=false
KUBE_PS1_SYMBOL_COLOR=null
KUBE_PS1_CTX_COLOR=null
KUBE_PS1_NS_COLOR=null
---
#아래는 context의 이름을 변경하여 구분하는 것이다.
#아래와 같이 변경시 확연하게 구분할 수 있다.
$cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeU1ETXdOVEU1TURNd00xb1hEVE15...y80ZHM2CnpablQyZmNCQmszNHFGUTNGS1Q1WE5pbXZoQlRidHZucUx4TQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    server: https://407B39524D80486F1EECD325C3180677.yl4.ap-northeast-2.eks.amazonaws.com
  name: NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io
contexts:
- context:
    cluster: NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io
    namespace: kube-system
    user: iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io
  name: eks-default
- context:
    cluster: NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io
    namespace: default
    user: iam-root-account@NRSON-EKS-CLUSTER.ap-northeast-2.eksctl.io
  name: kube    =================&gt; kubeapp으로 변경
current-context: kube
kind: Config
preferences: {}
users:
</code></pre>
<p><strong>유지보수관련 플러그인(모두 <code>krew</code>를 통해 설치 가능하다.)</strong></p>
<p>해당 <a href="https://waspro.tistory.com/765">블로그</a>를 참고해여 작성함.</p>
<ul>
<li><p><strong>neat : manifests 정돈에 좋다.</strong></p>
<ul>
<li><code>-o yaml</code> 으로 확인하면 관련된 상태들까지 출력이 되 보기가 불편하다.</li>
<li>ex) <code>kubectl get deployment my-nginx -o yaml | kubectl neat</code></li>
</ul>
</li>
<li><p><strong>kail : multi pod log 모니터링</strong></p>
<p>  별도로 옵션을 주지 않을 경우 클러스터 내 모든 파드를 대상으로 한다.</p>
<p>  범위를 좁히기 위해선 아래의 옵션이 있다.</p>
<pre><code class="language-bash">  $kail
  kube-logging/elasticsearch-master-745c995d88-ksldq[elasticsearch-master]: {&quot;type&quot;: &quot;server&quot;, &quot;timestamp&quot;: &quot;2022-03-07T15:41:50,374+0000&quot;, &quot;level&quot;: &quot;INFO&quot;, &quot;component&quot;: &quot;o.e.c.s.ClusterApplierService&quot;, &quot;cluster.name&quot;: &quot;elasticsearch&quot;, &quot;node.name&quot;: &quot;elasticsearch-master&quot;, &quot;cluster.uuid&quot;: &quot;w4yuNKxZRi2BxpDgrM37eg&quot;, &quot;node.id&quot;: &quot;4D8M6FE7Tsqmml7IaddNww&quot;,  &quot;message&quot;: &quot;added {{elasticsearch-client}{tHAltQHwSK-JAbfVGY-lTw}{DvjfXlbHQiubOqm2HdNCkw}{192.168.161.189}{192.168.161.189:9300}{i}{ml.machine_memory=8124866560, ml.max_open_jobs=20, xpack.installed=true},{elasticsearch-data}{lykb7kBAR5SvmTuSFs_O-g}{cgBjsrwIRWuIs2Ysf6kgkQ}{192.168.167.246}{192.168.167.246:9300}{d}{ml.machine_memory=8124866560, ml.max_open_jobs=20, xpack.installed=true},}, term: 1, version: 12, reason: Publication{term=1, version=12}&quot;  }
  kube-logging/elasticsearch-master-745c995d88-ksldq[elasticsearch-master]: {&quot;type&quot;: &quot;server&quot;, &quot;timestamp&quot;: &quot;2022-03-07T15:41:50,412+0000&quot;, &quot;level&quot;: &quot;INFO&quot;, &quot;component&quot;: &quot;o.e.x.i.a.TransportPutLifecycleAction&quot;, &quot;cluster.name&quot;: &quot;elasticsearch&quot;, &quot;node.name&quot;: &quot;elasticsearch-master&quot;, &quot;cluster.uuid&quot;: &quot;w4yuNKxZRi2BxpDgrM37eg&quot;, &quot;node.id&quot;: &quot;4D8M6FE7Tsqmml7IaddNww&quot;,  &quot;message&quot;: &quot;adding index lifecycle policy [watch-history-ilm-policy]&quot;  }
  kube-logging/elasticsearch-client-578dd48f84-lnmx8[elasticsearch-client]: {&quot;type&quot;: &quot;server&quot;, &quot;timestamp&quot;: &quot;2022-03-07T15:41:50,601+0000&quot;, &quot;level&quot;: &quot;INFO&quot;, &quot;component&quot;: &quot;o.e.x.m.e.l.LocalExporter&quot;, &quot;cluster.name&quot;: &quot;elasticsearch&quot;, &quot;node.name&quot;: &quot;elasticsearch-client&quot;, &quot;cluster.uuid&quot;: &quot;w4yuNKxZRi2BxpDgrM37eg&quot;, &quot;node.id&quot;: &quot;tHAltQHwSK-JAbfVGY-lTw&quot;,  &quot;message&quot;: &quot;waiting for elected master node [{elasticsearch-master}{4D8M6FE7Tsqmml7IaddNww}{2CWU2-ZqTeK1gCd6uklb0A}{192.168.144.190}{192.168.144.190:9300}{m}{ml.machine_memory=8124866560, ml.max_open_jobs=20, xpack.installed=true}] to setup local exporter [default_local] (does it have x-pack installed?)&quot;  }
  kube-logging/elasticsearch-data-0[elasticsearch-data]: {&quot;type&quot;: &quot;server&quot;, &quot;timestamp&quot;: &quot;2022-03-07T15:41:50,642+0000&quot;, &quot;level&quot;: &quot;INFO&quot;, &quot;component&quot;: &quot;o.e.x.m.e.l.LocalExporter&quot;, &quot;cluster.name&quot;: &quot;elasticsearch&quot;, &quot;node.name&quot;: &quot;elasticsearch-data&quot;, &quot;cluster.uuid&quot;: &quot;w4yuNKxZRi2BxpDgrM37eg&quot;, &quot;node.id&quot;: &quot;lykb7kBAR5SvmTuSFs_O-g&quot;,  &quot;message&quot;: &quot;waiting for elected master node [{elasticsearch-master}{4D8M6FE7Tsqmml7IaddNww}{2CWU2-ZqTeK1gCd6uklb0A}{192.168.144.190}{192.168.144.190:9300}{m}{ml.machine_memory=8124866560, ml.max_open_jobs=20, xpack.installed=true}] to setup local exporter [default_local] (does it have x-pack installed?)&quot;  }
  kube-logging/elasticsearch-client-578dd48f84-lnmx8[elasticsearch-client]: {&quot;type&quot;: &quot;server&quot;, &quot;timestamp&quot;: &quot;2022-03-07T15:41:50,928+0000&quot;, &quot;level&quot;: &quot;INFO&quot;, &quot;component&quot;: &quot;o.e.l.LicenseService&quot;, &quot;cluster.name&quot;: &quot;elasticsearch&quot;, &quot;node.name&quot;: &quot;elasticsearch-client&quot;, &quot;cluster.uuid&quot;: &quot;w4yuNKxZRi2BxpDgrM37eg&quot;, &quot;node.id&quot;: &quot;tHAltQHwSK-JAbfVGY-lTw&quot;,  &quot;message&quot;: &quot;license [1e86dffb-7ce3-40e5-b95f-e79a05cb5a40] mode [basic] - valid&quot;  }
  ...</code></pre>
</li>
<li><p><strong>sniff : tcp dump 생성</strong></p>
<ul>
<li><p>파드내 TCP 덤프를 생성하여 MSA 개발시 서비스간 네트워크 활동을 기록하는 데 용이다.</p>
<pre><code class="language-bash">$kubectl sniff my-nginx-6c6c46694f-qv69b
INFO[0000] using tcpdump path at: &#39;/root/.krew/store/sniff/v1.6.2/static-tcpdump&#39; 
INFO[0000] no container specified, taking first container we found in pod. 
INFO[0000] selected container: &#39;my-nginx&#39;               
INFO[0000] sniffing method: upload static tcpdump       
INFO[0000] sniffing on pod: &#39;my-nginx-6c6c46694f-qv69b&#39; [namespace: &#39;default&#39;, container: &#39;my-nginx&#39;, filter: &#39;&#39;, interface: &#39;any&#39;] 
INFO[0000] uploading static tcpdump binary from: &#39;/root/.krew/store/sniff/v1.6.2/static-tcpdump&#39; to: &#39;/tmp/static-tcpdump&#39; 
INFO[0000] uploading file: &#39;/root/.krew/store/sniff/v1.6.2/static-tcpdump&#39; to &#39;/tmp/static-tcpdump&#39; on container: &#39;my-nginx&#39; 
INFO[0000] executing command: &#39;[/bin/sh -c test -f /tmp/static-tcpdump]&#39; on container: &#39;my-nginx&#39;, pod: &#39;my-nginx-6c6c46694f-qv69b&#39;, namespace: &#39;default&#39; 
INFO[0000] command: &#39;[/bin/sh -c test -f /tmp/static-tcpdump]&#39; executing successfully exitCode: &#39;1&#39;, stdErr :&#39;&#39; 
INFO[0000] file not found on: &#39;/tmp/static-tcpdump&#39;, starting to upload 
INFO[0000] verifying file uploaded successfully         
INFO[0000] executing command: &#39;[/bin/sh -c test -f /tmp/static-tcpdump]&#39; on container: &#39;my-nginx&#39;, pod: &#39;my-nginx-6c6c46694f-qv69b&#39;, namespace: &#39;default&#39; 
INFO[0000] command: &#39;[/bin/sh -c test -f /tmp/static-tcpdump]&#39; executing successfully exitCode: &#39;0&#39;, stdErr :&#39;&#39; 
INFO[0000] file found: &#39;&#39;                               
INFO[0000] file uploaded successfully                   
INFO[0000] tcpdump uploaded successfully                
INFO[0000] spawning wireshark!                          
INFO[0000] starting sniffer cleanup                     
INFO[0000] sniffer cleanup completed successfully</code></pre>
</li>
</ul>
</li>
<li><p><strong>tree : object 간 소유권 관계 정리</strong></p>
<ul>
<li>Linux <code>tree -a</code> 와 같이 관계를 정의해준다.</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">kubectl tree deployment my-nginx        
NAMESPACE  NAME                                 READY  REASON  AGE 
default    Deployment/my-nginx                  -              136m
default    ㄴ--ReplicaSet/my-nginx-6c6c46694f   -              136m
default      ㄴ--Pod/my-nginx-6c6c46694f-qv69b  True           136m</code></pre>
<ul>
<li><p><strong>kubespy : kubespy는 kubernetes의 오브젝트에 대한 상태변화를 모니터링하는 도구이다.</strong></p>
<pre><code class="language-bash">  #install 
  $brew install kubespy
  or
  $wget https://github.com/pulumi/kubespy/releases/download/v0.6.0/kubespy-v0.6.0-linux-amd64.tar.gz
  $tar -xzvf kubespy-v0.6.0-linux-amd64.tar.gz
  $cp kubespy /usr/bin/
  #아래와 같이 추적하는 데 사용 대괄호를 통해 빠르게 표현 (added, deleted, modified)
  $kubespy trace deployment my-nginx
  [ADDED apps/v1/Deployment]  default/my-nginx
      Rolling out Deployment revision 1
      [55;8HDeployment is currently available
      [56;8HRollout successful: new ReplicaSet marked &#39;available&#39;

  ROLLOUT STATUS:
  - [Current rollout | Revision 1] [ADDED]  default/my-nginx-65cff45899
      [60;8HReplicaSet is available [2 Pods available of a 2 minimum]
         - [Ready] my-nginx-65cff45899-cmf4w
         - [Ready] my-nginx-65cff45899-kvv2t
  $kubespy status apps/v1 Deployment my-nginx
  해당 yaml 파일과 비교하여 현재의 상태를 표현한다. yaml과 비교한기에 정확히 어떤 부분에 변화가 있는 지 파악가능</code></pre>
</li>
</ul>
<h2 id="마치며">마치며</h2>
<p>첫 블로그 포스팅이어서 뿌듯하기도 하다. 하지만, 실습관련되서 정리를 깔끔하게 하지 못한 것 같아 아쉽다. </p>
]]></description>
        </item>
    </channel>
</rss>