<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ImOk.log</title>
        <link>https://velog.io/</link>
        <description>ImOk👌</description>
        <lastBuildDate>Sun, 09 Apr 2023 17:52:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ImOk.log</title>
            <url>https://velog.velcdn.com/images/imok-_/profile/cf4b19b3-4ce7-4865-83d2-4f1f65419330/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ImOk.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/imok-_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[k8s 보안]]></title>
            <link>https://velog.io/@imok-_/k8s-security</link>
            <guid>https://velog.io/@imok-_/k8s-security</guid>
            <pubDate>Sun, 09 Apr 2023 17:52:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<hr>
<h1 id="실습-환경-배포">실습 환경 배포</h1>
<pre><code class="language-bash"># CloudFormation 스택 배포
aws cloudformation deploy \
--template-file kops-oneclick-f1.yaml \
--stack-name mykops \
--parameter-overrides KeyName=kops-key \
SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 \
MyIamUserAccessKeyID=AKI... \
MyIamUserSecretAccessKey=&#39;o4H...&#39; \
ClusterBaseName=&#39;imokapp.net&#39; \
S3StateStore=&#39;imok-k8s-study&#39; \
MasterNodeInstanceType=t3.medium \
WorkerNodeInstanceType=c5d.large \
--region ap-northeast-2

# 13분 후 작업 SSH 접속
ssh -i kops-key.pem \
ec2-user@$(aws cloudformation describe-stacks \
--stack-name mykops \
--query &#39;Stacks[*].Outputs[0].OutputValue&#39; \
--output text)

kops validate cluster --wait 10m

# EC2 instance profiles 에 IAM Policy 추가(attach)
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$KOPS_CLUSTER_NAME
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$KOPS_CLUSTER_NAME

kubectl ns default</code></pre>
<hr>
<h1 id="ec2-메타데이터-호출-실습">EC2 메타데이터 호출 실습</h1>
<p><strong>IMDS(Instance Metadata Service)</strong>를 사용하여 실행 중인 인스턴스에서 인스턴스 메타데이터에 액세스할 수 있습니다.</p>
<p><a href="https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html">AWS 인스턴스 메타데이터 검색</a></p>
<h2 id="워커-노드-1대-ec2-메타데이터imdsv2-보안-제거-및-호출-확인">워커 노드 1대 EC2 메타데이터(IMDSv2) 보안 제거 및 호출 확인</h2>
<p>kops 클러스터로 배포되는 워커 노드의 인스턴스 그룹 2개 중,  첫 번째의 인스턴스 메타데이터 설정을 삭제합니다.</p>
<p>메타데이터 관련 보안 정책을 제거한 후 보안에 취약해집니다.</p>
<pre><code class="language-bash">#
kops edit ig nodes-ap-northeast-2a
---
# 아래 3줄 제거
spec:
  instanceMetadata:
    httpPutResponseHopLimit: 1
    httpTokens: required
---
# 업데이트 적용 : 노드1대 롤링업데이트
kops update cluster --yes &amp;&amp; echo &amp;&amp; sleep 3 &amp;&amp; kops rolling-update cluster --yes</code></pre>
<p>각각의 워커 노드에 접속해 메타데이터 사용이 가능한지 확인해 봅니다.</p>
<pre><code class="language-bash"># 워커 노드 Public IP 확인
aws ec2 describe-instances --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key==&#39;Name&#39;]|[0].Value}&quot; --filters Name=instance-state-name,Values=running --output table

# 워커 노드 Public IP 변수 지정
W1PIP=&lt;워커 노드 1 Public IP&gt;
W2PIP=&lt;워커 노드 2 Public IP&gt;

# 워커 노드 SSH 접속
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP
ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/773bfa04-3266-4182-8f49-6444944f64b1/image.png" alt=""></p>
<p><code>curl -v http://169.254.169.254/latest</code></p>
<p>kops 클러스터 기본 설정으로는 워커노드 2c는 다음과 같이 메타데이터를 호출할 수 없습니다.
<img src="https://velog.velcdn.com/images/imok-_/post/d81e863a-00c5-476d-8dee-b72f42659938/image.png" alt=""></p>
<p>보안을 제거한 워커노드 2a는 메타데이터를 호출할 수 있습니다.
<img src="https://velog.velcdn.com/images/imok-_/post/276bf388-f0b2-4585-b8ce-06ebae9c6615/image.png" alt=""></p>
<h2 id="pod에서-메타데이터-호출-확인">POD에서 메타데이터 호출 확인</h2>
<p>인스턴스 메타데이터 API 노드 설정은 pod까지 영향이 갑니다.</p>
<pre><code class="language-bash"># netshoot-pod 생성
cat &lt;&lt;EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-pod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: netshoot-pod
  template:
    metadata:
      labels:
        app: netshoot-pod
    spec:
      containers:
      - name: netshoot-pod
        image: nicolaka/netshoot
        command: [&quot;tail&quot;]
        args: [&quot;-f&quot;, &quot;/dev/null&quot;]
      terminationGracePeriodSeconds: 0
EOF

# 파드 이름 변수 지정
PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].metadata.name})
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].metadata.name})

# EC2 메타데이터 정보 확인
kubectl exec -it $PODNAME1 -- curl 169.254.169.254 ;echo
kubectl exec -it $PODNAME2 -- curl 169.254.169.254 ;echo</code></pre>
<p>Pod 2번에서는 다음과 같이 호출해도 아무것도 보이지 않습니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/90d0c505-240e-4b5b-9c6a-e56e18330d5e/image.png" alt=""></p>
<p>pod 1번에서는 다음과 같이 메타데이터를 호출할 수 있습니다.
<img src="https://velog.velcdn.com/images/imok-_/post/c1b67d8f-5209-4331-8432-8df4deb6ea4f/image.png" alt=""></p>
<p>또한 <code>security-credentials</code> 정보도 쉽게 출력할 수 있습니다. </p>
<pre><code class="language-bash">kubectl exec -it $PODNAME1 -- curl 169.254.169.254/latest/meta-data/iam/security-credentials/nodes.$KOPS_CLUSTER_NAME | jq
</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/4f353406-578d-4bb2-aeca-8968887b80a4/image.png" alt=""></p>
<blockquote>
<p>🚨 이처럼 Pod가 IMDS API를 사용 가능하게 된다면, 많은 <strong>보안상 위험에 노출됩니다.</strong> 
pod를 해킹해 IMDS API를 사용해서 credentials 정보를 탈취해, AWS 리소스를 생성할 수도 있습니다.</p>
</blockquote>
<hr>
<h1 id="쿠버네티스-보안-도구-활용">쿠버네티스 보안 도구 활용</h1>
<blockquote>
<p>클러스터 내부에 실행 중인 모든 파드는 기본 설정으로 클러스터 내 다른 모든 파드와 통신이 가능합니다. 
따라서 특정 파드가 외부 공격자에 노출되면, 같은 노드 내 다른 파드는 물론 통신 가능한 클러스터 전체의 <strong>다른 노드에서 실행 중인 모든 파드까지 영향을 끼칩니다.</strong></p>
</blockquote>
<h4 id="쿠버네티스-보안-적용-대상-4c">쿠버네티스 보안 적용 대상 4C</h4>
<p>각 계층 마다 각각 적절한 보안 설정을 적용해 전체적으로 안전한 시스템을 만들 필요가 있습니다.</p>
<ol>
<li>클라우드(Cloud)</li>
<li>클러스터(Cluster)</li>
<li>컨테이너(Container)</li>
<li>코드(Code)</li>
</ol>
<p><a href="https://kubernetes.io/docs/concepts/security/overview/#the-4c-s-of-cloud-native-security">The 4C&#39;s of Cloud Native security</a></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/58b60858-58dc-44c2-91ed-a121c19fa395/image.png" alt=""></p>
<hr>
<h2 id="kubescape">kubescape</h2>
<p>미국 NSA에서 발행한 쿠버네티스 보안 체크리스트를 기준으로 현재 클러스터의 취약점을 점검하고 이를 대시보드 형태로 리포트합니다.</p>
<p><a href="https://github.com/kubescape/kubescape">kubescape github</a></p>
<p><img src="https://github.com/kubescape/kubescape/raw/master/docs/img/architecture.drawio.svg" alt=""></p>
<h4 id="nsacisa-의-쿠버네티스-클러스터-보안-가이드">NSA/CISA 의 쿠버네티스 클러스터 보안 가이드</h4>
<ol>
<li>컨테이너의 root 사용자 권한 제거</li>
<li>컨테이너 내부 파일 시스템 쓰기 권한 제거</li>
<li>컨테이너의 불필요한 추가 특권 제거</li>
<li>리소스 제한 설정</li>
<li>불필요한 외부 접속 제한을 위한 네트워크 정책 적용</li>
<li>호스트 노드 보안 강화(SELinux 등 사용)</li>
</ol>
<blockquote>
<p>📝 실습 내용</p>
</blockquote>
<ol>
<li>kubescape를 설치 및 실행</li>
<li>NSA/CISA 보안 권고 사항 대비 현재 클러스터의 보안 취약점 확인</li>
</ol>
<hr>
<p>kubesacpe 설치</p>
<pre><code class="language-bash"># 설치
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash

kubescape version
Your current version is: v2.2.6 [git enabled in build: true]</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/0dfddb6d-85c6-4b56-b97e-f11622930829/image.png" alt=""></p>
<p>제공하는 보안 프레임워크들을 확인해 봅니다.</p>
<pre><code class="language-bash"># Download all artifacts and save them in the default path (~/.kubescape)
kubescape download artifacts
tree ~/.kubescape/
cat ~/.kubescape/attack-tracks.json | jq

# 제공하는 보안 프레임워크 확인
kubescape list frameworks --format json | jq &#39;.[]&#39;
&quot;AllControls&quot;
&quot;ArmoBest&quot;
&quot;DevOpsBest&quot;
&quot;MITRE&quot;
&quot;NSA&quot;
&quot;cis-eks-t1.2.0&quot;
&quot;cis-v1.23-t1.0.1&quot;

# 제공하는 통제 정책 확인
kubescape list controls</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/aa3bc93d-0944-45d6-9959-381ae4f1dd12/image.png" alt=""></p>
<p>kubesacpe를 사용해서 보안 취약점을 확인해 보겠습니다.</p>
<pre><code class="language-bash">kubescape scan --enable-host-scan --verbose
kubescape scan framework nsa -e kubesys-tem,kube-public</code></pre>
<p>클러스터 보안 점검 결과 많은 보안 취약점이 발견된 것을 볼 수 있습니다. 😭
<img src="https://velog.velcdn.com/images/imok-_/post/aa618e7a-6987-4d35-a08d-cc9080465945/image.png" alt=""></p>
<h3 id="kubescape-web">kubescape web</h3>
<p>kubescape에서 제공하는 web을 통해 보안 취약점을 확인해 봅시다.
회원가입 후 10개의 worker노드까지 무료로 사용할 수 있습니다.
<a href="https://cloud.armosec.io/">https://cloud.armosec.io/</a> </p>
<p>회원 가입 후 들어가면 다음과 같이 설치 명령어가 나옵니다.
<img src="https://velog.velcdn.com/images/imok-_/post/54a86005-eccb-4b7b-8cf1-4038775278cf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/058c5be0-99df-4fee-a260-c0b126f126d5/image.png" alt=""></p>
<h4 id="compliance">Compliance</h4>
<p>클러스터의 보안과 규정 준수를 유지하기 위해 보안 위험을 신속하게 발견, 평가 및 해결합니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/b5e6a97a-48ea-4b77-871e-f62998ca6b6e/image.png" alt=""></p>
<p>다음과 같이 보안이 필요한 항목에 대해 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f207fed3-3763-4c5a-aae9-5b48cadc9514/image.png" alt=""></p>
<h4 id="vulnerabilities">Vulnerabilities</h4>
<p>모든 클러스터에서 알려진 취약점 또는 새로운 취약점을 감지합니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/6f976afa-8cc8-45ce-addf-968a6ccb6494/image.png" alt=""></p>
<h4 id="rbac-visualizer">RBAC Visualizer</h4>
<p>RBAC 구성 상태를 시각화합니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/69288b3b-503b-4d6c-b177-a950be1c0f10/image.png" alt=""></p>
<hr>
<h2 id="polaris">polaris</h2>
<p> 보안 체크리스트 뿐만 아니라 가용성, 안정성 측면에서 모범 사례 대비 현재 매니페스트 yaml 파일의 부족한 점을 파악할 수 있습니다. 해결 방법도 다른 도구에 비해 좀 더 직관적 입니다.
<a href="https://github.com/FairwindsOps/polaris">polaris github</a></p>
<p><img src="https://camo.githubusercontent.com/cf8dc8d0a68de78de27ef0e156c7192e41269388509d89c847589367048774e7/68747470733a2f2f706f6c617269732e646f63732e6661697277696e64732e636f6d2f696d672f6172636869746563747572652e737667" alt=""></p>
<blockquote>
<p>📝 실습 내용</p>
</blockquote>
<ol>
<li>헬름 차트를 이용해 redis 설치 <a href="https://artifacthub.io/packages/helm/fairwinds-stable/polaris">Polaris 헬름 차트</a></li>
<li>보안 점검도구 polaris를 이용해 레디스 파드의 취약점을 확인하고 이를 수정</li>
</ol>
<h3 id="redis-설치">redis 설치</h3>
<p>인메모리 기반 데이터베이스인 레디스를 헬름 기반으로 설치합니다.</p>
<pre><code class="language-bash"># redis 헬름 차트 설치
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm pull bitnami/redis
tar xvfz redis-17.9.3.tgz
cd redis/
cp values.yaml my-values.yaml

# 배포
k create ns redis
k ns redis
helm install redis bitnami/redis --namespace redis -f my-values.yaml

# pod 확인
k get pod</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/76df2ee8-fded-43af-be2f-885d168c2514/image.png" alt=""></p>
<h3 id="polaris-설치-및-redis-보안-취약점-확인">polaris 설치 및 redis 보안 취약점 확인</h3>
<p>폴라리스를 설치해 보안 취약점을 확인합니다.</p>
<pre><code class="language-bash"># polaris 헬름 차트 설치
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm repo update
helm pull fairwinds-stable/polaris
tar xvfz polaris-5.7.3.tgz
cd polaris/
cp values.yaml my-values.yaml</code></pre>
<p>LoadBalancer로 변경
<img src="https://velog.velcdn.com/images/imok-_/post/22f29f11-0c6e-4fc6-aac5-3972b5412fe0/image.png" alt=""></p>
<pre><code class="language-bash">k create ns polaris
k ns polaris

# 배포
helm install polaris fairwinds-stable/polaris --namespace polaris --version 5.7.3 -f my-values.yaml
# CLB에 ExternanDNS 로 도메인 연결
kubectl annotate service polaris-dashboard &quot;external-dns.alpha.kubernetes.io/hostname=polaris.$KOPS_CLUSTER_NAME&quot; -n polaris
# 웹 접속 주소 확인 및 접속
echo -e &quot;Polaris Web URL = http://polaris.$KOPS_CLUSTER_NAME&quot;

# pod 확인
k get pod
# 웹 서비스 포트 확인
k get svc
# 노드 ip 주소 확인
k get node -o wide
</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2fda4387-6372-4e62-a768-b16280e50073/image.png" alt=""></p>
<p>앞에서 설치한 redis 애플리케이션의 상세한 권고 사항을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/imok-_/post/54ba78a6-5cfa-4cda-9137-e237d191186f/image.png" alt=""></p>
<p>❓ 버튼을 클릭하면, 해당 사항에 대하 자세한 설명을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/333f1482-b2f0-4b02-93f9-0ed056cf670e/image.png" alt=""></p>
<p>polaris pod의 매니페스트 yaml 파일은 모범 사례로 구성돼 있기 때문에 해당 yaml 파일을 참고해서 다른 애플리케이션을 수정해 적용할 수 있습니다.
구체적인 설정을 확인하기 위해 실행 중인 polaris pod 설정을 확인해 봅니다.
<code>k get pod polaris-dashboard-78 -o yaml &gt; polaris-dashboard-pod.yaml</code></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/d63cb5da-aed7-464e-944b-f18ffde10d99/image.png" alt=""></p>
<p>해당 파일을 참고해서 redis 헬름 차트의 yaml 파일에 <code>securityContext.allowPrivilegeEscalation</code> 설정을 추가해봅니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/1ff4d463-4f61-430c-978d-462f684998ea/image.png" alt=""></p>
<pre><code class="language-bash"># redis 재설치
helm upgrade redis bitnami/redis --namespace redis -f my-values.yaml</code></pre>
<p>polaris 웹에서 변경 사항을 확인합니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/1d31b27d-4090-4bc0-8537-8674e26e5f18/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Monitoring (2) - kwatch & prometheus alertmanager]]></title>
            <link>https://velog.io/@imok-_/k8s-kwatch</link>
            <guid>https://velog.io/@imok-_/k8s-kwatch</guid>
            <pubDate>Sat, 01 Apr 2023 23:05:45 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<hr>
<h1 id="kwatch">kwatch</h1>
<blockquote>
<p>kwatch helps you monitor all changes in your Kubernetes(K8s) cluster, detects crashes in your running apps in realtime, and publishes notifications to your channels (Slack, Discord, etc.) instantly</p>
</blockquote>
<p><a href="https://github.com/abahmed/kwatch">kwatch gitHub</a>
<a href="https://artifacthub.io/packages/helm/kwatch/kwatch">kwatch Helm</a></p>
<pre><code class="language-bash"># configmap 생성
cat &lt;&lt;EOT &gt; ~/kwatch-config.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: kwatch
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: kwatch
  namespace: kwatch
data:
  config.yaml: |
    alert:
      slack:
        webhook: &#39;https://hooks.slack.com/services/슬랙주소&#39;
        #title:
        #text:
    pvcMonitor:
      enabled: true
      interval: 5
      threshold: 70
EOT

# 배포
kubectl apply -f kwatch-config.yaml
</code></pre>
<h4 id="잘못된-이미지-파드-배포-및-확인">잘못된 이미지 파드 배포 및 확인</h4>
<pre><code class="language-bash"># 터미널1
watch kubectl get pod

# 잘못된 이미지 정보의 파드 배포
kubectl apply -f https://raw.githubusercontent.com/junghoon2/kube-books/main/ch05/nginx-error-pod.yml
kubectl get events -w

# 이미지 업데이트 방안2 : set 사용 - iamge 등 일부 리소스 값을 변경 가능!
kubectl set 
kubectl set image pod nginx-19 nginx-pod=nginx:1.19

# 삭제
kubectl delete pod nginx-19</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/d549bcb5-c352-4358-b81e-36aa70ceb5e2/image.png" alt=""></p>
<hr>
<h1 id="prometheus-alertmanager">prometheus alertmanager</h1>
<p>프로메테우스는 장애 등의 메시지 전달 기능을 얼럿매니저로 분리해서 관리합니다.
경고 메시지가 발생하면, 이를 얼럿매니저에 푸시 이벤트로 전달하고, 얼럿메니저는 이를 그룹화, 일시 중지(silence) 등의 가공 과정을 거쳐 이메일, 슬랙 등으로 전달합니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/8db5f8cc-0902-4550-a653-08b552ac75b9/image.png" alt=""></p>
<blockquote>
<p>프로메테우스의 임곗값 도달 시 경고 메시지를 얼럿매니저에 푸시 이벤트로 전달 -&gt; 얼럿매니저는 이를 가공후 이메일/슬랙 등에 전달</p>
</blockquote>
<p><a href="https://prometheus.io/docs/alerting/latest/alertmanager/">prometheus alertmanager</a></p>
<p>워커 노드 3번 추가 <a href="https://kops.sigs.k8s.io/tutorial/working-with-instancegroups/">링크</a></p>
<ul>
<li>노드 추가 시 프로메테우스의 Target들이 자동으로 발견(Auto Discovery)되어서 등록이 되고, 메트릭 수집이 되는지 확인</li>
</ul>
<pre><code class="language-bash"># EC2 인스턴스 모니터링
while true; do aws ec2 describe-instances --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key==&#39;Name&#39;]|[0].Value,Status:State.Name}&quot; --output text | sort; echo &quot;------------------------------&quot; ;date; sleep 1; done

# 인스턴스그룹 정보 확인
kops get ig

# 노드 추가
kops edit ig nodes-ap-northeast-2a --set spec.minSize=2 --set spec.maxSize=2

# 적용
kops update cluster --yes &amp;&amp; echo &amp;&amp; sleep 3 &amp;&amp; kops rolling-update cluster

# 워커노드 증가 확인
while true; do kubectl get node; echo &quot;------------------------------&quot; ;date; sleep 1; done</code></pre>
<h2 id="얼럿매니저-웹-접속--얼럿매니저-대시보드-karma-사용">얼럿매니저 웹 접속 &amp; 얼럿매니저 대시보드 karma 사용</h2>
<p><a href="https://github.com/prymitive/karma">karma github</a></p>
<h4 id="얼럿매니저-웹-접속">얼럿매니저 웹 접속</h4>
<pre><code class="language-bash"># ingress 도메인으로 웹 접속
echo -e &quot;Alertmanager Web URL = https://alertmanager.$KOPS_CLUSTER_NAME&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/38e8298f-0b01-4cdb-bebb-e31d9f244f60/image.png" alt=""></p>
<blockquote>
<ol>
<li>Alerts 경고: 시스템 문제 시 프로메테우스가 전달한 경고 메시지 목록을 확인</li>
<li>Silences 일시 중지 : 계획 된 장애 작업 시 일정 기간 동안 경고 메시지를 받지 않을 때, 메시지별로 경고 메시지를 일시 중단 설정</li>
<li>Statue 상태 : 얼럿매니저 상세 설정 확인</li>
</ol>
</blockquote>
<h4 id="얼럿매니저-대시보드-karma-컨테이너로-실행">얼럿매니저 대시보드 karma 컨테이너로 실행</h4>
<pre><code class="language-bash"># 실행
docker run -d -p 80:8080 -e ALERTMANAGER_URI=https://alertmanager.$KOPS_CLUSTER_NAME ghcr.io/prymitive/karma:latest

# 확인
docker ps

# karma 웹 접속 주소 확인
echo -e &quot;karma Web URL = http://$(aws cloudformation describe-stacks --stack-name mykops --query &#39;Stacks[*].Outputs[0].OutputValue&#39; --output text)&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/68fcf5f4-da23-418b-81f3-b8826959c7cc/image.png" alt=""></p>
<h2 id="프로메테우스-웹-alert--얼럿매니저-웹-karma">프로메테우스 웹 Alert &amp; 얼럿매니저 웹 karma</h2>
<h4 id="프로메테우스-웹-접속-후-상단-alert-메뉴-확인">프로메테우스 웹 접속 후 상단 Alert 메뉴 확인</h4>
<blockquote>
<ul>
<li><strong>Inactive</strong> 비활성화 : prometheusrules 중 경고가 활성화되지 않은 <strong>정상</strong>적인 상태</li>
</ul>
</blockquote>
<ul>
<li><strong>Pending</strong> 지연 : 설정한 <strong>임곗값</strong>을 초과해 경고 상황이지만 경고 메시지를 전달하기까지 <strong>임곗값 시간</strong>을 초과하지 않은 상태, 이를 통해 오탐과 자동 복구된 에러 메시지를 처리</li>
<li><strong>Firing</strong> 경보 : 임곗값과 임곗값 시간을 초과해서 <strong>경보</strong>가 발생한 메시지. 해당 메시지는 <strong>얼럿매니저</strong>에 <strong>전달</strong>되어 얼럿매니저를 통해 <strong>전파</strong>됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/imok-_/post/890a908b-edb3-4edb-8d1d-614ea4f70fe4/image.png" alt=""></p>
<pre><code class="language-bash"># 프로메테우스 룰 확인
kubectl get prometheusrules -n monitoring
kubectl get prometheusrules -n monitoring kube-prometheus-stack-kubernetes-system-controller-manager -o json | jq

# 룰 전체 확인
kubectl get prometheusrules -n monitoring -o json | more

# 메트릭 이름 확인
kubectl get prometheusrules -n monitoring -o json | grep &#39;&quot;record&quot;:&#39; | sed &#39;s/^ *//&#39;
kubectl get prometheusrules -n monitoring -o json | grep &#39;&quot;record&quot;:&#39; | sed &#39;s/^ *//&#39; | wc -l

# 얼럿 이름 확인
kubectl get prometheusrules -n monitoring -o json | grep &#39;&quot;alert&quot;:&#39; | sed &#39;s/^ *//&#39;

# 얼럿 갯수 확인
kubectl get prometheusrules -n monitoring -o json | grep &#39;&quot;alert&quot;:&#39; | sed &#39;s/^ *//&#39; | wc -l
134</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/cbfbf722-24d9-4309-ace9-cdd46ae29933/image.png" alt=""></p>
<h4 id="얼럿매니저-대시보드-karma-웹-접속-확인">얼럿매니저 대시보드 karma 웹 접속 확인</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2c3a0c7f-16cd-46a6-8508-050820c30957/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Monitoring (3) - PLG Stack]]></title>
            <link>https://velog.io/@imok-_/k8s-loki</link>
            <guid>https://velog.io/@imok-_/k8s-loki</guid>
            <pubDate>Sun, 26 Mar 2023 14:44:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<hr>
<h1 id="plg-stack">PLG Stack</h1>
<p>Promtail + Loki + Grafana 여러 파드의 로그들을 중앙 서버에 저장하고 이를 조회
<a href="https://grafana.com/docs/loki/latest/">grafana loki</a></p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/0862f7967545b9ebe1041764e9427a8bf0f44a08/6b8ba/assets/img/uploads/2020/04/image2.png" alt="">
<a href="https://www.infracloud.io/blogs/logging-in-kubernetes-efk-vs-plg-stack/">PLG Stack</a></p>
<h4 id="loki">Loki</h4>
<p>오픈소스 소프트웨어로, 쿠버네티스 환경에서 종료된 파드를 포함한 전체 파드의 로그를 중앙 시스템에서 조회할 수 있음</p>
<blockquote>
<ul>
<li>Loki에 저장한 로그는 LogQL(PromQL과 유사)을 이용해 조회 할 수 있으며, 그라파나 웹이나 logcli를 이용해 조회 가능</li>
<li>기존 로그 통합 시스템과 달리 전체 로그 파일 단위로 인덱싱 하지 않고, 레이블 기반의 메타데이터만 인덱싱해서 인덱스에 소요되는 메모리 사용량이 적다.</li>
</ul>
</blockquote>
<h4 id="promtail">Promtail</h4>
<p>쿠버네티스 환경에 특화된 에이전트로서 도커, FluentD 등 다른 로그 수집 에이전트도 사용 가능</p>
<blockquote>
<ul>
<li>Promtail은 데몬셋으로 실행되며 각 로그에 로그를 중앙 로키 서버에 전달</li>
</ul>
</blockquote>
<h2 id="loki--promtail-설치">Loki &amp; Promtail 설치</h2>
<h4 id="loki-설치">Loki 설치</h4>
<pre><code class="language-bash"># 모니터링
kubectl create ns loki
watch kubectl get pod,pvc,svc,ingress -n loki

# Repo 추가
helm repo add grafana https://grafana.github.io/helm-charts

# 파라미터 설정 파일 생성
cat &lt;&lt;EOT &gt; ~/loki-values.yaml
persistence:
  enabled: true
  size: 20Gi

serviceMonitor:
  enabled: true
EOT

# 배포
helm install loki grafana/loki --version 2.16.0 -f loki-values.yaml --namespace loki</code></pre>
<pre><code class="language-bash"># 설치 확인 : 데몬셋, 스테이트풀셋, PVC 확인
helm list -n loki
kubectl get pod,pvc,svc,ds,sts -n loki
kubectl get-all -n loki
kubectl get servicemonitor -n loki
kubectl krew install df-pv &amp;&amp; kubectl df-pv

# curl 테스트 용 파드 생성
kubectl delete -f ~/pkos/2/netshoot-2pods.yaml
kubectl apply -f ~/pkos/2/netshoot-2pods.yaml

# 로키 gateway 접속 확인
kubectl exec -it pod-1 -- curl -s http://loki.loki.svc:3100/api/prom/label

# (참고) 삭제 시
helm uninstall loki -n loki
kubectl delete pvc -n loki --all</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/c69e2b54-8f62-4214-9b2c-c63b10c10270/image.png" alt=""></p>
<h4 id="promtail-설치">Promtail 설치</h4>
<pre><code class="language-bash"># 파라미터 설정 파일 생성
cat &lt;&lt;EOT &gt; ~/promtail-values.yaml
serviceMonitor:
  enabled: true
config:
  serverPort: 3101
  clients:
    - url: http://loki-headless:3100/loki/api/v1/push
#defaultVolumes:
#  - name: pods
#    hostPath:
#      path: /var/log/pods
EOT

# 배포
helm install promtail grafana/promtail --version 6.0.0 -f promtail-values.yaml --namespace loki

# (참고) 파드 로그는 /var/log/pods에 저장
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME ls /var/log/pods

# 설치 확인 : 데몬셋, 스테이트풀셋, PVC 확인
helm list -n loki
kubectl get pod,pvc,svc,ds,sts,servicemonitor -n loki
kubectl get-all -n loki

# (참고) 삭제 시
helm uninstall promtail -n loki</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/21cf888c-69d3-46e6-b595-780189d041d8/image.png" alt=""></p>
<hr>
<h1 id="그라파나">그라파나</h1>
<p><img src="https://velog.velcdn.com/images/imok-_/post/02e42bca-9223-4386-9348-1ffefa1a5fe7/image.png" alt=""></p>
<p>그라파나 → Configuration → Data Source : 데이터 소스 추가 → Loki 클릭 →<code>HTTP URL : http://loki-headless.loki:3100</code> → Save &amp; Test</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2e4afed8-6f2a-4950-9ed3-7f68c1b53762/image.png" alt=""></p>
<h4 id="nginx-반복-접속">nginx 반복 접속</h4>
<pre><code class="language-bash"># 접속 주소 확인 및 접속
kubectl logs deploy/nginx -f

# 반복 접속
while true; do curl -s http://nginx2.$KOPS_CLUSTER_NAME -I | head -n 1; date; sleep 1; done</code></pre>
<p>그라파나 → Explore : 상단 데이터 소스 Loki 선택 → Logfilters : Job → default/nginx 값 선택 ⇒ 우측 상단 Run query 클릭</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/12abdb78-5103-43a9-b1c9-7d7f5e8df671/image.png" alt=""></p>
<p>임의의 로그 클릭 : 상세 정보 확인 - 파드 레이블, 네임스페이스, 잡 등 전체 로그 레이블 확인 가능
<img src="https://velog.velcdn.com/images/imok-_/post/26ba731f-7a58-40fa-9acb-b1624c3c4df0/image.png" alt=""></p>
<p>Loki 로그 확인 대시보드 : 15141</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/94052a4d-c66a-47f5-a56d-6ecfc2765d99/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Monitoring (1) - 프로메테우스 & 그라파나]]></title>
            <link>https://velog.io/@imok-_/k8s-prometheus</link>
            <guid>https://velog.io/@imok-_/k8s-prometheus</guid>
            <pubDate>Sun, 26 Mar 2023 14:41:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<hr>
<h1 id="쿠버네티스-모니터링">쿠버네티스 모니터링</h1>
<p>쿠버네티스 모니터링 솔루션으로는 프로메테우스(Prometheus), 스카우터(Scouter) 등과 같은 오픈소스 솔루션, 데이터독(DataDog), 뉴렐릭(new relic) 등의 상용 솔루션으로 분류됩니다.</p>
<h4 id="쿠버네티스-환경의-모니터링-대상">쿠버네티스 환경의 모니터링 대상</h4>
<blockquote>
<ol>
<li>노드와 컨테이너 자원 사용량 모니터링<ul>
<li>CPU, 메모리, 네트워크, 스토리지 등</li>
</ul>
</li>
<li>클러스터 모니터링<ul>
<li>쿠버네티스 오브젝트의 전체 수량, 종류 등 전반적인 현황과 피드 재시작, 이벤트 메시지 등, 장애와 관련된 모니터링 </li>
</ul>
</li>
<li>애플리케이션 모니터링<ul>
<li>컨트롤 플레인 파드(etcd, apiserver, coredns),페이지 응답 속도, 세션 수, 데이터베이스 쿼리 응답 속도 등 사용자 체감 정보</li>
</ul>
</li>
</ol>
</blockquote>
<hr>
<h1 id="실습-환경-배포">실습 환경 배포</h1>
<pre><code class="language-bash">aws cloudformation deploy \
--template-file kops-oneclick-f1.yaml \
--stack-name mykops \
--parameter-overrides KeyName=kops-key \
SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 \
MyIamUserAccessKeyID=AKI... \
MyIamUserSecretAccessKey=&#39;o4H...&#39; \
ClusterBaseName=&#39;imokapp.net&#39; \
S3StateStore=&#39;imok-k8s-study&#39; \
MasterNodeInstanceType=c5a.2xlarge \
WorkerNodeInstanceType=c5a.2xlarge \
--region ap-northeast-2

ssh -i kops-key.pem ec2-user@$(aws cloudformation describe-stacks --stack-name mykops --query &#39;Stacks[*].Outputs[0].OutputValue&#39; --output text)

kops validate cluster --wait 10m

kubectl ns default</code></pre>
<hr>
<h1 id="프로메테우스">프로메테우스</h1>
<blockquote>
<p><strong>Prometheus</strong> : SoundCloud에서 만든 오픈소스 시스템 모니터링 및 경고 툴킷</p>
</blockquote>
<ul>
<li><a href="https://prometheus.io/">공식문서</a></li>
<li><a href="https://prometheus-operator.dev/">prometheus-operator</a></li>
</ul>
<h4 id="features">Features</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/0c6eccbe-0e2c-4429-b15e-c3ba77bbc1a4/image.png" alt=""></p>
<ol>
<li>시계열 데이터베이스(time-series database, TSDB) 사용</li>
<li>강력한 queries PromQL 사용</li>
<li>다양한 애플리케이션 익스포터 제공 - HAProxy, StatsD, MySQL</li>
<li>Pull 방식 - 중앙 서버가 모니터링 대상의 정보를 직접 가져오는 방식 ( &lt;&gt; Push 방식 - 에이전트가 중앙 서버로 모니터링 정보를 전달하는 방식 )</li>
<li>서비스 디스커버리 - 개별 모니터링 대상을 서비스 엔드포인트로 등록해 자동으로 변경 내역 감지</li>
</ol>
<h4 id="architecture">Architecture</h4>
<p><img src="https://prometheus.io/assets/architecture.png" alt=""></p>
<h2 id="프로메테우스-설치">프로메테우스 설치</h2>
<blockquote>
<p><strong>프로메테우스-스택 설치</strong> : 모니터링에 필요한 여러 요소를 단일 스택으로 제공</p>
<ul>
<li><a href="https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack">kube prometheus stack Helm</a></li>
</ul>
</blockquote>
<pre><code class="language-bash"># 모니터링
kubectl create ns monitoring
watch kubectl get pod,pvc,svc,ingress -n monitoring

# 사용 리전의 인증서 ARN 확인
CERT_ARN=`aws acm list-certificates --query &#39;CertificateSummaryList[].CertificateArn[]&#39; --output text`
echo &quot;alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN&quot;

# 설치
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/e52759ca-e369-494e-9452-6e93cf9afa8b/image.png" alt=""></p>
<pre><code class="language-bash"># 파라미터 파일 생성
cat &lt;&lt;EOT &gt; ~/monitor-values.yaml
alertmanager:
  ingress:
    enabled: true
    ingressClassName: alb

    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/group.name: &quot;monitoring&quot;

    hosts:
      - alertmanager.$KOPS_CLUSTER_NAME

    paths:
      - /*

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

  ingress:
    enabled: true
    ingressClassName: alb

    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/group.name: &quot;monitoring&quot;

    hosts:
      - grafana.$KOPS_CLUSTER_NAME

    paths:
      - /*

prometheus:
  ingress:
    enabled: true
    ingressClassName: alb

    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/group.name: &quot;monitoring&quot;

    hosts:
      - prometheus.$KOPS_CLUSTER_NAME

    paths:
      - /*

  prometheusSpec:
    podMonitorSelectorNilUsesHelmValues: false
    serviceMonitorSelectorNilUsesHelmValues: false
    retention: 5d
    retentionSize: &quot;10GiB&quot;
EOT


# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 45.7.1 \
--set prometheus.prometheusSpec.scrapeInterval=&#39;15s&#39; --set prometheus.prometheusSpec.evaluationInterval=&#39;15s&#39; \
-f monitor-values.yaml --namespace monitoring</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/9abbc192-f54b-4304-8672-f24275a55889/image.png" alt=""></p>
<pre><code class="language-bash">
# 확인
## alertmanager-0 : 사전에 정의한 정책 기반(예: 노드 다운, 파드 Pending 등)으로 시스템 경고 메시지를 생성 후 경보 채널(슬랙 등)로 전송
## grafana : 프로메테우스는 메트릭 정보를 저장하는 용도로 사용하며, 그라파나로 시각화 처리
## prometheus-0 : 모니터링 대상이 되는 파드는 ‘exporter’라는 별도의 사이드카 형식의 파드에서 모니터링 메트릭을 노출, pull 방식으로 가져와 내부의 시계열 데이터베이스에 저장
## node-exporter : 노드익스포터는 물리 노드에 대한 자원 사용량(네트워크, 스토리지 등 전체) 정보를 메트릭 형태로 변경하여 노출
## operator : 시스템 경고 메시지 정책(prometheus rule), 애플리케이션 모니터링 대상 추가 등의 작업을 편리하게 할수 있게 CRD 지원
## kube-state-metrics : 쿠버네티스의 클러스터의 상태(kube-state)를 메트릭으로 변환하는 파드
helm list -n monitoring
kubectl get pod,svc,ingress -n monitoring
kubectl get-all -n monitoring
kubectl get prometheus,alertmanager -n monitoring
kubectl get prometheusrule -n monitoring
kubectl get servicemonitors -n monitoring
kubectl get crd | grep monitoring</code></pre>
<h2 id="프로메테우스-기본-사용">프로메테우스 기본 사용</h2>
<h4 id="메트릭-정보-저장">메트릭 정보 저장</h4>
<ul>
<li>모니터링 대상이 되는 서비스는 일반적으로 자체 웹 서버의 <code>/metrics</code> 엔드포인트 경로에 다양한 메트릭 정보를 노출<ul>
<li>이후 프로메테우스는 해당 경로에 http get 방식으로 메트릭 정보를 가져와 TSDB 형식으로 저장<pre><code class="language-bash"># 아래 처럼 프로메테우스가 각 서비스의 9100 접속하여 메트릭 정보를 수집
kubectl get node -owide
kubectl get svc,ep -n monitoring kube-prometheus-stack-prometheus-node-exporter
</code></pre>
</li>
</ul>
</li>
</ul>
<h1 id="마스터노드에-lynx-설치">마스터노드에 lynx 설치</h1>
<p>ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME hostname
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME sudo apt install lynx -y</p>
<h1 id="노드의-9100번의-metrics-접속-시-다양한-메트릭-정보를-확인할수-있음--마스터-이외에-워커노드도-확인-가능">노드의 9100번의 /metrics 접속 시 다양한 메트릭 정보를 확인할수 있음 : 마스터 이외에 워커노드도 확인 가능</h1>
<p>ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME lynx -dump localhost:9100/metrics</p>
<pre><code>![](https://velog.velcdn.com/images/imok-_/post/9aa5f2db-5a2a-4b72-98e3-32ff34732edf/image.png)


#### 프로메테우스 도메인 접속

```bash
# ingress 확인
kubectl get ingress -n monitoring kube-prometheus-stack-prometheus
kubectl describe ingress -n monitoring kube-prometheus-stack-prometheus

# 프로메테우스 ingress 도메인으로 웹 접속
echo -e &quot;Prometheus Web URL = https://prometheus.$KOPS_CLUSTER_NAME&quot;</code></pre><p><strong>웹 상단 주요 메뉴 설명</strong></p>
<blockquote>
<ol>
<li>경고(Alert) : 사전에 정의한 시스템 경고 정책(Prometheus Rules)에 대한 상황</li>
<li>그래프(Graph) : 프로메테우스 자체 검색 언어 PromQL을 이용하여 메트릭 정보를 조회 -&gt; 단순한 그래프 형태 조회</li>
<li>상태(Status) : 경고 메시지 정책(Rules), 모니터링 대상(Targets) 등 다양한 프로메테우스 설정 내역을 확인 &gt; 버전(2.42.0)</li>
<li>도움말(Help)</li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/imok-_/post/8089ab15-96f4-4f26-aa13-66f8e9b827a0/image.png" alt=""></p>
<h4 id="프로메테우스-설정-확인">프로메테우스 설정 확인</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/4ee56940-c204-4740-9ac1-dfc8bcbba7f6/image.png" alt=""></p>
<h4 id="메트릭을-그래프로-조회">메트릭을 그래프로 조회</h4>
<p>Graph &gt; 아래 PromQL 쿼리(전체 클러스터 노드의 CPU 사용량 합계)입력 후 조회 &gt; Graph 확인</p>
<pre><code class="language-bash">1- avg(rate(node_cpu_seconds_total{mode=&quot;idle&quot;}[1m]))</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/c1dba634-1fa3-46b8-a48a-53ce2681a09b/image.png" alt=""></p>
<hr>
<h1 id="그라파나">그라파나</h1>
<p>그라파나는 다양한 데이터소스를 토대로 사용자 대시보드를 제공하는 솔루션입니다.
프로메테우스 데이터, 퍼블릭 클라우드, 라즈베리파이 등과 같은 데이터 소스, 로그, 메트릭 등 다양한 데이터 형식을 지원합니다.</p>
<blockquote>
<p>TSDB 데이터를 시각화
다양한 데이터 형식 지원(메트릭, 로그, 트레이스 등) 
그라파나는 시각화 솔루션으로 데이터 자체를 저장하지 않음</p>
</blockquote>
<ul>
<li><a href="https://grafana.com/">그라파나 공식 문서</a></li>
</ul>
<h4 id="황금-신호golden-signals">황금 신호(Golden-Signals)</h4>
<p>구글이 공유한 모니터링 항목의 주요 요소</p>
<ol>
<li><strong>지연(Latency)</strong><ul>
<li>사용자 요청에 응답하는 소요 시간</li>
<li>데이터베이스가 웹 서비스의 쿼리에 응답하는 데 걸리는 시간</li>
</ul>
</li>
<li><strong>트래픽(Traffic)</strong><ul>
<li>웹 서비스에 대한 초당 HTTP 요청 또는 페이지 로드</li>
<li>데이터베이스, 스토리지 서비스에 대한 초당 트랜젝션</li>
</ul>
</li>
<li><strong>오류(Errors)</strong><ul>
<li>여러 가지 이유로 특정 요청이 실패했을 때 발생</li>
</ul>
</li>
<li><strong>포화도(Saturation)</strong><ul>
<li>트래픽 증가, 노드 장애 등의 상황이 발생했을 때 예비 노드에서 처리 가능한 시스템 자원 사용률</li>
<li>주어진 리소스가 한 번에 얼마나 많이 소비되고 있는지 측정</li>
</ul>
</li>
</ol>
<h2 id="그라파나-기본-사용">그라파나 기본 사용</h2>
<p>접속 정보 확인 및 로그인 : 기본 계정 <code>admin / prom-operator</code></p>
<pre><code class="language-bash"># ingress 확인
kubectl get ingress -n monitoring kube-prometheus-stack-grafana
kubectl describe ingress -n monitoring kube-prometheus-stack-grafana

# ingress 도메인으로 웹 접속
echo -e &quot;Grafana Web URL = https://grafana.$KOPS_CLUSTER_NAME&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/6ac34e44-f14a-4a00-8346-7cd3bc3fb4b3/image.png" alt=""></p>
<p><strong>웹 주요 메뉴 설명</strong></p>
<blockquote>
<ol>
<li>Search dashboards : 대시보드 검색</li>
<li>Starred : 즐겨찾기 대시보드</li>
<li>Dashboards : 대시보드 전체 목록 확인</li>
<li>Explore : 쿼리 언어 PromQL를 이용해 메트릭 정보를 그래프 형태로 탐색</li>
<li>Alerting : 경고, 에러 발생 시 사용자에게 경고를 전달</li>
<li>Configuration : 설정, 예) 데이터 소스 설정 등</li>
<li>Server admin : 사용자, 조직, 플러그인 등 설정</li>
<li>admin : admin 사용자의 개인 설정</li>
</ol>
</blockquote>
<p>Configuration → Data sources : 스택의 경우 자동으로 프로메테우스를 데이터 소스로 추가해둠 
<img src="https://velog.velcdn.com/images/imok-_/post/3a929a22-69c9-4052-b8a1-ba32d3c8d3d3/image.png" alt=""></p>
<pre><code class="language-bash"># 서비스 주소 확인
kubectl get svc,ep -n monitoring kube-prometheus-stack-prometheus
NAME                                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/kube-prometheus-stack-prometheus   ClusterIP   100.70.252.201   &lt;none&gt;        9090/TCP   92m

NAME                                         ENDPOINTS          AGE
endpoints/kube-prometheus-stack-prometheus   172.30.55.6:9090   92m</code></pre>
<p>해당 데이터 소스 접속 확인</p>
<pre><code class="language-bash"># 테스트용 파드 배포
kubectl apply -f ~/pkos/2/netshoot-2pods.yaml
kubectl get pod

# 접속 확인
kubectl exec -it pod-1 -- nslookup kube-prometheus-stack-prometheus.monitoring
kubectl exec -it pod-1 -- curl -s kube-prometheus-stack-prometheus.monitoring:9090/graph -v

# 삭제
kubectl delete -f ~/pkos/2/netshoot-2pods.yaml</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/93bfa338-a42e-4cf5-bba6-8384470f9859/image.png" alt=""></p>
<h2 id="대시보드-사용">대시보드 사용</h2>
<h4 id="기본-대시보드">기본 대시보드</h4>
<p>스택을 통해서 설치된 기본 대시보드 확인 : Dashboards → Browse
<img src="https://velog.velcdn.com/images/imok-_/post/f6491452-ff55-4973-a928-4b906edbaa81/image.png" alt=""></p>
<h4 id="공식-대시보드-가져오기">공식 대시보드 가져오기</h4>
<p><a href="https://grafana.com/grafana/dashboards/?pg=docs-grafana-latest-dashboards">공식 대시보드 링크</a>
<a href="https://grafana.com/orgs/imrtfm/dashboards">추천 대시보드</a></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/012130a3-73b2-4c62-82e7-80957bd18c2b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/3a1da5db-62fe-4567-8931-ae2ce82c7ff9/image.png" alt=""></p>
<hr>
<h1 id="nginx-웹서버-배포-및-애플리케이션-모니터링-설정-및-접속">NGINX 웹서버 배포 및 애플리케이션 모니터링 설정 및 접속</h1>
<h4 id="서비스-모니터-동작">서비스 모니터 동작</h4>
<p><a href="https://containerjournal.com/topics/container-management/cluster-monitoring-with-prometheus-operator/">cluster-monitoring-with-prometheus-operator</a></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/a835ca10-a2ea-4101-8cf3-c3e473d09507/image.png" alt=""></p>
<h2 id="nginx-웹-서버-helm-설치">nginx 웹 서버 helm 설치</h2>
<p>[nginx stack Helm] (<a href="https://artifacthub.io/packages/helm/bitnami/nginx">https://artifacthub.io/packages/helm/bitnami/nginx</a>)</p>
<pre><code class="language-bash"># help repo add
helm repo add bitnami https://charts.bitnami.com/bitnami

# 파라미터 파일 생성 : 서비스 모니터 방식으로 nginx 모니터링 대상을 등록하고, export 는 9113 포트 사용, nginx 웹서버 노출은 AWS CLB 기본 사용
cat &lt;&lt;EOT &gt; ~/nginx-values.yaml
metrics:
  enabled: true

  service:
    port: 9113

  serviceMonitor:
    enabled: true
    namespace: monitoring
    interval: 10s
EOT

# 배포
helm install nginx bitnami/nginx --version 13.2.23 -f nginx-values.yaml</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/81c4632c-e122-4cbc-ad1c-2d6ec511b653/image.png" alt=""></p>
<pre><code class="language-bash"># CLB에 ExternanDNS 로 도메인 연결
kubectl annotate service nginx &quot;external-dns.alpha.kubernetes.io/hostname=nginx.$KOPS_CLUSTER_NAME&quot;

# 확인
kubectl get pod,svc,ep
kubectl get servicemonitor -n monitoring nginx
kubectl get servicemonitor -n monitoring nginx -o json | jq

# nginx 파드내에 컨테이너 갯수 확인
kubectl get pod -l app.kubernetes.io/instance=nginx
kubectl describe pod -l app.kubernetes.io/instance=nginx

# 접속 주소 확인 및 접속
echo -e &quot;Nginx WebServer URL = http://nginx.$KOPS_CLUSTER_NAME&quot;
curl -s http://nginx.$KOPS_CLUSTER_NAME
kubectl logs deploy/nginx -f

# 반복 접속
while true; do curl -s http://nginx.$KOPS_CLUSTER_NAME -I | head -n 1; date; sleep 1; done</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/b90baa64-1521-4ec5-b36c-e7e30db708a5/image.png" alt=""></p>
<h2 id="프로메테우스-웹서버-확인">프로메테우스 웹서버 확인</h2>
<p>state &gt; target에 nginx 서비스 모니터 추가 확인
<img src="https://velog.velcdn.com/images/imok-_/post/b38fbb18-8fb9-4212-803e-5bada4ee6427/image.png" alt=""></p>
<h2 id="그라파나-대시보드-추가">그라파나 대시보드 추가</h2>
<p><code>12708</code> 대시보드 추가</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/120e2ece-28ab-485a-b872-f058397f0bd1/image.png" alt=""></p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[GitOps 시스템 (3) - ArgoCD]]></title>
            <link>https://velog.io/@imok-_/gitops-argocd</link>
            <guid>https://velog.io/@imok-_/gitops-argocd</guid>
            <pubDate>Sat, 25 Mar 2023 19:56:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<hr>
<h1 id="아르고시디argocd를-활용한-깃옵스gitops-시스템-구축">아르고시디(ArgoCD)를 활용한 깃옵스(GitOps) 시스템 구축</h1>
<p>ArgoCD : 쿠버네티스를 위한 GitOps도구</p>
<ul>
<li>공식문서 : <a href="https://argo-cd.readthedocs.io/en/stable/">ArgoCD</a></li>
</ul>
<p><img src="https://velog.velcdn.com/images/imok-_/post/023a79f7-b4c0-48c5-a697-f3bd5b44a099/image.webp" alt=""></p>
<h2 id="헬름-차트로-argocd-설치-후-웹-로그인">헬름 차트로 ArgoCD 설치 후 웹 로그인</h2>
<p><a href="https://artifacthub.io/packages/helm/argo/argo-cd">Argo CD Chart</a></p>
<pre><code class="language-bash"># 모니터링
kubectl create ns argocd
watch kubectl get pod,pvc,svc -n argocd

# 설치
cd
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd --set server.service.type=LoadBalancer --namespace argocd --version 5.19.14

# 확인
# argocd-application-controller : 실행 중인 k8s 애플리케이션의 설정과 깃 저장소의 소스 파일에 선언된 상태를 서로 비교하는 컨트롤러. 상태와 다르면 ‘OutOfSync’ 에러를 출력.
# argocd-dex-server : 외부 사용자의 LDAP 인증에 Dex 서버를 사용할 수 있음
# argocd-repo-server : 원격 깃 저장소의 소스 코드를 아르고시디 내부 캐시 서버에 저장합니다. 디렉토리 경로, 소스, 헬름 차트 등이 저장.
helm list -n argocd
kubectl get pod,pvc,svc,deploy,sts -n argocd
kubectl get-all -n argocd

kubectl get crd | grep argoproj
applications.argoproj.io              2023-03-25T20:11:47Z
applicationsets.argoproj.io           2023-03-25T20:11:47Z
appprojects.argoproj.io               2023-03-25T20:11:47Z</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/dde8f0b9-e50c-42f0-b782-1574a0e28d65/image.png" alt=""></p>
<pre><code class="language-bash"># CLB에 ExternanDNS 로 도메인 연결
kubectl annotate service -n argocd argocd-server &quot;external-dns.alpha.kubernetes.io/hostname=argocd.$KOPS_CLUSTER_NAME&quot;

# admin 계정의 암호 확인
ARGOPW=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=&quot;{.data.password}&quot; | base64 -d)
echo $ARGOPW
scn...

# 웹 접속 로그인 (admin) CLB의 DNS 주소로 접속
echo -e &quot;Argocd Web URL = https://argocd.$KOPS_CLUSTER_NAME&quot;
</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/4cf01e77-94f5-4ed4-9b04-ccb707e97831/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/0925bc4a-a51b-4b49-a166-7dc780ff9539/image.png" alt=""></p>
<hr>
<h2 id="argocd-cli-도구-설치">argocd CLI 도구 설치</h2>
<p><a href="https://argo-cd.readthedocs.io/en/stable/cli_installation/">argocd cli</a></p>
<pre><code class="language-bash"># 최신버전 설치
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
chmod +x /usr/local/bin/argocd

# 버전 확인
argocd version --short

# Help
# argocd app : 쿠버네티스 애플리케이션 동기화 상태 확인
# argocd context : 복수의 쿠버네티스 클러스터 등록 및 선택
# argocd login : 아르고시디 서버에 로그인 
# argocd repo : 원격 깃 저장소를 등록하고 현황 파악
argocd

# argocd 서버 로그인
argocd login argocd.$KOPS_CLUSTER_NAME --username admin --password $ARGOPW</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/7276bf5d-9463-4d60-b387-4128cd5fcd48/image.png" alt=""></p>
<p>전 단계에서 설치한 깃랩의 프로젝트 URL 을 argocd 깃 리포지토리(argocd repo)로 등록. 깃랩은 프로젝트 단위로 소스 코드를 보관.</p>
<pre><code class="language-bash">argocd repo add https://gitlab.$KOPS_CLUSTER_NAME/&lt;깃랩 계정명&gt;/test-stg.git --username &lt;깃랩 계정명&gt; --password &lt;깃랩 계정 암호&gt;

# 등록 확인 : 기본적으로 아르고시디가 설치된 쿠버네티스 클러스터는 타깃 클러스터로 등록됨
argocd repo list
TYPE  NAME  REPO                                          INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
git         https://gitlab.imokapp.net/imok/test-stg.git  false     false  false  true   Successful
(imokapp:default) [root@kops-ec2 test-stg]# argocd cluster list

# 기본적으로 아르고시디가 설치된 쿠버네티스 클러스터는 타깃 클러스터로 등록됨
argocd cluster list
SERVER                          NAME        VERSION  STATUS   MESSAGE                                                  PROJECT
https://kubernetes.default.svc  in-cluster           Unknown  Cluster has no applications and is not being monitored.
</code></pre>
<hr>
<h2 id="argocd를-이용하여-rabbitmq-헬름-애플리케이션-배포하기">ArgoCD를 이용하여 RabbitMQ 헬름 애플리케이션 배포하기</h2>
<p>RabbitMQ Helm 깃랩 업로드 - <a href="https://artifacthub.io/packages/helm/bitnami/rabbitmq">RabbitMQ</a></p>
<pre><code class="language-bash"># test-stg 깃 디렉터리에서 아래 실행
cd ~/gitlab-test/test-stg

# 깃 원격 오리진 주소 확인
git config -l | grep remote.origin.url

# RabbitMQ 헬름 차트 설치
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm fetch bitnami/rabbitmq --untar --version 11.10.3
cd rabbitmq/
cp values.yaml my-values.yaml

# 헬름 차트를 깃랩 저장소에 업로드
git add . &amp;&amp; git commit -m &quot;add rabbitmq helm&quot;
git push

# argocd CRD 확인
kubectl get crd | grep argo
applications.argoproj.io                              2023-03-25T20:11:47Z   # 배포 앱 현재 실행 상태와 깃 저장소의 의도한 상태를 계속 비교
appprojects.argoproj.io                               2023-03-25T20:11:47Z   # 프로젝트 단위 구분
argocdextensions.argoproj.io                          2023-03-25T20:11:47Z</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/0e385e7c-ab02-45f0-be6d-bafbe69b93e5/image.png" alt=""></p>
<pre><code class="language-bash"># 수정
cd ~/
curl -s -O https://raw.githubusercontent.com/wikibook/kubepractice/main/ch15/rabbitmq-helm-argo-application.yml
vim rabbitmq-helm-argo-application.yml
--------------------------------------
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: rabbitmq-helm
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: rabbitmq
    server: https://kubernetes.default.svc
  project: default
  source:
    repoURL: https://gitlab.imokapp.net/imok/test-stg.git
    path: rabbitmq
    targetRevision: HEAD
    helm:
      valueFiles:
      - my-values.yaml
  syncPolicy:
    syncOptions:
    - CreateNamespace=true
--------------------------------------</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/cfbdd71d-ef5d-4195-8fd0-f70c39af9b25/image.png" alt=""></p>
<pre><code class="language-bash"># 모니터링 : argocd 웹 화면 보고 있기!
echo -e &quot;Argocd Web URL = https://argocd.$KOPS_CLUSTER_NAME&quot;

# 배포
kubectl apply -f rabbitmq-helm-argo-application.yml

# YAML 파일을 적용(apply)하여 아르고시디 ‘Application’ CRD를 생성
kubectl get applications.argoproj.io -n argocd
NAME            SYNC STATUS   HEALTH STATUS
rabbitmq-helm   OutOfSync     Missing</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f32f7f3e-b2a0-4636-802b-bb74acc66da2/image.png" alt=""></p>
<p>argocd 웹 화면 rabbitmq 클릭 &gt; 헬름 차트 상세 내역 확인 &gt; 화면 상단 SYNC 클릭해 동기화 실행</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f30302c7-811a-40d0-900c-d70a447853eb/image.png" alt=""></p>
<pre><code class="language-bash"># 모니터링
watch kubectl get pod,pvc -n rabbitmq

# 배포 확인
kubectl get all,svc,cm -n rabbitmq

# sts 파드 1개에서 2개로 증가 설정 후 argocd 웹 화면 모니터링
kubectl scale statefulset -n rabbitmq rabbitmq-helm --replicas 2</code></pre>
<p>sync 확인</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/854e441f-d08a-4d0d-be82-080ee94725b8/image.png" alt=""></p>
<hr>
<h1 id="실습-환경-삭제">실습 환경 삭제</h1>
<p>헬름 차트 삭제</p>
<pre><code class="language-bash">kubectl delete application -n argocd rabbitmq-helm  # 미 삭제되어 있을 경우 삭제
helm uninstall -n argocd argocd
kubectl delete ns argocd
kubectl delete ns rabbitmq</code></pre>
<p>kOps 클러스터 삭제 &amp; AWS CloudFormation 스택 삭제</p>
<pre><code class="language-bash">kops delete cluster --yes &amp;&amp; aws cloudformation delete-stack --stack-name mykops</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[GitOps 시스템 (2) - GitLab]]></title>
            <link>https://velog.io/@imok-_/gitops-gitlab</link>
            <guid>https://velog.io/@imok-_/gitops-gitlab</guid>
            <pubDate>Sat, 25 Mar 2023 18:25:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<hr>
<h1 id="깃랩gitlab를-이용하여-로컬-깃git-소스-저장소-구축하기">깃랩(GitLab)를 이용하여 로컬 깃(Git) 소스 저장소 구축하기</h1>
<blockquote>
<p>🎯 자신만의 텍스트 파일을 kops-ec2 로컬에서 Gitlab Repo에 올려보고, 다운로드 받아보기</p>
</blockquote>
<h2 id="헬름-차트로-gitlab-설치-후-웹-로그인">헬름 차트로 gitlab 설치 후 웹 로그인</h2>
<p><a href="https://artifacthub.io/packages/helm/gitlab/gitlab">Cloud Native GitLab Helm Chart</a>
<a href="https://docs.gitlab.com/charts/">GitLab Helm chart</a></p>
<pre><code class="language-bash"># 설치
echo $CERT_ARN
helm repo add gitlab https://charts.gitlab.io/
helm repo update
helm fetch gitlab/gitlab --untar --version 6.8.1
vim ~/gitlab/values.yaml
----------------------
global:
  hosts:
    domain: &lt;각자자신의도메인&gt;             # 52줄
    https: true

  ingress:                             # 66줄~
    configureCertmanager: false
    provider: aws
    class: alb
    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/group.name: &quot;gitlab&quot;
    tls:                               # 79줄
      enabled: false
----------------------</code></pre>
<p><code>alb.ingress.kubernetes.io/group.name: &quot;gitlab&quot;</code> 을 설정하면, ALB 1대만으로 동작 가능</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/c1a7e6b4-8fd1-44ab-9ca3-2a72c4871dce/image.png" alt=""></p>
<pre><code class="language-bash"># 모니터링
kubectl create ns gitlab
watch kubectl get pod,pvc,ingress -n gitlab
# 설치
helm install gitlab gitlab/gitlab -f ~/gitlab/values.yaml \
--set certmanager.install=false \
--set nginx-ingress.enabled=false \
--set prometheus.install=false \
--set gitlab-runner.install=false \
--namespace gitlab --version 6.8.4

# 확인 - SubCharts
# gitlab-gitaly : 웹서비스 혹은 ssh 방식으로 진행되는 깃 제목, 브랜치, 태그 등의 깃 요청 등에 대한 작업을 담당
# gitlab-gitlab-shell : https 가 아닌 ssh 방식으로 깃 명령어 실행 시 해당 요청을 처리
# gitlab-kas : gitlab agent server
# gitlab-postgresql : 유저, 권한, 이슈 등 깃랩의 메타 데이터 정보가 저장
# gitlab-redis-master : 깃랩 작업 정보는 레디스 캐시 서버를 이용하여 처리
# gitlab-sidekiq-all-in-1-v2 : 레디스와 연동하여 작업 큐 처리 용도로 사용
# gitlab-webservice-default : 깃랩 웹 서비스를 처리
helm list -n gitlab
kubectl get pod,pvc,ingress,deploy,sts -n gitlab
kubectl df-pv -n gitlab
kubectl get-all -n gitlab</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/3034e117-582b-4c2b-b73e-6a11d432fb76/image.png" alt=""></p>
<pre><code class="language-bash"># 4개의 Ingress 가 1개의 ALB를 공유해서 사용 : ALB의 Rule 확인해볼것!
# alb.ingress.kubernetes.io/group.name: &quot;gitlab&quot;
kubectl get ingress -n gitlab
NAME                        CLASS   HOSTS                  ADDRESS                                                            PORTS   AGE
gitlab-kas                  alb     kas.imokapp.net        k8s-gitlab-806aafdd2a-290919404.ap-northeast-2.elb.amazonaws.com   80      8m15s
gitlab-minio                alb     minio.imokapp.net      k8s-gitlab-806aafdd2a-290919404.ap-northeast-2.elb.amazonaws.com   80      8m15s
gitlab-registry             alb     registry.imokapp.net   k8s-gitlab-806aafdd2a-290919404.ap-northeast-2.elb.amazonaws.com   80      8m15s
gitlab-webservice-default   alb     gitlab.imokapp.net     k8s-gitlab-806aafdd2a-290919404.ap-northeast-2.elb.amazonaws.com   80      8m15s</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/3a94667b-75be-4767-9bf1-d5b792c39e20/image.png" alt=""></p>
<pre><code class="language-bash"># 웹 root 계정 암호 확인
kubectl get secrets -n gitlab gitlab-gitlab-initial-root-password --template={{.data.password}} | base64 -d ;echo
hhBvAjXo...

# 웹 접속 주소 확인 및 접속
echo -e &quot;gitlab URL = https://gitlab.$KOPS_CLUSTER_NAME&quot;

# 웹 접속 https://gitlab.&lt;각자 자신의 도메인&gt; (root / 웹 root 계정 암호)</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/de015ff9-0507-4138-a521-a9eb2179425a/image.png" alt=""></p>
<hr>
<h2 id="gitlab에-별도의-사용자-생성">gitlab에 별도의 사용자 생성</h2>
<p>Admins &gt; Users</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2eee0f1c-5773-4cf3-b57d-03933e36c1b9/image.png" alt=""></p>
<p>Administrator 권한 부여
<img src="https://velog.velcdn.com/images/imok-_/post/0a881d28-bd59-493e-8d81-a25dfa47102b/image.png" alt=""></p>
<p>Impersonation Tokens 생성 - 토큰 값 확인
<img src="https://velog.velcdn.com/images/imok-_/post/84c502b2-06b0-4722-af61-7393f5586559/image.png" alt=""></p>
<p>password 설정 &gt; root 계정 로그아웃 &gt; imok 계정 로그인</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/539180fa-f68e-4e4b-b387-71ab04020ded/image.png" alt=""></p>
<p>깃랩 신규 프로젝트 작성
<img src="https://velog.velcdn.com/images/imok-_/post/f58de298-5a5a-48de-a328-7fa815c11d72/image.png" alt=""></p>
<hr>
<h2 id="gitlab에-파일-업로드">gitlab에 파일 업로드</h2>
<pre><code class="language-bash">mkdir ~/gitlab-test &amp;&amp; cd ~/gitlab-test

# git 계정 초기화 : 토큰 및 로그인 실패 시 매번 실행해주자
git config --system --unset credential.helper
git config --global --unset credential.helper

# git 계정 정보 확인 및 global 계정 정보 입력
git config --list
git config --global user.name &quot;&lt;각자 자신의 Gialba 계정&gt;&quot;
git config --global user.email &quot;&lt;각자 자신의 Gialba 계정의 이메일&gt;&quot;

# git clone
git clone https://gitlab.$KOPS_CLUSTER_NAME/&lt;각자 자신의 Gitlab 계정&gt;/test-stg.git
Cloning into &#39;test-stg&#39;...
Username for &#39;https://gitlab.imokapp.net&#39;: imok
Password for &#39;https://imok@gitlab.imokapp.net&#39;:&lt;토큰 입력&gt;

# 이동
ls -al test-stg &amp;&amp; cd test-stg &amp;&amp; pwd

# 파일 생성 및 깃 업로드(push) : 웹에서 확인
echo &quot;gitlab test memo&quot; &gt;&gt; test.txt
git add . &amp;&amp; git commit -m &quot;initial commit - add test.txt&quot;
git push
Username for &#39;https://gitlab.imokapp.net&#39;: imok
Password for &#39;https://imok@gitlab.imokapp.net&#39;:&lt;토큰 입력&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f372b0ff-0b54-41db-8115-c3723ab65d03/image.png" alt=""></p>
<hr>
<h1 id="실습-환경-삭제">실습 환경 삭제</h1>
<p>헬름 차트 삭제</p>
<pre><code class="language-bash">helm uninstall -n gitlab gitlab
kubectl delete pvc --all -n gitlab
kubectl delete ns gitlab</code></pre>
<p>kOps 클러스터 삭제 &amp; AWS CloudFormation 스택 삭제</p>
<pre><code class="language-bash">kops delete cluster --yes &amp;&amp; aws cloudformation delete-stack --stack-name mykops</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[GitOps 시스템 (1) - Harbor ]]></title>
            <link>https://velog.io/@imok-_/gitops-harbor</link>
            <guid>https://velog.io/@imok-_/gitops-harbor</guid>
            <pubDate>Sat, 25 Mar 2023 16:03:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<hr>
<h1 id="실습-환경-배포">실습 환경 배포</h1>
<pre><code class="language-bash">aws cloudformation deploy \
--template-file kops-oneclick-f1.yaml \
--stack-name mykops \
--parameter-overrides
KeyName=kops-key \
SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  \
MyIamUserAccessKeyID=AKIA... \
MyIamUserSecretAccessKey=o4... \
ClusterBaseName=&#39;imokapp.net&#39; \
S3StateStore=&#39;imok-k8s-study&#39; \
MasterNodeInstanceType=c5a.2xlarge \
WorkerNodeInstanceType=c5a.2xlarge \
--region ap-northeast-2</code></pre>
<pre><code class="language-bash"># CloudFormation 스택 배포 완료 후 kOps EC2 IP 출력
aws cloudformation describe-stacks --stack-name mykops --query &#39;Stacks[*].Outputs[0].OutputValue&#39; --output text

# SSH 접속
ssh -i kops-key.pem ec2-user@$(aws cloudformation describe-stacks --stack-name mykops --query &#39;Stacks[*].Outputs[0].OutputValue&#39; --output text)

# EC2 instance profiles 에 IAM Policy 추가(attach)
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$KOPS_CLUSTER_NAME
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$KOPS_CLUSTER_NAME

# 메트릭 서버 확인 : 메트릭은 15초 간격으로 cAdvisor를 통하여 가져옴
kubectl top node</code></pre>
<h2 id="도커-엔진-설치-확인">도커 엔진 설치 확인</h2>
<pre><code class="language-bash"># default NS 진입
kubectl ns default

# 도커 설치
amazon-linux-extras install docker -y
systemctl start docker &amp;&amp; systemctl enable docker

# 설치된 패키지 확인 : 도커 엔진 확인
yum list installed

# 도커 정보 확인 : client - server, Docker Root Dir, Registry
docker info

# 도커 정보 확인 : Docker Engine - Community
docker version

# 도커 서비스 상태 확인
systemctl status docker

# 모든 서비스의 상태 표시 - 링크
systemctl list-units --type=service

# 도커 루트 디렉터리 확인
tree -L 3 /var/lib/docker</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/0df81e5f-5fac-4932-a2a2-f7866a2de9af/image.png" alt=""></p>
<hr>
<h1 id="하버harbor를-이용하여-로컬-컨테이너-이미지-저장소-구축하기">하버(Harbor)를 이용하여 로컬 컨테이너 이미지 저장소 구축하기</h1>
<blockquote>
<p>🎯 Harbor 에 아무 이미지나 태그해서 업로드하고 다운로드 해보고, 파드/디플로이먼트 YAML 배포 시 해당 이미지 주소를 사용해보기</p>
</blockquote>
<h2 id="헬름-차트로-하버-설치">헬름 차트로 하버 설치</h2>
<p><a href="https://artifacthub.io/packages/helm/harbor/harbor">Helm Chart for Harbor</a>
<a href="https://goharbor.io/docs/2.7.0/install-config/harbor-ha-helm/">Deploying Harbor with High Availability via Helm</a></p>
<pre><code class="language-bash"># 사용 리전의 인증서 ARN 확인
aws acm list-certificates --query &#39;CertificateSummaryList[].CertificateArn[]&#39; --output text
CERT_ARN=`aws acm list-certificates --query &#39;CertificateSummaryList[].CertificateArn[]&#39; --output text`
echo &quot;alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN&quot;

# 하버 설치
helm repo add harbor https://helm.goharbor.io
helm fetch harbor/harbor --untar --version 1.11.0
vim ~/harbor/values.yaml
----------------------
expose.tls.certSource=none                        # 19줄
#expose.ingress.hosts.core=harbor.&lt;각자자신의도메인&gt;    # 36줄
#expose.ingress.hosts.notary=notary.&lt;각자자신의도메인&gt;  # 37줄
expose.ingress.hosts.core=harbor.imokapp.net
expose.ingress.hosts.notary=notary.imokapp.net
expose.ingress.controller=alb                      # 44줄
expose.ingress.className=alb                       # 47줄
#expose.ingress.annotations=alb.ingress.kubernetes.io/scheme: internet-facing # 51줄
#expose.ingress.annotations=alb.ingress.kubernetes.io/target-type: ip
#expose.ingress.annotations=alb.ingress.kubernetes.io/listen-ports: &#39;[{&quot;HTTPS&quot;:443}, {&quot;HTTP&quot;:80}]&#39;
#expose.ingress.annotations=alb.ingress.kubernetes.io/certificate-arn: ${CERT_ARN}   # 각자 자신의 값으로 수정입력
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}   # 각자 자신의 값으로 수정입력
#externalURL=https://harbor.&lt;각자자신의도메인&gt;          # 131줄
externalURL=https://harbor.imokapp.net           
-------------------</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/a4740a45-fda5-4bb7-860c-99681e69712a/image.png" alt=""></p>
<pre><code class="language-bash"># 모니터링
kubectl create ns harbor
watch kubectl get pod,pvc,ingress -n harbor

# 설치
helm install harbor harbor/harbor -f ~/harbor/values.yaml --namespace harbor --version 1.11.0

# 확인
# registry : 컨테이너 이미지를 저장
# chartmuseum : 하버를 컨테이너 이미지뿐 아니라, 헬름 차트 리포지토리로도 사용
# notary : 서명이 완료된 컨테이너 이미지만 운영 환경에 사용하도록 설정. 서명이 완료된 이미지는 별도로 구분
# trivy : 컨테이너 이미지의 보안 취약점을 스캔, 스캔 기능은 별도 솔루션에서 제공하여 관리자는 보안 스캔용 도구를 선택 가능
helm list -n harbor
kubectl get-all -n harbor
kubectl get pod,pvc,ingress,deploy,sts -n harbor
kubectl get ingress -n harbor harbor-ingress -o json | jq
kubectl krew install df-pv &amp;&amp; kubectl df-pv

# 웹 접속 주소 확인 및 접속
echo -e &quot;harbor URL = https://harbor.$KOPS_CLUSTER_NAME&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/87b66c58-a7fc-432f-9e8c-ba47ae421d76/image.png" alt=""></p>
<hr>
<h2 id="하버-웹-접속-및-로컬-이미지-업로드">하버 웹 접속 및 로컬 이미지 업로드</h2>
<ul>
<li>로그인 : admin/Harbor12345</li>
<li>NEW PROJECT → Name(<strong>pkos</strong>), Access Level(Public Check) ⇒ OK 클릭</li>
</ul>
<p><img src="https://velog.velcdn.com/images/imok-_/post/e5f342ff-3d1b-4006-ad26-9bf51d31d99c/image.png" alt=""></p>
<pre><code class="language-bash"># 컨테이너 이미지 가져오기
docker pull nginx &amp;&amp; docker pull busybox &amp;&amp; docker images

# 태그 설정
docker tag busybox harbor.$KOPS_CLUSTER_NAME/pkos/busybox:0.1
docker image ls

# 로그인 - 방안2
echo &#39;Harbor12345&#39; &gt; harborpw.txt
cat harborpw.txt | docker login harbor.$KOPS_CLUSTER_NAME -u admin --password-stdin
cat /root/.docker/config.json | jq

# 이미지 업로드
docker push harbor.$KOPS_CLUSTER_NAME/pkos/busybox:0.1</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/c7b077f9-7595-42f4-abbf-7e6acb5ac1db/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/aa452ede-5a65-420b-b4a3-0c9bfa5599ba/image.png" alt=""></p>
<hr>
<h2 id="harbor-저장소-이미지를-사용하는-디플로이먼트-생성하기">harbor 저장소 이미지를 사용하는 디플로이먼트 생성하기</h2>
<p>쿠버네티스 YAML 파일의 컨테이너 이미지 저장소 주소를 로컬 하버로 변경</p>
<pre><code class="language-bash"># 파드 배포
curl -s -O https://raw.githubusercontent.com/junghoon2/kube-books/main/ch13/busybox-deploy.yml
sed -i &quot;s|harbor.myweb.io/erp|harbor.$KOPS_CLUSTER_NAME/pkos|g&quot; busybox-deploy.yml
kubectl apply -f busybox-deploy.yml

# 확인 : 정상적으로 harbor 에서 이미지 다운로드되어 파드가 동작!
kubectl get pod

kubectl describe pod | grep Events: -A7</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/edcac747-303f-43df-80b7-2bc26d4d649f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/3e6c23c9-6728-4f11-858e-d625b6d02d27/image.png" alt=""></p>
<hr>
<h2 id="이미지-보안-스캔-기능-사용">이미지 보안 스캔 기능 사용</h2>
<p>컨테이너 이미지 업로드 시 자동으로 이미지 보안 스캔 기능 사용</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/677c7adb-bc37-422e-a8b5-da7bf7df9667/image.png" alt=""></p>
<p>자동 보안 스캔 설정 및 확인</p>
<ul>
<li>프로젝트에 &gt; Configuration &gt; Automatically... 클릭 &gt; 하단 Save 선택</li>
</ul>
<p><img src="https://velog.velcdn.com/images/imok-_/post/3d8abf67-ba97-4cf1-801d-6ec4e26ffc60/image.png" alt=""></p>
<pre><code class="language-bash"># 태그 설정
docker tag nginx harbor.$KOPS_CLUSTER_NAME/pkos/nginx:0.1
docker image ls

# 이미지 업로드
docker push harbor.$KOPS_CLUSTER_NAME/pkos/nginx:0.1</code></pre>
<p>harbor 웹에서 확인</p>
<ul>
<li>아래 처럼 자동으로 스캔 수행됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/imok-_/post/4699379d-69e7-4f32-8ac4-476d0253a6c6/image.png" alt=""></p>
<hr>
<h1 id="실습-환경-삭제">실습 환경 삭제</h1>
<p>헬름 차트 삭제</p>
<pre><code class="language-bash">helm uninstall -n harbor harbor
kubectl delete pvc --all -n harbor
kubectl delete ns harbor</code></pre>
<p>kOps 클러스터 삭제 &amp; AWS CloudFormation 스택 삭제</p>
<pre><code class="language-bash">kops delete cluster --yes &amp;&amp; aws cloudformation delete-stack --stack-name mykops</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[k8s 네트워크]]></title>
            <link>https://velog.io/@imok-_/k8s-network</link>
            <guid>https://velog.io/@imok-_/k8s-network</guid>
            <pubDate>Sat, 18 Mar 2023 18:27:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<hr>
<h1 id="1-aws-vpc-cni">1. AWS VPC CNI</h1>
<p><code>K8S CNI</code>(Container Network Interface)</p>
<ul>
<li><a href="https://kubernetes.io/docs/concepts/cluster-administration/networking/">k8s 네트워크 환경</a> 구성</li>
<li>다양한 플러그인 존재 : <a href="https://kubernetes.io/docs/concepts/cluster-administration/addons/#networking-and-network-policy">Installing Addons</a></li>
</ul>
<p><code>AWS VPC CNI</code> : </p>
<ul>
<li><strong>파드의 IP를 할당</strong> </li>
<li>파드의 IP 네트워크 대역과 노드(워커)의 IP 대역이 같아 <strong>직접 통신 가능</strong> </li>
<li><a href="https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/cni-proposal.md">Proposal</a></li>
<li>VPC 와 통합 : VPC Flow logs, VPC 라우팅 정책, <del>보안 그룹(Security group)</del> 사용 가능 → 아쉽지만 kOps 는 SG for Pod 미지원 - <a href="https://reaperes.medium.com/security-group-%EC%9D%84-pod-%EB%8B%A8%EC%9C%84%EB%A1%9C-%ED%95%A0%EB%8B%B9%ED%95%98%EA%B8%B0-bef922065684">링크</a></li>
<li>This plugin assigns an IP address from your VPC to each pod.</li>
<li>supports native VPC networking with the Amazon VPC Container Network Interface (CNI) plugin for Kubernetes.</li>
<li>VPC ENI 에 미리 할당된 IP를 파드에서 사용할 수 있음</li>
</ul>
<h4 id="k8s-cni-플러그인calico와-aws-vpc-cni의-노드와-파드-네트워크-대역-비교">K8S CNI 플러그인(Calico)와 AWS VPC CNI의 노드와 파드 네트워크 대역 비교</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/23d33a2b-df32-4aef-98c2-8ddf2b18a597/image.png" alt=""></p>
<h4 id="k8s-cni-플러그인calico와-aws-vpc-cni의-파드-간-통신-비교">K8S CNI 플러그인(Calico)와 AWS VPC CNI의 파드 간 통신 비교</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/38279f7c-2943-4d1b-b2b5-317b85ef0611/image.png" alt=""></p>
<hr>
<h2 id="네트워크-기본-정보-확인">네트워크 기본 정보 확인</h2>
<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.2
amazon-k8s-cni:v1.12.2

# 노드 IP 확인
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

# 파드 IP 확인
kubectl get pod -n kube-system -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase

# 파드 이름 확인
kubectl get pod -A -o name

# 파드 갯수 확인
kubectl get pod -A -o name | wc -l
kubectl ktop

# [master node] aws vpc cni log
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME ls /var/log/aws-routed-eni</code></pre>
<p>확인해보면, node와 pod의 네트워크 대역대는 172.30.0.0로 동일
<img src="https://velog.velcdn.com/images/imok-_/post/9e58dc09-5cee-4e2a-b346-99ab36fd6284/image.png" alt=""></p>
<h2 id="master-node-확인">master node 확인</h2>
<pre><code class="language-bash"># [master node] SSH 접속
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME

-----------------
# 툴 설치
sudo apt install -y tree jq net-tools

# CNI 정보 확인
ls /var/log/aws-routed-eni
cat /var/log/aws-routed-eni/plugin.log | jq
cat /var/log/aws-routed-eni/ipamd.log | jq

# 네트워크 정보 확인 : eniY는 pod network 네임스페이스와 veth pair
ip -br -c addr
ip -c addr
ip -c route
sudo iptables -t nat -S
sudo iptables -t nat -L -n -v

# 빠져나오기
exit
-----------------</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/863823c8-1bbb-49e6-b0ff-55e7daea4ba7/image.png" alt=""></p>
<h2 id="worker-node-확인">worker node 확인</h2>
<pre><code class="language-bash"># 워커 노드 Public IP 확인
aws ec2 describe-instances --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key==&#39;Name&#39;]|[0].Value}&quot; --filters Name=instance-state-name,Values=running --output table

# 워커 노드 Public IP 변수 지정
W1PIP=&lt;워커 노드 1 Public IP&gt;
W2PIP=&lt;워커 노드 2 Public IP&gt;

# 워커 노드 SSH 접속
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP
exit
ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP
exit

# [워커 노드1~2] SSH 접속 : 접속 후 아래 툴 설치 등 정보 각각 확인
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP
ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP
--------------
# 툴 설치
sudo apt install -y tree jq net-tools

# CNI 정보 확인
ls /var/log/aws-routed-eni
cat /var/log/aws-routed-eni/plugin.log | jq
cat /var/log/aws-routed-eni/ipamd.log | jq

# 네트워크 정보 확인
ip -br -c addr
ip -c addr
ip -c route
sudo iptables -t nat -S
sudo iptables -t nat -L -n -v

# 빠져나오기
exit
--------------</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2ba70cf3-8d42-44c6-b101-2cbaa194d606/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/883ae3c9-bacd-46c6-8285-b6ad24f14c42/image.png" alt=""></p>
<hr>
<h1 id="2-노드에서-기본-네트워크-정보-확인">2. 노드에서 기본 네트워크 정보 확인</h1>
<h4 id="worker-node1-인스턴스의-네트워크-정보-확인">worker node1 인스턴스의 네트워크 정보 확인</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/9d0e3651-3b45-416c-b363-2221d1bf7dd0/image.png" alt=""></p>
<h4 id="보조-ipv4-주소를-파드가-사용하는지-확인">보조 IPv4 주소를 파드가 사용하는지 확인</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/075191c1-122f-4347-95c0-70354aa9e68f/image.png" alt=""></p>
<h4 id="테스트용-파드-생성">테스트용 파드 생성</h4>
<pre><code class="language-bash"># [터미널1~2] 워커 노드 1~2 모니터링
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP
watch -d &quot;ip link | egrep &#39;ens5|eni&#39; ;echo;echo &quot;[ROUTE TABLE]&quot;; route -n | grep eni&quot;

ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP
watch -d &quot;ip link | egrep &#39;ens5|eni&#39; ;echo;echo &quot;[ROUTE TABLE]&quot;; route -n | grep eni&quot;

# 테스트용 파드 netshoot-pod 생성
cat &lt;&lt;EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-pod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: netshoot-pod
  template:
    metadata:
      labels:
        app: netshoot-pod
    spec:
      containers:
      - name: netshoot-pod
        image: nicolaka/netshoot
        command: [&quot;tail&quot;]
        args: [&quot;-f&quot;, &quot;/dev/null&quot;]
      terminationGracePeriodSeconds: 0
EOF

# 파드 이름 변수 지정
PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].metadata.name})
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].metadata.name})

# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/6cb4aadd-8db0-4c3e-ac22-4e81d0f80cdb/image.png" alt=""></p>
<ul>
<li>파드가 생성되면, <strong>워커 노드</strong>에 <strong>eniY@ifN</strong> <strong>추가</strong>되고 라우팅 테이블에도 정보가 추가된다.</li>
</ul>
<pre><code class="language-bash"># 노드에서 네트워크 인터페이스 정보 확인
ip -br -c addr show
ip -c link
ip -c addr
ip route # 혹은 route -n

# 마지막 생성된 네임스페이스 정보 출력 -t net(네트워크 타입)
sudo lsns -o PID,COMMAND -t net | awk &#39;NR&gt;2 {print $1}&#39; | tail -n 1

# 마지막 생성된 네임스페이스 net PID 정보 출력 -t net(네트워크 타입)를 변수 지정
MyPID=$(sudo lsns -o PID,COMMAND -t net | awk &#39;NR&gt;2 {print $1}&#39; | tail -n 1)

# PID 정보로 파드 정보 확인
sudo nsenter -t $MyPID -n ip -c addr
sudo nsenter -t $MyPID -n ip -c route</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/cda86130-a5de-4ee2-8dd6-cdccb846a975/image.png" alt=""></p>
<hr>
<h1 id="3-pod-to-pod-communication">3. Pod to Pod Communication</h1>
<h4 id="life-of-a-pod-to-pod-ping-packet">Life of a Pod to Pod Ping Packet</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/1f0c5d99-4e51-40ee-b1a5-3f42c1daae8e/image.png" alt=""></p>
<h4 id="파드간-통신-테스트-및-확인">파드간 통신 테스트 및 확인</h4>
<pre><code class="language-bash"># 파드 IP 변수 지정
PODIP1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].status.podIP})
PODIP2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].status.podIP})

# 파드1 Shell 에서 파드2로 ping 테스트
kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2

# 파드2 Shell 에서 파드1로 ping 테스트
kubectl exec -it $PODNAME2 -- ping -c 2 $PODIP1

# 워커 노드 EC2 : TCPDUMP 확인 - ens6 에서 패킷 덤프 확인이 되나요?
sudo tcpdump -i any -nn icmp
sudo tcpdump -i ens5 -nn icmp
sudo tcpdump -i ens6 -nn icmp

[워커 노드1]
# routing policy database management 확인
ip rule

# routing table management 확인
ip route show table local

# 디폴트 네트워크 정보를 ens5 을 통해서 빠져나간다
ip route show table main
default via 172.30.64.1 dev ens5 proto dhcp src 172.30.85.242 metric 100
...</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/25e3c054-2175-400b-bdf8-db96281c7d55/image.png" alt=""></p>
<hr>
<h1 id="4-pod-to-external-communications">4. Pod to external communications</h1>
<h4 id="life-of-a-pod-to-external-packet">Life of a Pod to External Packet</h4>
<p><img src="https://velog.velcdn.com/images/imok-_/post/e54dac0d-0da6-458c-878c-60737eafa0a3/image.png" alt=""></p>
<p>VPC CNI 의 External source network address translation (SNAT) 설정에 따라, 외부(인터넷) 통신 시 SNAT 하거나 혹은 SNAT 없이 통신을 할 수 있다.</p>
<h4 id="파드에서-외부-통신-테스트-및-확인">파드에서 외부 통신 테스트 및 확인</h4>
<pre><code class="language-bash"># 작업용 EC2 : pod-1 Shell 에서 외부로 ping
kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com
kubectl exec -it $PODNAME1 -- ping -i 0.1 www.google.com

# 워커 노드 EC2 : 퍼블릭IP 확인, TCPDUMP 확인
curl -s ipinfo.io/ip ; echo
sudo tcpdump -i any -nn icmp
sudo tcpdump -i ens5 -nn icmp</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/be044203-6c48-4750-a4fd-ecb4cb46492a/image.png" alt=""></p>
<pre><code class="language-bash"># 작업용 EC2 : pod-1 Shell 에서 외부 접속 확인 - 공인IP는 어떤 주소인가?
## The right way to check the weather - 링크
kubectl exec -it $PODNAME1 -- curl -s ipinfo.io/ip ; echo
kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul
kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul?format=3
kubectl exec -it $PODNAME1 -- curl -s wttr.in/Moon
kubectl exec -it $PODNAME1 -- curl -s wttr.in/:help</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/eaa41791-2485-4a4e-9b8d-5750ed890db5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/344880aa-d6eb-4762-a609-bd9326f847ea/image.png" alt=""></p>
<pre><code class="language-bash"># 워커 노드 EC2
ip rule
ip route show table main
sudo iptables -L -n -v -t nat
sudo iptables -t nat -S

# 파드가 외부와 통신시에는 아래 처럼 &#39;AWS-SNAT-CHAIN-0, AWS-SNAT-CHAIN-1&#39; 룰(rule)에 의해서 SNAT 되어서 외부와 통신!
# 뒤 IP는 eth0(ENI 첫번째)의 IP 주소이다
sudo iptables -t nat -S | grep &#39;A AWS-SNAT-CHAIN&#39;
</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/13667625-0de9-472b-a03d-668fb18df914/image.png" alt=""></p>
<pre><code class="language-bash"># 카운트 확인 시 AWS-SNAT-CHAIN-0, AWS-SNAT-CHAIN-1 에 매칭되어, 목적지가 172.30.0.0/16 아니고 외부 빠져나갈때 SNAT 172.30.40.153로 변경되어 나간다!
sudo iptables -t filter --zero; sudo iptables -t nat --zero; sudo iptables -t mangle --zero; sudo iptables -t raw --zero
watch -d &#39;sudo iptables -v --numeric --table nat --list AWS-SNAT-CHAIN-0; echo ; sudo iptables -v --numeric --table nat --list AWS-SNAT-CHAIN-1; echo ; sudo iptables -v --numeric --table nat --list KUBE-POSTROUTING&#39;

# conntrack 확인
sudo conntrack -L -n |grep -v &#39;169.254.169&#39;
conntrack v1.4.5 (conntrack-tools): 23 flow entries have been shown.
tcp      6 53 TIME_WAIT src=172.30.57.192 dst=5.9.243.187 sport=55954 dport=80 src=5.9.243.187 dst=172.30.40.153 sport=80 dport=55815 [ASSURED] mark=128 use=1</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2a263c1a-ea0a-45d9-8a36-8cdfb4d34e49/image.png" alt=""></p>
<p>다음 실습을 위해서 파드 삭제:<code>kubectl delete deploy netshoot-pod</code></p>
<hr>
<h1 id="5-파드-생성-갯수-제한">5. 파드 생성 갯수 제한</h1>
<blockquote>
<p>최대 파드 생성 갯수 : (Number of network interfaces for the instance type × (the number of IP addressess per network interface - 1)) + 2
<a href="https://github.com/awslabs/amazon-eks-ami/blob/master/files/eni-max-pods.txt">eni-max-Pods.txt</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/imok-_/post/47be9ab7-3a60-4523-b614-4d39d838d3df/image.png" alt=""></p>
<pre><code class="language-bash"># t3 타입의 정보(필터) 확인
aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \
 --query &quot;InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}&quot; \
 --output table</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/e02fc50c-7fba-4f8b-a175-52e7e61b5e5d/image.png" alt=""></p>
<h4 id="최대-파드-생성-확인">최대 파드 생성 확인</h4>
<pre><code class="language-bash">```bash
# 워커 노드 EC2 - 모니터링
watch -d &quot;ip link | egrep &#39;ens|eni&#39;&quot;
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

# 작업용 EC2 - 터미널1
watch -d &#39;kubectl get pods -o wide&#39;

# 작업용 EC2 - 터미널2
# 디플로이먼트 생성
cat ~/pkos/2/nginx-dp.yaml | yh
**kubectl apply -f ~/pkos/2/nginx-dp.yaml**

# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
kubectl ktop

# 파드 증가 테스트 &gt;&gt; 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인
kubectl scale deployment nginx-deployment --replicas=8

# 파드 증가 테스트 &gt;&gt; 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 &gt;&gt; 어떤일이 벌어졌는가?
kubectl scale deployment nginx-deployment --replicas=10

# 파드 증가 테스트 &gt;&gt; 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 &gt;&gt; 어떤일이 벌어졌는가?
**kubectl scale deployment nginx-deployment --replicas=30**

# 파드 생성 실패!
kubectl get pods | grep Pending
nginx-deployment-7fb7fd49b4-d4bk9   0/1     Pending   0          3m37s
nginx-deployment-7fb7fd49b4-qpqbm   0/1     Pending   0          3m37s
...

kubectl describe pod &lt;Pending 파드&gt; | grep Events: -A5
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  45s   default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 **Too many pods**. preemption: 0/3 nodes are available: 1 Preemption is not helpful for scheduling, 2 No preemption victims found for incoming pod.

# 디플로이먼트 삭제
kubectl delete deploy nginx-deployment</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/b1e25d1c-b74c-4c57-b53f-8a072f0cc159/image.png" alt=""></p>
<hr>
<h1 id="6-max-pod-설정">6. max-pod 설정</h1>
<pre><code class="language-bash"># 파드 갯수 모니터링
watch &quot;kubectl get pod | grep -v NAME | wc -l&quot;

# 파드 100개 배포
kubectl apply -f ~/pkos/2/nginx-dp.yaml
kubectl scale deployment nginx-deployment --replicas=0
kubectl scale deployment nginx-deployment --replicas=10
kubectl scale deployment nginx-deployment --replicas=30
kubectl scale deployment nginx-deployment --replicas=100

# Nitro 인스턴스 유형 확인
aws ec2 describe-instance-types --filters Name=hypervisor,Values=nitro --query &quot;InstanceTypes[*].[InstanceType]&quot; --output text | sort | egrep &#39;t3\.|c5\.|c5d\.&#39;

# 노드 인스턴스 타입 확인
kubectl describe nodes | grep &quot;node.kubernetes.io/instance-type&quot;

# 노드 상세 정보 확인 : 노드 상세 정보의 Allocatable 에 노드별 최대 생성 가능한 pods 정보 확인 - 각각 마스터 노드, 워커 노드
kubectl describe node | grep Allocatable: -A6

# 파드 배포 확인
kubectl get pod
kubectl get pod | grep -v NAME | wc -l
kubectl get replicasets

# LimitRanges 기본 정책 확인
kubectl describe limitranges

# 수정 전 env 정보 확인
kubectl describe ds -n kube-system aws-node | grep ADDITIONAL_ENI_TAGS: -A22
kubectl describe daemonsets.apps -n kube-system aws-node | egrep &#39;ENABLE_PREFIX_DELEGATION|WARM_PREFIX_TARGET&#39;</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/3aff620f-3add-4274-a04f-9c51aceca535/image.png" alt=""></p>
<p><a href="https://learnk8s.io/kubernetes-instance-calculator">https://learnk8s.io/kubernetes-instance-calculator</a>
<a href="https://docs.google.com/spreadsheets/d/1yhkuBJBY2iO2Ax5FcbDMdWD5QLTVO6Y_kYt_VumnEtI/edit#gid=1994017257">https://docs.google.com/spreadsheets/d/1yhkuBJBY2iO2Ax5FcbDMdWD5QLTVO6Y_kYt_VumnEtI/edit#gid=1994017257</a></p>
<hr>
<h1 id="7-service--aws-loadbalancer-controller">7. Service &amp; AWS LoadBalancer Controller</h1>
<pre><code class="language-bash"># 기본 EC2 profile 권한 확인 
aws elbv2 describe-load-balancers

# 계정 Account ID 변수 지정
ACCOUNT_ID=`aws sts get-caller-identity --query &#39;Account&#39; --output text`
echo $ACCOUNT_ID

# 생성된 IAM Policy Arn 확인
aws iam list-policies --scope Local
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --query 

# EC2 instance profiles 이름 확인 
aws iam list-instance-profiles
aws iam list-instance-profiles --query &#39;InstanceProfiles[*].InstanceProfileName&#39;

# kOps 클러스터 편집 : 아래 내용 추가
## 인증서 구성을 웹훅에 삽입할 수 있도록 cert-manager를 설치합니다. 
## Cert-manager는 쿠버네티스 클러스터 내에서 TLS인증서를 자동으로 프로비저닝 및 관리하는 오픈 소스입니다
kops edit cluster --name ${KOPS_CLUSTER_NAME}
-----
spec:
  certManager:
    enabled: true
  awsLoadBalancerController:
    enabled: true
-----

# 업데이트 적용 : 적용이 안될 경우 한번 더 아래 명령 실행
kops update cluster --yes &amp;&amp; echo &amp;&amp; sleep 5 &amp;&amp; kops rolling-update cluster

# aws-load-balancer-controller 파드 확인 : 서비스 정상 상태로 변경까지 대략 2분 30초 정도 소요
watch kubectl get pod -A

# 버전 확인 : v2.4.3
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep Image | cut -d &quot;/&quot; -f 2

# crds 확인
kubectl get crd
</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/c0f8a910-bd06-4009-898e-952bb1ccdf8e/image.png" alt=""></p>
<h4 id="서비스파드-배포-테스트-with-nlb">서비스/파드 배포 테스트 with NLB</h4>
<pre><code class="language-bash"># 작업용 EC2 - 디플로이먼트 &amp; 서비스 생성
cat ~/pkos/2/echo-service-nlb.yaml | yh
kubectl apply -f ~/pkos/2/echo-service-nlb.yaml

# 확인
kubectl get deploy,pod
kubectl get crd
kubectl get svc,ep,ingressclassparams,targetgroupbindings
kubectl get targetgroupbindings -o json | jq

# AWS ELB(NLB) 정보 확인
aws elbv2 describe-load-balancers | jq
aws elbv2 describe-load-balancers --query &#39;LoadBalancers[*].State.Code&#39; --output text

# 웹 접속 주소 확인
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;

------------------------------
# CLB에 ExternanDNS 로 도메인 연결
kubectl annotate service svc-nlb-ip-type &quot;external-dns.alpha.kubernetes.io/hostname=nginx.$KOPS_CLUSTER_NAME&quot;

# 확인
dig +short nginx.$KOPS_CLUSTER_NAME
kubectl logs -n kube-system -l k8s-app=external-dns

# 웹 접속 주소 확인 및 접속
echo -e &quot;Nginx Web URL = http://nginx.$KOPS_CLUSTER_NAME&quot;
------------------------------

# 파드 로깅 모니터링
kubectl logs -l app=deploy-websrv -f

# 분산 접속 확인
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})
curl -s $NLB
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
  52 Hostname: deploy-echo-55456fc798-2w65p
  48 Hostname: deploy-echo-55456fc798-cxl7z

# 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등)
while true; do curl -s --connect-timeout 1 $NLB | egrep &#39;Hostname|client_address&#39;; echo &quot;----------&quot; ; date &quot;+%Y-%m-%d %H:%M:%S&quot; ; sleep 1; done</code></pre>
<h4 id="파드-2개-→-1개-→-3개-설정-시-동작">파드 2개 → 1개 → 3개 설정 시 동작</h4>
<p>자동으로 반영 가능! =&gt; AWS와 k8s의 role</p>
<pre><code class="language-bash"># 작업용 EC2 - 파드 1개 설정 
kubectl scale deployment deploy-echo --replicas=1

# 확인
kubectl get deploy,pod,svc,ep
curl -s nginx.$KOPS_CLUSTER_NAME
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 nginx.$KOPS_CLUSTER_NAME | grep Hostname ; done | sort | uniq -c | sort -nr
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr


# 작업용 EC2 - 파드 3개 설정 
kubectl scale deployment deploy-echo --replicas=3

# 확인
kubectl get deploy,pod,svc,ep
curl -s nginx.$KOPS_CLUSTER_NAME
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 nginx.$KOPS_CLUSTER_NAME | grep Hostname ; done | sort | uniq -c | sort -nr
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/3e5c5390-3253-4682-901d-9ff473510b71/image.png" alt=""></p>
<pre><code class="language-bash"># [AWS LB Ctrl] 클러스터 롤 바인딩 정보 확인
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding

# [AWS LB Ctrl] 클러스터롤 확인 
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/4788b7dc-c95a-455d-bee1-66e702894bc4/image.png" alt=""></p>
<hr>
<h1 id="8-ingress">8. Ingress</h1>
<p>클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할</p>
<h4 id="서비스파드-배포-테스트-with-ingressalb">서비스/파드 배포 테스트 with Ingress(ALB)</h4>
<pre><code class="language-bash"># 게임 파드와 Service, Ingress 배포
cat ~/pkos/3/ingress1.yaml | yh
kubectl apply -f ~/pkos/3/ingress1.yaml

# 생성 확인
kubectl get-all -n game-2048
kubectl get ingress,svc,ep,pod -n game-2048
kubectl get targetgroupbindings -n game-2048
NAME                               SERVICE-NAME   SERVICE-PORT   TARGET-TYPE   AGE
k8s-game2048-service2-e48050abac   service-2048   80             ip            87s

# Ingress 확인
kubectl describe ingress -n game-2048 ingress-2048

# 게임 접속 : ALB 주소로 웹 접속
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk &#39;{ print &quot;Game URL = http://&quot;$1 }&#39;
kubectl get logs -n game-2048 -l app=game-2048

# 파드 IP 확인
kubectl get pod -n game-2048 -owide
NAME                               READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES
deployment-2048-6bc9fd6bf5-7f4mr   1/1     Running   0          72s   172.30.53.54    i-000e0b051c37cf359   &lt;none&gt;           &lt;none&gt;
deployment-2048-6bc9fd6bf5-g4h4f   1/1     Running   0          72s   172.30.95.128   i-0a5c718b3b0a7996b   &lt;none&gt;           &lt;none&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2e7fccfe-4273-4ed5-a810-ae2387e01a26/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/7616c834-3437-44c4-a367-f394b6910450/image.png" alt=""></p>
<hr>
<h1 id="도전-과제">도전 과제</h1>
<p><a href="https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/ingress/annotations/">AWS Load Balancer Controller</a> 참고</p>
<pre><code class="language-bash">AWS_ACM_ARN=`aws acm list-certificates --query &#39;CertificateSummaryList[1].CertificateArn&#39; --output text`

k apply -f imok-games.yaml
k get ingress,svc,ep,pod -n game

k delete -f imok-games.yaml</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f1d116d0-55f5-4bc8-a7d7-fd34ec51bd43/image.png" alt=""></p>
<pre><code class="language-yaml"># imok-games.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: game
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: game
  name: tetris
  labels:
    app: tetris
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tetris
  template:
    metadata:
      labels:
        app: tetris
    spec:
      containers:
      - name: tetris
        image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
  namespace: game
  name: tetris
  annotations:
     alb.ingress.kubernetes.io/healthcheck-path: /tetris/index.html
spec:
  selector:
    app: tetris
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: game
  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:
  namespace: game
  name: mario
  annotations:
    alb.ingress.kubernetes.io/healthcheck-path: /mario/index.html
spec:
  selector:
    app: mario
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  type: NodePort
  externalTrafficPolicy: Local
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: game
  name: ingress-game-imok
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/certificate-arn: ${AWS_ACM_ARN}
    alb.ingress.kubernetes.io/healthcheck-port: traffic-port
    alb.ingress.kubernetes.io/listen-ports: &#39;[{&quot;HTTP&quot;: 80}, {&quot;HTTPS&quot;:443}]&#39;
    alb.ingress.kubernetes.io/ssl-redirect: &#39;443&#39;
spec:
  ingressClassName: alb
  rules:
    - host: albweb.imokapp.net
      http:
          paths:
          - path: /tetris
            pathType: Prefix
            backend:
              service:
                name: tetris
                port:
                  number: 80
          - path: /mario
            pathType: Prefix
            backend:
              service:
                name: mario
                port:
                  number: 80</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/ae332867-3887-4e5e-ae1d-806802fb5221/image.png" alt=""></p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS kOps 실습 환경 배포]]></title>
            <link>https://velog.io/@imok-_/kops-cloudformation</link>
            <guid>https://velog.io/@imok-_/kops-cloudformation</guid>
            <pubDate>Sat, 18 Mar 2023 16:15:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<h1 id="실습-환경-배포">실습 환경 배포</h1>
<ol>
<li>CloudFormation 스택 배포<pre><code class="language-bash"># YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/kops-oneclick-f1.yaml
</code></pre>
</li>
</ol>
<h1 id="cloudformation-스택-배포">CloudFormation 스택 배포</h1>
<p>aws cloudformation deploy --template-file kops-oneclick-f1.yaml --stack-name mykops --parameter-overrides KeyName=kops-key SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=&#39;[본인 AccessKey입력]&#39; MyIamUserSecretAccessKey=&#39;[본인 SecretAccessKey 입력]&#39; ClusterBaseName=&#39;imokapp.net&#39; S3StateStore=&#39;imok-k8s-s3&#39; MasterNodeInstanceType=t3.medium WorkerNodeInstanceType=c5d.large --region ap-northeast-2</p>
<h1 id="cloudformation-스택-배포-완료-후-kops-ec2-ip-출력">CloudFormation 스택 배포 완료 후 kOps EC2 IP 출력</h1>
<p>aws cloudformation describe-stacks --stack-name mykops --query &#39;Stacks[*].Outputs[0].OutputValue&#39; --output text</p>
<h1 id="ssh-접속">SSH 접속</h1>
<p>ssh -i kops-key.pem ec2-user@$(aws cloudformation describe-stacks --stack-name mykops --query &#39;Stacks[*].Outputs[0].OutputValue&#39; --output text)</p>
<pre><code>
2. EC2 접속 &gt; IAM Role에 정책 추가
```bash
# AWSLoadBalancerController IAM 정책 생성 : 이미 정책이 있다면 Skip~
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.5/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json

# EC2 instance profiles 에 IAM Policy 추가(attach)
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$KOPS_CLUSTER_NAME
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$KOPS_CLUSTER_NAME</code></pre><p><img src="https://velog.velcdn.com/images/imok-_/post/19eb4fe8-1817-454d-803c-cc399b0f0945/image.png" alt=""></p>
<ol start="3">
<li>IAM Role 확인</li>
</ol>
<p><img src="https://velog.velcdn.com/images/imok-_/post/3165795c-4b82-4e8e-af71-e6f9fb999f3d/image.png" alt=""></p>
<ol start="4">
<li><code>kops validate cluster --wait 10m</code> 명령어로 ready 될 때까지 확인</li>
</ol>
<p><img src="https://velog.velcdn.com/images/imok-_/post/5565bb4a-e8c5-43ce-8f29-7189826acf42/image.png" alt=""></p>
<ol start="5">
<li>메트릭 서버 확인</li>
</ol>
<pre><code class="language-bash"># 메트릭 서버 확인 : 메트릭은 15초 간격으로 cAdvisor를 통하여 가져옴
kubectl top node
kubectl top pod -A

# (옵션) LimitRanges 기본 정책 삭제
kubectl describe limitranges # LimitRanges 기본 정책 확인 : 컨테이너는 기본적으로 0.1CPU(=100m vcpu)를 최소 보장(개런티)
kubectl delete limitranges limits
kubectl get limitranges</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/4f138e03-a169-4aaa-88a6-c7bc24b7205b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS kOps 설치 및 기본 사용]]></title>
            <link>https://velog.io/@imok-_/aws-kops</link>
            <guid>https://velog.io/@imok-_/aws-kops</guid>
            <pubDate>Sat, 11 Mar 2023 18:00:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Cloudnet@에서 진행하는 <strong>쿠버네티스 실무 실습 스터디</strong>를 진행하면서 작성한 글입니다.
스터디에서 사용하는 교재는 <strong>24단계 실습으로 정복하는 쿠버네티스</strong> 입니다.</p>
</blockquote>
<p><a href="https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863">Cloudnet@</a>
<a href="http://www.yes24.com/Product/goods/115843609">24단계 실습으로 정복하는 쿠버네티스</a></p>
<h1 id="사전-준비">사전 준비</h1>
<p><a href="https://velog.io/@imok-_/Route-53-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B5%AC%EB%A7%A4">Route53 도메인 구매</a></p>
<hr>
<h1 id="kops">kOps</h1>
<p>Kubernetes Operations (kOps) - Production Grade k8s Installation, Upgrades and Management</p>
<hr>
<h1 id="실습-과정">실습 과정</h1>
<ol>
<li><p>k8s 를 배포할 수 있는 kops 가 설치된 <strong>ec2</strong> 를 <strong>cloudformation</strong> 로 생성</p>
</li>
<li><p><strong>kops</strong> 로 <strong>k8s 클러스터</strong>를 생성 : s3 에 k8s 설정 파일 저장</p>
</li>
<li><p><strong>버전</strong> : k8s <strong>v1.24.10</strong>, OS Ubuntu 20.04 LTS</p>
</li>
<li><p><strong>kops-ec2</strong> 역할 : kOps 배포 수행, kubectl 명령 실행 등</p>
</li>
<li><p><strong>마스터 노드</strong>와 워커 <strong>노드</strong> : EC2 Auto Scaling Group(=ASG) 설정으로 구성</p>
</li>
<li><p>도메인은 <strong>퍼블릭</strong> 도메인 사용</p>
</li>
</ol>
<hr>
<h1 id="aws-kops-설치">AWS kOps 설치</h1>
<blockquote>
<p>💡 AWS kOps 클러스터 설치 후 정보 확인하기
<code>kops get cluster</code> , <code>kops get instances</code></p>
</blockquote>
<h2 id="1-기본-인프라-배포">1. 기본 인프라 배포</h2>
<ol>
<li>key pair 생성
<img src="https://velog.velcdn.com/images/imok-_/post/f772fb16-d037-4518-847b-58910e8707fc/image.png" alt=""></li>
</ol>
<ol start="2">
<li>AWS CloudFormation 스택 구성
<a href="https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-2#/stacks/new?stackName=mykops&amp;templateURL=https:%2F%2Fs3.ap-northeast-2.amazonaws.com%2Fcloudformation.cloudneta.net%2FK8S%2Fkops-new-ec2.yaml">AWS CloudFormation 링크</a></li>
</ol>
<ul>
<li><p>create stack
<img src="https://velog.velcdn.com/images/imok-_/post/cd49127e-b50a-49da-8a96-bb9d565331e0/image.png" alt=""></p>
</li>
<li><p>key, ami, ip 설정
<img src="https://velog.velcdn.com/images/imok-_/post/e1a4e3dd-274d-4173-bc53-9c6884007f26/image.png" alt=""></p>
</li>
<li><p>스택 구성 완료
<img src="https://velog.velcdn.com/images/imok-_/post/677bbf44-a66f-4118-b19c-ef35f0708b1d/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>kops-ec2 접속 확인
<img src="https://velog.velcdn.com/images/imok-_/post/a3cdc18f-eaa7-4480-ac1d-7289b4ea5b68/image.png" alt=""></li>
</ul>
<hr>
<h2 id="2-kops-클러스터-배포-및-확인">2. kOps 클러스터 배포 및 확인</h2>
<p>위에서 생성된 EC2 접속 후 실행</p>
<h3 id="1-설치된-기본-툴-확인">1. 설치된 기본 툴 확인</h3>
<ul>
<li><p>kubectl 버전 확인 : <code>kubectl version --client=true -o yaml | yh</code></p>
</li>
<li><p>kops 버전 확인 : <code>kops version</code></p>
</li>
<li><p>aws cli 버전 확인 : <code>aws --version</code></p>
</li>
<li><p>개인 키와 공개키 확인 : <code>ls /root/.ssh/id_rsa*</code></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/imok-_/post/90c09d18-a024-4fbc-bb8d-36a6d93bab9c/image.png" alt=""></p>
<hr>
<h3 id="2-iam-user-자격-구성">2. IAM User 자격 구성</h3>
<p>실습의 편의를 위해 administrator 권한을 가진 IAM User 생성</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/fc5fb851-ebd6-42a2-a709-c93848ef0b63/image.png" alt=""></p>
<ul>
<li><p>자격 구성</p>
<pre><code class="language-bash">$aws configure
AWS Access Key ID [None]: AKIA5...
AWS Secret Access Key [None]: CVNa2...
Default region name [None]: ap-northeast-2
Default output format [None]: json</code></pre>
</li>
<li><p>자격 구성 적용 확인</p>
</li>
</ul>
<p><code>aws ec2 describe-instances</code></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2a1a6310-c504-4767-825d-3da93c9a372c/image.png" alt=""></p>
<hr>
<h3 id="3-배포">3. 배포</h3>
<ul>
<li>S3 버킷 생성</li>
</ul>
<pre><code class="language-bash"># aws cli 페이지 출력 옵션
export AWS_PAGER=&quot;&quot;

# 리소스를 배치할 리전이름을 변수 지정
REGION=ap-northeast-2  # 서울 리전 사용

# k8s 설정 파일이 저장될 버킷 생성
aws s3 mb s3://버킷&lt;유일한 이름&gt; --region $REGION

aws s3 ls</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f078ac7b-c0a8-44ab-9e95-63a24d1ad2d7/image.png" alt=""></p>
<ul>
<li>배포</li>
</ul>
<pre><code>export AWS_PAGER=&quot;&quot;
export REGION=ap-northeast-2
export KOPS_CLUSTER_NAME=&lt;자신의 퍼블릭 호스팅 메인 주소&gt;
export KOPS_STATE_STORE=&lt;s3://(위에서 생성한 자신의 버킷 이름)&gt;

echo &#39;export AWS_PAGER=&quot;&quot;&#39; &gt;&gt;~/.bashrc
echo &#39;export REGION=ap-northeast-2&#39; &gt;&gt;~/.bashrc
echo &#39;export KOPS_CLUSTER_NAME=&lt;자신의 퍼블릭 호스팅 메인 주소&gt;&#39; &gt;&gt;~/.bashrc
echo &#39;export KOPS_STATE_STORE=&lt;s3://(위에서 생성한 자신의 버킷 이름)&gt;&#39; &gt;&gt;~/.bashrc
</code></pre><ul>
<li>EC2 생성 모니터링</li>
</ul>
<pre><code class="language-bash">while true; do aws ec2 describe-instances --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,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</code></pre>
<pre><code class="language-bash">kops create cluster --zones=&quot;$REGION&quot;a,&quot;$REGION&quot;c --networking amazonvpc --cloud aws \
--master-size t3.medium --node-size t3.medium --node-count=2 --network-cidr 172.30.0.0/16 \
--ssh-public-key ~/.ssh/id_rsa.pub --name=$KOPS_CLUSTER_NAME --kubernetes-version &quot;1.24.10&quot; -y</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/054bade7-882c-4794-8de8-12cd30da32ae/image.png" alt=""></p>
<ul>
<li>확인</li>
</ul>
<p><code>kops validate cluster --wait 10m</code></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/63fdf0c2-f859-4df6-9191-200bef6b4ff8/image.png" alt=""></p>
<hr>
<h3 id="4-aws-route53-도메인-정보-확인">4. AWS Route53 도메인 정보 확인</h3>
<ul>
<li><p>A 레코드 등록된 ip는 처음에 기본 <code>203.0.113.123</code>이고, 배포가 완료되면 ec2 ip로 변경
<img src="https://velog.velcdn.com/images/imok-_/post/17a2d50f-2579-4d74-9ed0-2d53d8471f5b/image.png" alt=""></p>
</li>
<li><p><code>dig +short api.imokapp.net</code> 명령어로 바뀐 ip 확인</p>
</li>
</ul>
<pre><code class="language-bash"># 자신의 도메인 변수 지정 : 소유하고 있는 자신의 도메인을 입력하시면 됩니다
MyDomain=&lt;자신의 도메인&gt;

# 자신의 Route 53 도메인 ID 조회 및 변수 지정
aws route53 list-hosted-zones-by-name --dns-name &quot;${MyDomain}.&quot; | jq
aws route53 list-hosted-zones-by-name --dns-name &quot;${MyDomain}.&quot; --query &quot;HostedZones[0].Name&quot;
aws route53 list-hosted-zones-by-name --dns-name &quot;${MyDomain}.&quot; --query &quot;HostedZones[0].Id&quot; --output text
MyDnzHostedZoneId=`aws route53 list-hosted-zones-by-name --dns-name &quot;${MyDomain}.&quot; --query &quot;HostedZones[0].Id&quot; --output text`
echo $MyDnzHostedZoneId

# A 레코드 타입 조회
aws route53 list-resource-record-sets --hosted-zone-id &quot;${MyDnzHostedZoneId}&quot; --query &quot;ResourceRecordSets[?Type == &#39;A&#39;]&quot; | jq
aws route53 list-resource-record-sets --hosted-zone-id &quot;${MyDnzHostedZoneId}&quot; --query &quot;ResourceRecordSets[?Type == &#39;A&#39;].Name&quot; | jq
aws route53 list-resource-record-sets --hosted-zone-id &quot;${MyDnzHostedZoneId}&quot; --query &quot;ResourceRecordSets[?Type == &#39;A&#39;].Name&quot; --output text

# A 레코드 값 반복 조회
while true; do aws route53 list-resource-record-sets --hosted-zone-id &quot;${MyDnzHostedZoneId}&quot; --query &quot;ResourceRecordSets[?Type == &#39;A&#39;]&quot; | jq ; date ; echo ; sleep 1; done</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/6270ffd7-7f59-498c-b0ec-f85c8882f821/image.png" alt=""></p>
<hr>
<h3 id="5-kops-설치-확인">5. kOps 설치 확인</h3>
<pre><code class="language-bash"># 노드 IP 확인
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

# 파드 IP 확인
kubectl get pod -n kube-system -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase

# kops 클러스터 정보 확인
$kops get cluster
NAME        CLOUD    ZONES
imokapp.net    aws    ap-northeast-2a,ap-northeast-2c

kops get cluster -o yaml
kops get cluster -o yaml | yh
...

# 인스턴스그룹 정보 확인
kops get ig

kops get ig -o yaml
kops get ig -o yaml | yh
...

# 인스턴스 정보 확인
kops get instances
kops get instances -o yaml | yh
...
</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/84956107-56ed-4c0a-aacc-c18d4537844c/image.png" alt=""></p>
<h3 id="6-세부-정보-확인">6. 세부 정보 확인</h3>
<pre><code class="language-bash"># 클러스터 정보 확인
kubectl cluster-info
kubectl cluster-info dump

# 노드 정보 확인 : 사용자 인증 정보
kubectl get nodes -v6
...

# CRI 컨테이너 런타임
kubectl get nodes -o wide

# 배포 완료 후 정보 확인
tree -L 1 ~/.kube
cat .kube/config
cat .kube/config | yh

# volume(sc)
kubectl get sc

kubectl get sc kops-csi-1-21 -o jsonpath={.parameters} ;echo
{&quot;encrypted&quot;:&quot;true&quot;,&quot;type&quot;:&quot;gp3&quot;}

kubectl get sc kops-ssd-1-17 -o jsonpath={.parameters} ;echo
{&quot;encrypted&quot;:&quot;true&quot;,&quot;type&quot;:&quot;gp2&quot;}

# [master node] aws vpc cni log
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME ls /var/log/aws-routed-eni
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME cat /var/log/aws-routed-eni/plugin.log | jq
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME cat /var/log/aws-routed-eni/ipamd.log | jq

# pod ip 확인
kubectl get pod -n kube-system
kubectl get pod -n kube-system -owide

# [master node] iptables rules
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME sudo iptables -t nat -S

# [master node] 컨테이너 정보 확인
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME ps axf |grep /usr/bin/containerd
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME ps afxuwww

# [master node] tree 툴 설치
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME sudo apt install -y tree jq

# [master node] Static 파드 정보 확인
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME tree /etc/kubernetes/manifests/
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME cat /etc/kubernetes/manifests/kube-apiserver.manifest
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME cat /etc/kubernetes/manifests/kube-controller-manager.manifest
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME cat /etc/kubernetes/manifests/kube-proxy.manifest
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME cat /etc/kubernetes/manifests/kube-scheduler.manifest

# [master node] 볼륨/마운트 확인 : nvme1n1 과 nvme2n1 은 etcd-events, etcd-main 으로 사용
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME lsblk
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME df -hT --type=ext4

Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/root      ext4   62G  5.6G   57G   9% /
/dev/nvme1n1   ext4   20G  126M   20G   1% /mnt/master-vol-08d1c4acf0650414b
/dev/nvme2n1   ext4   20G  123M   20G   1% /mnt/master-vol-0cc3c6878de9f5263

# [master node] nvme1n1,nvme1n2 디렉터리 확인
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME tree /mnt/master-vol-03072035bffdd8252
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME tree /mnt/master-vol-0db4765644c7d6ecb

# [master node] kubelet 상태 확인
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME systemctl status kubelet</code></pre>
<ul>
<li>master node에 SSH 접속 후 확인</li>
</ul>
<pre><code class="language-bash">
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME

# [master node] EC2 메타데이터 확인 : IAM Role - 링크
TOKEN=`curl -s -X PUT &quot;http://169.254.169.254/latest/api/token&quot; -H &quot;X-aws-ec2-metadata-token-ttl-seconds: 21600&quot;`
echo $TOKEN

curl -s -H &quot;X-aws-ec2-metadata-token: $TOKEN&quot; –v http://169.254.169.254/
curl -s -H &quot;X-aws-ec2-metadata-token: $TOKEN&quot; –v http://169.254.169.254/latest/
curl -s -H &quot;X-aws-ec2-metadata-token: $TOKEN&quot; –v http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl -s -H &quot;X-aws-ec2-metadata-token: $TOKEN&quot; –v http://169.254.169.254/latest/meta-data/iam/security-credentials/masters.imokapp.net | jq</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f5c5cdc8-c2d8-435e-9f7a-9687dac2f846/image.png" alt=""></p>
<ul>
<li>워커 노드에 SSH 접속 후 확인 : 워커 노드의 public ip 로 SSH 접속</li>
</ul>
<pre><code class="language-bash"># 워커 노드 Public IP 확인
aws ec2 describe-instances --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key==&#39;Name&#39;]|[0].Value}&quot; --filters Name=instance-state-name,Values=running --output table

# 워커 노드 Public IP 변수 지정
W1PIP=&lt;워커 노드 1 Public IP&gt;
W2PIP=&lt;워커 노드 2 Public IP&gt;
W1PIP=3.35.50.183
W2PIP=54.180.118.11

# 워커 노드 SSH 접속
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP
exit
ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP
exit

# 워커 노드 스토리지 확인
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP lsblk
ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP lsblk
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP df -hT -t ext4
ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP df -hT -t ext4

# 워커 노드 SSH 접속 후 node EC2 메타데이터 확인 : IAM Role - 링크
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP
ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP

TOKEN=`curl -s -X PUT &quot;http://169.254.169.254/latest/api/token&quot; -H &quot;X-aws-ec2-metadata-token-ttl-seconds: 21600&quot;`
echo $TOKEN
curl -s -H &quot;X-aws-ec2-metadata-token: $TOKEN&quot; –v http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl -s -H &quot;X-aws-ec2-metadata-token: $TOKEN&quot; –v http://169.254.169.254/latest/meta-data/iam/security-credentials/nodes.imokapp.net | jq</code></pre>
<hr>
<h2 id="3-관리-편의성">3. 관리 편의성</h2>
<ol>
<li>자동 완성 및 alias 축약 설정</li>
</ol>
<pre><code class="language-bash">source &lt;(kubectl completion bash)
echo &#39;source &lt;(kubectl completion bash)&#39; &gt;&gt; ~/.bashrc
echo &#39;alias k=kubectl&#39; &gt;&gt; ~/.bashrc
echo &#39;complete -F __start_kubectl k&#39; &gt;&gt; ~/.bashrc</code></pre>
<ol start="2">
<li>kubectl cli 플러그인 매니저 <a href="https://krew.sigs.k8s.io/docs/user-guide/setup/install/">쿠버네티스 크루(krew)</a> 설치</li>
</ol>
<pre><code class="language-bash"># 설치
$curl -fsSLO https://github.com/kubernetes-sigs/krew/releases/download/v0.4.3/krew-linux_amd64.tar.gz
$tar zxvf krew-linux_amd64.tar.gz
$./krew-linux_amd64 install krew
$tree -L 3 /root/.krew/bin

# PATH 추가
export PATH=&quot;${PATH}:/root/.krew/bin&quot;
echo &#39;export PATH=&quot;${PATH}:/root/.krew/bin&quot;&#39; &gt;&gt;~/.bashrc

# krew 확인
kubectl krew
kubectl krew update
kubectl krew search
kubectl krew list
kubectl krew</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/69fad8e5-430c-45bd-b0c9-c98ff436cae6/image.png" alt=""></p>
<ol start="3">
<li>krew 로 kube-ctx, kube-ns 설치 및 사용</li>
</ol>
<ul>
<li>kube-ctx : 쿠버네티스 컨텍스트 사용</li>
</ul>
<pre><code class="language-bash"># 설치
kubectl krew install ctx

# 컨텍스트 확인
kubectl ctx

# 컨텍스트 사용 
kubectl ctx &lt;각자 자신의 컨텍스트 이름&gt;</code></pre>
<ul>
<li>kube-ns : 네임스페이스(단일 클러스터 내에서 가상 클러스터) 사용</li>
</ul>
<pre><code class="language-bash"># 설치
kubectl krew install ns

# 네임스페이스 확인
kubectl ns

# 터미널1
watch kubectl get pod

# kube-system 네임스페이스 선택 사용
kubectl ns kube-system

# default 네임스페이스 선택 사용
kubectl ns -
혹은
kubectl ns default</code></pre>
<ul>
<li>krew list 확인</li>
</ul>
<pre><code class="language-bash">kubectl krew list</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/b83738a5-0767-4f17-a9fd-34729cb16e87/image.png" alt=""></p>
<ol start="4">
<li>krew 로 기타 플러그인 설치 및 사용 : df-pv get-all ktop neat oomd view-secret</li>
</ol>
<pre><code class="language-bash"># 설치
kubectl krew install df-pv get-all ktop neat oomd view-secret # mtail tree

# get-all 사용
kubectl get-all
kubectl get-all -n kube-system

# ktop 사용
kubectl ktop

# oomd 사용
kubectl oomd

# df-pv 사용
kubectl df-pv

# view-secret 사용 : 시크릿 복호화
kubectl view-secret</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/7a979207-7f1c-47a6-9d8d-c1b78589b8ad/image.png" alt=""></p>
<ol start="5">
<li>kube-ps1 설치 및 사용</li>
</ol>
<pre><code class="language-bash"># 설치 및 설정
git clone https://github.com/jonmosco/kube-ps1.git /root/kube-ps1

cat &lt;&lt;&quot;EOT&quot; &gt;&gt; /root/.bash_profile
source /root/kube-ps1/kube-ps1.sh
KUBE_PS1_SYMBOL_ENABLE=true
function get_cluster_short() {
  echo &quot;$1&quot; | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=&#39;) &#39;
PS1=&#39;$(kube_ps1)&#39;$PS1
EOT

# 적용
exit
exit

# default 네임스페이스 선택
kubectl ns default</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/a6a6df4d-ff4f-4ffd-9639-cba161d93c74/image.png" alt=""></p>
<hr>
<h1 id="서비스파드-배포-테스트-with-clb">서비스/파드 배포 테스트 with CLB</h1>
<ul>
<li>마리오 게임 🎮</li>
</ul>
<pre><code class="language-bash"># 수퍼마리오 디플로이먼트 배포
$curl -s -O https://raw.githubusercontent.com/euneun316/k8s-study/main/1/mario.yaml
$kubectl apply -f mario.yaml
cat mario.yaml | yh

# 배포 확인 : CLB 배포 확인 &gt;&gt; 5분 이상 소요
kubectl get deploy,svc,ep mario
watch kubectl get svc mario

# 마리오 게임 접속 : CLB 주소로 웹 접속
kubectl get svc mario -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk &#39;{ print &quot;Maria URL = http://&quot;$1 }&#39;</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/ed552937-8899-438b-8153-07cc7ce8fcc7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/2322c7fd-127d-4d58-b02c-ef93b0ab3b9a/image.png" alt=""></p>
<hr>
<h1 id="실습-환경-삭제">실습 환경 삭제</h1>
<p>🚨 <strong>실습 완료 후 실습 리소스 삭제 필요</strong></p>
<ul>
<li>kOps 클러스터 삭제:  <code>kops delete cluster --yes</code></li>
<li>(클러스터 삭제 완료 확인 후) AWS CloudFormation 스택 삭제 : <code>aws cloudformation delete-stack --stack-name mykops</code></li>
</ul>
<p>🚨 <strong>kOps 설치 중간에 실패 시 삭제 방법</strong></p>
<ol>
<li><strong>EC2 Auto Scaling 그룹</strong> : 3개 삭제</li>
<li><strong>EC2 시작 템플릿</strong> Launch Templates : 3개 삭제</li>
<li><strong>S3 버킷 비우기</strong></li>
<li>Route53에 추가된 <strong>A 레코드 3개 삭제</strong></li>
<li>CloudFormation 삭제</li>
</ol>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Route 53 도메인 구매]]></title>
            <link>https://velog.io/@imok-_/Route-53-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B5%AC%EB%A7%A4</link>
            <guid>https://velog.io/@imok-_/Route-53-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B5%AC%EB%A7%A4</guid>
            <pubDate>Sat, 11 Mar 2023 17:25:09 GMT</pubDate>
            <description><![CDATA[<h1 id="route-53-도메인-구매">Route 53 도메인 구매</h1>
<ol>
<li><p>Route53 검색 &gt; Registered domains &gt; Register domain
<img src="https://velog.velcdn.com/images/imok-_/post/0e03db60-1172-4405-9918-e5f0243dedb7/image.png" alt=""></p>
</li>
<li><p>도메인 이름 검색 &gt; 선택 &gt; 기간 선택
<img src="https://velog.velcdn.com/images/imok-_/post/7ba4ffe1-00e8-42ef-8849-93d9c868a90a/image.png" alt=""></p>
</li>
<li><p>등록자 정보 입력
<img src="https://velog.velcdn.com/images/imok-_/post/4b7e5913-2087-421c-b864-4f4802f57baa/image.png" alt=""></p>
</li>
</ol>
<ol start="4">
<li><p>등록자 정보 확인 및 주문
<img src="https://velog.velcdn.com/images/imok-_/post/07dab7e0-ac52-42af-be99-339187482bcf/image.png" alt=""></p>
</li>
<li><p>Pending requests 확인
<img src="https://velog.velcdn.com/images/imok-_/post/c4621393-3b30-4472-9dd0-e5acd97a0b4a/image.png" alt=""></p>
</li>
</ol>
<ol start="6">
<li><p>Hosted zones 확인
<img src="https://velog.velcdn.com/images/imok-_/post/f0f26e82-d4ad-42b3-8cb7-151db9de96bb/image.png" alt=""></p>
</li>
<li><p>이메일 확인
<img src="https://velog.velcdn.com/images/imok-_/post/e8353c72-004c-4966-a7d5-30f9b6de3396/image.png" alt=""></p>
</li>
<li><p>정상 등록 확인
<img src="https://velog.velcdn.com/images/imok-_/post/e9d1ce60-1b0d-4103-9166-16249908a8bf/image.png" alt=""></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform module을 활용한 기본 인프라 구축]]></title>
            <link>https://velog.io/@imok-_/Terraform-Module-AWS-Default-Infra</link>
            <guid>https://velog.io/@imok-_/Terraform-Module-AWS-Default-Infra</guid>
            <pubDate>Sun, 11 Dec 2022 20:15:53 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하기-앞서">시작하기 앞서..</h1>
<p>지난번 &#39;<strong>terraform으로 간단한 인프라 구축하기</strong>&#39;에 대한 내용을 작성했습니다. <a href="https://velog.io/@imok-_/Terraform-AWS-Default-Infra">Terraform 기본 인프라 구축하기</a></p>
<p>비슷한 인프라 구축 요구 사항이 들어왔을 때는 해당 코드를 활용하기 좋았지만, 조금이라도 새로운 리소스 구성이 필요하면 코드의 재사용이 어렵다는 것을 느꼈습니다.</p>
<p>또한 비슷한 환경을 생성할 때 마다 terraform의 <code>.tf</code> 파일을 복사해 계속 새로운 파일을 만들어 내는 점이 비효율적이라 생각되었습니다.</p>
<blockquote>
<p><strong><em>💡 따라서 공통으로 사용하는 리소스를 만들어 코드를 재사용하기 위해 <code>terraform module</code>을 사용하고, 팀원들과 동일 코드을 다른 환경에서 사용하기 위해 <code>terraform workspace</code>를 도입하기로 했습니다.</em></strong></p>
</blockquote>
<p>⚠️ 본 내용은 Terraform스터디를 진행하며 현업에 적용 해 본 내용을 기재하였기 때문에, AWS 및 Terraform의 기본 개념에 대해서는 생략하였습니다.</p>
<hr>
<h1 id="인프라-구축-전-체크-사항">인프라 구축 전 체크 사항</h1>
<blockquote>
<p>지난번 사원 👶🏻 OK는 terraform으로 기본 인프라 구성을 완료했습니다.
하지만 코드의 재사용에 있어 몇 가지 불편한 점이 있었고, 규모가 커질수록 단순한 방식으로 처리가 힘들어졌습니다.
또한 추가로 팀원들과 공동 작업을 하기 위해, 다음과 같이 코드를 개선하기로 했습니다.</p>
</blockquote>
<h2 id="인프라-개선-필요-사항-체크-✅">인프라 개선 필요 사항 체크 ✅</h2>
<ol>
<li><p><strong>terraform의 count 매개변수의 제약 사항</strong></p>
<ul>
<li><p>전체 리소스를 반복할 수는 있지만 리소스 내에서 인라인 블록을 반복할 수는 없음</p>
</li>
<li><p>리소스가 배열의 형식으로 생성되기 때문에, 어떤 리소스가 생성되는지 명확히 알 수 없어, 수정 시 에러 발생 가능성이 높음</p>
<p><em>** ➡️ for_each 표현식으로 변경**</em></p>
</li>
</ul>
</li>
<li><p><strong>구성 환경의 중복된 코드</strong></p>
<ul>
<li><strong>개발(Dev)과 운영(Prod) 환경은</strong> 일부 속성만 다르기 때문에 <strong>코드에 중복된 내용이 상당히 많았음</strong></li>
</ul>
<p><em>** ➡️ <code>terraform module</code>과 <code>terraform workspace</code>를 활용하는 방식으로 코드 변경 **</em></p>
</li>
<li><p>규모가 커짐에 따라 필요한 리소스들(EC2, S3, DynamoDB 등)이 증가해, root 경로에서 파일 이름만으로 분리된 소스를 관리하는 것이 불편해짐</p>
<p><em>**  ➡️ 폴더별로 리소스를 구분하고, root 경로에서 리소스별 모듈을 사용하는 코드로 변경**</em></p>
</li>
<li><p><strong>Bastion Host를 통한 Tunneling 접속 방식의 불편함</strong></p>
<p> <em>** ➡️ Private Server에 직접 접속할 수 있는 AWS Systems Manager(AWS SSM) 구성**</em></p>
</li>
</ol>
<hr>
<h2 id="구축-내용-✅">구축 내용 ✅</h2>
<blockquote>
<p>👶🏻 OK는 개선이 필요한 사항을 확인하고, Terraform 구성 전에 미리 구축 내용을 작성했습니다.</p>
</blockquote>
<ol>
<li><p><strong>VPC(Virtual Private Cloud)</strong>
 <strong>다양한 네트워크 환경 구성</strong></p>
<ul>
<li>IP 대역 : <code>10.60.0.0/16</code></li>
<li>Internet Gateway</li>
<li>NAT gateways</li>
<li>Route tables</li>
</ul>
</li>
<li><p><strong>Subnet 구성</strong>
 <strong>Public 환경과 Private 환경 구분</strong></p>
<ul>
<li><p>Region : ap-northeast-2 [Asia Pacific (Seoul)]</p>
<table>
<thead>
<tr>
<th>Env</th>
<th>AZ</th>
<th>Subnet</th>
<th>IPv4 CIDR</th>
</tr>
</thead>
<tbody><tr>
<td>Public</td>
<td>ap-northeast-2a</td>
<td>T101-pub-sub-2a</td>
<td>10.60.0.0/24</td>
</tr>
<tr>
<td>Public</td>
<td>ap-northeast-2c</td>
<td>T101-pub-sub-2c</td>
<td>10.60.1.0/24</td>
</tr>
<tr>
<td>Private</td>
<td>ap-northeast-2a</td>
<td>T101-pri-sub-2a</td>
<td>10.60.2.0/24</td>
</tr>
<tr>
<td>Private</td>
<td>ap-northeast-2c</td>
<td>T101-pri-sub-2c</td>
<td>10.60.3.0/24</td>
</tr>
</tbody></table>
</li>
</ul>
</li>
<li><p><strong>SG(Security Group)</strong>
 <strong>보안그룹은 언제든 확장할 수 있게 구성</strong></p>
<ul>
<li><p>EC2 접속을 위한 On-Premise의 <strong>IP Inbound Source</strong> 정보 (IP는 보안을 위해 임의로 생성했습니다.)</p>
<table>
<thead>
<tr>
<th>CIDR</th>
<th>Port</th>
<th>Desc</th>
</tr>
</thead>
<tbody><tr>
<td>151.149.23.124/32</td>
<td>22</td>
<td>From Imok SSH(random test ip)</td>
</tr>
<tr>
<td>151.149.23.124/32</td>
<td>25</td>
<td>From Imok SMTP(random test ip)</td>
</tr>
<tr>
<td>151.149.23.124/32</td>
<td>80</td>
<td>From Imok HTTP(random test ip)</td>
</tr>
</tbody></table>
</li>
</ul>
</li>
<li><p><strong>EC2</strong>
 <strong>다양한 환경에서 EC2 접속할 수 있게 구성</strong></p>
<ul>
<li><p>Public EC2 : pem key를 이용한 ssh 접속</p>
</li>
<li><p>Private EC2 : AWS SSM을 이용해 접속</p>
</li>
<li><p>OS : Amazon Linux 2</p>
<table>
<thead>
<tr>
<th>용도</th>
<th>Name</th>
<th>CPU</th>
<th>Memory</th>
<th>Disk</th>
<th>Type</th>
</tr>
</thead>
<tbody><tr>
<td>public-server</td>
<td>T101-public-server</td>
<td>2</td>
<td>1GiB</td>
<td>30GiB</td>
<td>t3.micro</td>
</tr>
<tr>
<td>private-server</td>
<td>T101-private-server</td>
<td>2</td>
<td>1GiB</td>
<td>3oGiB</td>
<td>r5.micro</td>
</tr>
</tbody></table>
</li>
</ul>
</li>
<li><p><strong>IAM(Identity and Access Management)</strong>
  <strong>SSM 사용 및 S3 접근을 위해 Private EC2에 <code>IAM role</code> 적용</strong></p>
<ul>
<li>AWS SSM Policy : <code>AmazonSSMManagedInstanceCore</code></li>
<li>Amazon S3 Policy : <code>AmazonS3FullAccess</code></li>
</ul>
</li>
</ol>
<hr>
<h2 id="구축-예상-아키텍처-✅">구축 예상 아키텍처 ✅</h2>
<blockquote>
<p>👶🏻 OK는 다음과 같이 구축하게 될 예상 아키텍처를 그려봤습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f2647b67-7eb3-450f-9043-31571356dced/image.png" alt=""></p>
<hr>
<h1 id="인프라-구축">인프라 구축</h1>
<blockquote>
<p>👶🏻 OK는 팀원들에게 공통 <code>terraform module</code>을 작성해 배포해야 했기 때문에 다음과 같은 목표를 세웠습니다.</p>
<blockquote>
<p>💡 서비스별로 폴더를 분리해, <code>root module</code>에서만 코드 수정하도록 환경 구성
💡 각자의 aws 환경에서 진행할 수 있도록 <code>terraform workspace</code> 환경 구성</p>
</blockquote>
</blockquote>
<h2 id="terraform-workspace-구성">Terraform Workspace 구성</h2>
<p><strong>작업 공간을 통한 Workspaces 격리</strong> 
HashiCorp 공식 문서 : <a href="https://developer.hashicorp.com/terraform/language/state/workspaces">Terraform Workspaces</a></p>
<ul>
<li>테라폼은 기본 <code>default</code> 작업 공간을 사용합니다. 새 작업 공간을 만들거나 전환하려면 <code>terraform workspace</code> 명령을 사용합니다.</li>
<li>작업 공간은 code refactoring을 시도하는 것 같이, 이미 배포된 인프라에 영향을 주지 않고 테라폼 모듈을 테스트할 때 유용합니다.</li>
</ul>
<h3 id="workspace-구조">workspace 구조</h3>
<p><code>terraform.tfstate.d</code>라는 폴더 아래에 workspace 별로 폴더가 생성되고, 각각 상태 파일인 <code>.tfstate</code> 파일이 관리되는 형태입니다.</p>
<pre><code class="language-bash"># workspace 구조 예시

├── terraform.tfstate.d
│   ├── imok
│   │   ├── terraform.tfstate
│   │   └── terraform.tfstate.backup
│   └── project
│       ├── terraform.tfstate
│       └── terraform.tfstate.backup
</code></pre>
<h3 id="workspace-사용">workspace 사용</h3>
<ol>
<li><p>새 workspace 생성</p>
<p><code>terraform workspace new [workspace name]</code></p>
</li>
<li><p>workspace 전환</p>
<p><code>terraform workspace select [workspace name]</code></p>
</li>
<li><p>workspace 리스트 확인</p>
<p><code>terraform workspace list</code></p>
</li>
<li><p>현재 workspace 확인</p>
<p><code>terraform workspace show</code></p>
</li>
</ol>
<h3 id="aws-credentials-사용">aws credentials 사용</h3>
<p>terraform 프로젝트를 두 개 이상의 어카운트에서 사용해야 할 경우, <code>aws credential</code>의 <code>profile</code> 이름을 workspace 이름과 일치시키는 방법을 사용합니다.</p>
<ul>
<li><code>~/.aws/credentials</code> 파일 예</li>
</ul>
<pre><code class="language-bash">[imok]
aws_access_key_id = []
aws_secret_access_key = []
[workspace name]
aws_access_key_id = []
aws_secret_access_key = []
...</code></pre>
<hr>
<h2 id="terraform-module-구성">Terraform module 구성</h2>
<p><img src="https://velog.velcdn.com/images/imok-_/post/4a2b7843-963b-47d0-8fe5-26b8ce7bb3e4/image.png" alt=""></p>
<ol>
<li><strong>root module</strong> : 실제로 수행하게 되는 작업 디렉터리의 terraform 코드 모음<ul>
<li><code>root.tf</code></li>
</ul>
</li>
<li><strong>child module</strong> : root module에서 리소스를 생성하기 위해 참조하고 있는 module block<ul>
<li><code>EC2</code>, <code>VPC</code>, <code>IAM</code>, <code>SG</code></br>

</li>
</ul>
</li>
</ol>
<p><strong>child module</strong>에서 모듈이 생성하는 <code>resource</code>는 보통의 리소스를 생성하는 코드와 동일하지만, <strong>root module(모듈을 사용하는 코드)에서 건네주는 변수들을 사용해서 리소스를 생성해야 합니다.</strong> 
저는 <strong>root module</strong>을 <code>root.tf</code>파일로 정의하고, 해당 파일에서 모듈을 사용하는 코드를 작성했습니다.</p>
<p><code>var.az_names</code>와 같이 <strong>root module</strong>에서 설정한 변수를 받아와 <strong>child module</strong>에서 리소스를 생성할 수 있습니다.
이러한 변수를 받아오기 위해서는 <strong>child module</strong>에서 <code>variable</code>을 설정해주어야 합니다.</p>
<p><strong>child module</strong>에서 리소스를 생성한 후, 생성한 리소스에 대한 정보(arn, id 등)를 받기 위해 <strong>child module</strong>에서 <code>output</code>을 통해 리소스 정보를 출력해주어야 합니다.</p>
<p><strong>root module</strong>에서는 <code>module.&lt;MODULE_NAME&gt;.id</code>와 같이 <code>output</code>을 받아올 수 있습니다.</p>
<blockquote>
<p>⚠️ <strong><em>해당 내용이 모듈을 도입하면서 제일 많이 헷갈리고, 제일 많은 오류를 겪었던 부분입니다. 😵<br>반복해서 환경을 구성해보면서 이해하는 것이 좋습니다.</em></strong></p>
</blockquote>
<p>저는 다음과 같이 <strong>root module</strong>과 서비스 별 <strong>child module</strong>영역으로 구분해 코드를 작성했습니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/04adc927-4630-4d44-b270-66f3dba2f058/image.png" alt=""></p>
<pre><code class="language-bash">.
├── ec2
│   ├── ami.tf
│   ├── key-pair.tf
│   ├── main.tf
│   ├── outputs.tf
│   ├── user_data
│   │   ├── user_data_private.sh
│   │   └── user_data_public.sh
│   └── variables.tf
├── iam
│   ├── main.tf
│   ├── outputs.tf
│   └── variables.tf
├── sg
│   ├── main.tf
│   ├── outputs.tf
│   └── variables.tf
├──vpc
│   ├── main.tf
│   ├── outputs.tf
│   └── variables.tf
├── terraform.tfstate.d # workspace
│   └── imok
│       ├── terraform.tfstate
│       └── terraform.tfstate.backup
├── terraform.tfvars
├── outputs.tf
├── provider.tf
├── root.tf # root module
└── variables.tf</code></pre>
<hr>
<h3 id="vpc-resouece">VPC Resouece</h3>
<p>지난 번 구성과 다른 점은 <code>count</code>를 <code>for_each</code> 문으로 변경했다는 점입니다.
for each 표현식에 대한 설명은 제 블로그 내용 참고 부탁드립니다. <a href="https://velog.io/@imok-_/Terraform-Loops">Terraform 반복문 Loops 사용하기</a></p>
<ol>
<li><p><code>main.tf</code> 파일 구성</p>
<pre><code class="language-bash">resource &quot;aws_subnet&quot; &quot;private&quot; {
 for_each          = var.private_subnets
 vpc_id            = aws_vpc.vpc.id
 cidr_block        = each.value[&quot;cidr&quot;]
 availability_zone = each.value[&quot;zone&quot;]

 tags = merge(
   {
     Name = format(
       &quot;%s-pri-sub-%s&quot;,
       var.name,
       element(split(&quot;_&quot;, each.key), 2)
     )
   },
   var.tags,
 )
}</code></pre>
</li>
<li><p><code>variables.tf</code> 파일 구성
 <strong>root module</strong>에서 정의한 변수를 받아오기 위해 다음과 같이 <code>variable</code>을 설정했습니다.</p>
<pre><code class="language-bash"># project name
variable &quot;name&quot; {}

# VPC default CIDR
variable &quot;vpc_cidr&quot; {}

# Availability Zones
variable &quot;az_names&quot; {}

# public subnet list
variable &quot;public_subnets&quot; {}

# private subnet list
variable &quot;private_subnets&quot; {}

# Tags
variable &quot;tags&quot; {}</code></pre>
</li>
<li><p><code>output.tf</code> 파일 구성
ec2 리소스에서 vpc의 <code>subnet id</code> 변수를 사용하기 위해 <code>output</code>에 정의했습니다.</p>
<pre><code class="language-bash">output &quot;public_subnet_ids&quot; {
 value = values(aws_subnet.public)[*].id
}

output &quot;private_subnet_ids&quot; {
 value = values(aws_subnet.private)[*].id
}</code></pre>
</li>
</ol>
<hr>
<h3 id="ec2-resouece">EC2 Resouece</h3>
<ol>
<li><code>main.tf</code> 파일 구성</li>
</ol>
<ul>
<li><p>미리 <code>ami.tf</code> 파일에서 정의한 ami 중 최신 amazon linux2의 이미지를 선택했고, 인스턴스 타입, 볼륨 등을 설정했습니다.</p>
</li>
<li><p><code>user_data</code>는 미리 파일을 구성해서 <code>${path.module}</code>를 사용해 해당 모듈 경로에 있는 파일을 불러와 인스턴스를 생성했습니다.</p>
<pre><code class="language-bash">resource &quot;aws_instance&quot; &quot;private&quot; {
  key_name               = var.key_name
  ami                    = data.aws_ami.amazon_linux2_kernel_5.id
  instance_type          = var.ec2_type_private
  vpc_security_group_ids = [var.security_group_id_private]
  subnet_id              = var.pri_sub_ids[0]

  iam_instance_profile    = var.iam_instance_profile
  disable_api_termination = var.instance_disable_termination

  user_data = file(&quot;${path.module}/user_data/user_data_private.sh&quot;)

  root_block_device {
    volume_size           = 10
    volume_type           = &quot;gp3&quot;
    delete_on_termination = true
  }

  tags = merge(
    {
      Name = format(
        &quot;%s-private-server&quot;,
        var.name
      )
    },
    var.tags,
  )
}</code></pre>
</li>
</ul>
<ol start="2">
<li><p><code>variables.tf</code> 파일 구성
 <strong>root module</strong>에서 정의한 변수를 받아오기 위해 다음과 같이 <code>variable</code>을 설정했습니다.
 <strong>다른 모듈의 <code>output</code>에서 정의된 변수를 여기에서 받아 옵니다.</strong></p>
<pre><code class="language-bash">#  For EC2
variable &quot;name&quot; {}
variable &quot;tags&quot; {}
variable &quot;az_names&quot; {}
variable &quot;instance_disable_termination&quot; {}
variable &quot;key_name&quot; {}
variable &quot;ec2_type_public&quot; {}
variable &quot;ec2_type_private&quot; {}
variable &quot;volume_size&quot; {}
variable &quot;public_subnets&quot; {}
variable &quot;private_subnets&quot; {}

#  From module VPC
variable &quot;pub_sub_ids&quot; {}
variable &quot;pri_sub_ids&quot; {}

#  From module IAM
variable &quot;iam_instance_profile&quot; {}

#  From module SG
variable &quot;security_group_id_public&quot; {}
variable &quot;security_group_id_private&quot; {}</code></pre>
</li>
<li><p><code>output.tf</code> 파일 구성
 <strong>root module</strong>의 <code>output</code>에서 <strong>ec2 접속을 위한 정보를 확인하기 위해</strong> 다음과 같이 필요한 변수들을 정의했습니다.</p>
<pre><code class="language-bash">output &quot;key_pair&quot; {
 value = var.key_name
}

output &quot;public_eip&quot; {
 value = aws_eip.public.public_ip
}

output &quot;ec2_private_id&quot; {
 value = aws_instance.private.id
}</code></pre>
</li>
<li><p><code>key-pair.tf</code> 파일 구성
<code>${path.module}</code>를 사용해 local의 모듈 경로에 key file을 생성합니다.</p>
<pre><code class="language-bash"># Generates a secure private key and encodes it as PEM
resource &quot;tls_private_key&quot; &quot;key_pair&quot; {
 algorithm = &quot;RSA&quot;
 rsa_bits  = 4096
}
# Create the Key Pair
resource &quot;aws_key_pair&quot; &quot;key_pair&quot; {
 key_name   = &quot;${var.name}-key&quot;
 public_key = tls_private_key.key_pair.public_key_openssh
}
# Save Pem Key
resource &quot;local_file&quot; &quot;ssh_key&quot; {
 filename = &quot;${path.module}/${aws_key_pair.key_pair.key_name}.pem&quot;
 content  = tls_private_key.key_pair.private_key_pem
}</code></pre>
</li>
</ol>
<hr>
<h3 id="sg-resouece">SG Resouece</h3>
<p>SG 구성은 지난번 구성과 거의 동일하고, <code>locals</code> 변수를 추가해 tag에 활용했습니다.</p>
<ol>
<li><p><code>main.tf</code> 파일 구성</p>
<pre><code class="language-bash">locals {
 public_sg  = format(&quot;%s-%s-sg&quot;, var.name, &quot;public&quot;)
 private_sg = format(&quot;%s-%s-sg&quot;, var.name, &quot;private&quot;)
}

resource &quot;aws_security_group&quot; &quot;private&quot; {
 name        = local.private_sg
 description = &quot;private security group for ${var.name}&quot;
 vpc_id      = var.vpc_id

 # inbound rule
 dynamic &quot;ingress&quot; {
   for_each = [for s in var.private_ingress_rules : {
     from_port = s.from_port
     to_port   = s.to_port
     desc      = s.desc
     cidrs     = [s.cidr]
   }]
   content {
     from_port   = ingress.value.from_port
     to_port     = ingress.value.to_port
     cidr_blocks = ingress.value.cidrs
     protocol    = &quot;tcp&quot;
     description = ingress.value.desc
   }
 }

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

 tags = merge(
   {
     Name = local.private_sg
   },
   var.tags
 )
}</code></pre>
</li>
<li><p><code>variables.tf</code> 파일 구성
 <strong>root module</strong>에서 정의한 변수를 받아오기 위해 다음과 같이 <code>variable</code>을 설정했습니다.</p>
<pre><code class="language-bash"># Project name
variable &quot;name&quot; {}

# Tags
variable &quot;tags&quot; {}

# public ingress IP list
variable &quot;public_ingress_rules&quot; {}

# private ingress IP list
variable &quot;private_ingress_rules&quot; {}

# From module VPC
variable &quot;vpc_id&quot; {}</code></pre>
</li>
<li><p><code>output.tf</code> 파일 구성
ec2 리소스에서 sg의 <code>security_group_id</code> 변수를 사용하기 위해 <code>output</code>에 정의했습니다.</p>
<pre><code class="language-bash">output &quot;security_group_id_public&quot; {
 value = aws_security_group.public.id
}

output &quot;security_group_id_private&quot; {
 value = aws_security_group.private.id
}</code></pre>
</li>
</ol>
<hr>
<h3 id="iam-resouece">IAM Resouece</h3>
<p>Private EC2에 AWS SSM을 통해 접속하기 위한 IAM Role 구성입니다.
SSM의 <code>AmazonSSMManagedInstanceCore</code> 정책과 S3의 <code>AmazonS3FullAccess</code> 정책을 붙였습니다.</p>
<ol>
<li><p><code>main.tf</code> 파일 구성</p>
<pre><code class="language-bash"># IAM role
resource &quot;aws_iam_role&quot; &quot;private&quot; {
 name = format(&quot;%s-private-role&quot;, lower(var.name))

 assume_role_policy = &lt;&lt;EOF
{
   &quot;Version&quot;: &quot;2012-10-17&quot;,
   &quot;Statement&quot;: [
       {
           &quot;Sid&quot;: &quot;&quot;,
           &quot;Effect&quot;: &quot;Allow&quot;,
           &quot;Principal&quot;: {
               &quot;Service&quot;: &quot;ec2.amazonaws.com&quot;
           },
           &quot;Action&quot;: &quot;sts:AssumeRole&quot;
       }
   ]
}
EOF
}

# Attaches a Managed IAM Policy to an IAM role
resource &quot;aws_iam_role_policy_attachment&quot; &quot;private_for_s3&quot; {
 role       = aws_iam_role.private.name
 policy_arn = &quot;arn:aws:iam::aws:policy/AmazonS3FullAccess&quot;
}

resource &quot;aws_iam_role_policy_attachment&quot; &quot;private_for_ssm&quot; {
 role       = aws_iam_role.private.name
 policy_arn = &quot;arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore&quot;
}

# IAM instance profile
resource &quot;aws_iam_instance_profile&quot; &quot;private&quot; {
 name = format(&quot;%s-private&quot;, lower(var.name))
 role = aws_iam_role.private.name
}</code></pre>
</li>
<li><p><code>variables.tf</code> 파일 구성
 <strong>root module</strong>에서 정의한 변수를 받아오기 위해 다음과 같이 <code>variable</code>을 설정했습니다.</p>
<pre><code class="language-bash"># Project name
variable &quot;name&quot; {}

# Tags
variable &quot;tags&quot; {}</code></pre>
</li>
<li><p><code>output.tf</code> 파일 구성
ec2 리소스에서 iam의 <code>iam_instance_profile</code> 변수를 사용하기 위해 <code>output</code>에 정의했습니다.</p>
<pre><code class="language-bash">output &quot;iam_instance_profile&quot; {
 value = aws_iam_instance_profile.private.name
}</code></pre>
</li>
</ol>
<hr>
<h3 id="root-module">root module</h3>
<ol>
<li><p><code>provider.tf</code> 파일 구성
<code>terraform.workspace</code>를 사용해 workspace를 변경할 때마다 환경이 적용되도록 구성했습니다.</p>
<pre><code class="language-bash">terraform {
 required_providers {
   aws = {
     source  = &quot;hashicorp/aws&quot;
     version = &quot;~&gt; 4.45.0&quot;
   }
 }

 required_version = &quot;&gt;= 1.2.0&quot;
}

provider &quot;aws&quot; {
 region  = var.region
 profile = terraform.workspace
}</code></pre>
</li>
<li><p><code>root.tf</code> 파일 구성
 <strong>root module</strong>에서 <strong>child module</strong>을 사용하기 위한 코드를 작성했습니다.
 각각의 모듈의 경로를 <code>source = &quot;./vpc&quot;</code> 다음과 같이 정의하고 시작하고, <strong>child module</strong>에 건네주기 위한 변수를 정의합니다.
 이제 파일 한 곳에서 모든 리소스를 통제할 수 있습니다. 🤗</p>
<pre><code class="language-bash">module &quot;vpc&quot; {
 # Required
 source = &quot;./vpc&quot;

 # environment = var.environment
 name     = var.name
 tags     = var.tags
 az_names = var.az_names

 vpc_cidr        = var.vpc_cidr
 public_subnets  = var.public_subnets
 private_subnets = var.private_subnets
}

module &quot;iam&quot; {
 # Required
 source = &quot;./iam&quot;

 name = var.name
 tags = var.tags
}

module &quot;ec2&quot; {
 # Required
 source = &quot;./ec2&quot;

 name     = var.name
 tags     = var.tags
 az_names = var.az_names

 public_subnets  = var.public_subnets
 private_subnets = var.private_subnets

 # module vpc
 pub_sub_ids = module.vpc.public_subnet_ids
 pri_sub_ids = module.vpc.private_subnet_ids

 # module iam
 iam_instance_profile = module.iam.iam_instance_profile

 # module sg
 security_group_id_public  = module.sg.security_group_id_public
 security_group_id_private = module.sg.security_group_id_private

 instance_disable_termination = var.instance_disable_termination
 key_name                     = &quot;${var.name}-key&quot;
 volume_size                  = var.ec2_volume_size
 ec2_type_public              = var.ec2_type_public
 ec2_type_private             = var.ec2_type_private
}

module &quot;sg&quot; {
 # Required
 source = &quot;./sg&quot;

 name = var.name
 tags = var.tags

 public_ingress_rules  = var.public_ingress_rules
 private_ingress_rules = var.private_ingress_rules

 # module vpc
 vpc_id = module.vpc.vpc_id
}</code></pre>
</li>
<li><p><code>terraform.tfvars.tf</code> 파일 구성
variables에서 사용하기 위한 변수를 <code>tfvars.tf</code>에서 정의했습니다.</p>
<pre><code class="language-bash"># Terraform setting
environment = &quot;dev&quot;
region      = &quot;ap-northeast-2&quot;

tags = {
 MadeBy = &quot;imok&quot;
}

# Project name
name = &quot;T101&quot;

# Network setting 
vpc_cidr = &quot;10.60.0.0/16&quot;

az_names = [
 &quot;ap-northeast-2a&quot;,
 &quot;ap-northeast-2c&quot;
]

public_subnets = {
 pub_sub_2a = {
   zone = &quot;ap-northeast-2a&quot;
   cidr = &quot;10.60.0.0/24&quot;
 },
 pub_sub_2c = {
   zone = &quot;ap-northeast-2c&quot;
   cidr = &quot;10.60.1.0/24&quot;
 }
}</code></pre>
</li>
<li><p><code>variables.tf</code> 파일 구성</p>
</li>
</ol>
<p><strong>root module</strong> 구성에 필요한 모든 변수를 정의합니다.</p>
<pre><code class="language-bash">  # env - e.g: dev|prd|stage
  variable &quot;environment&quot; {}

  # Region - e.g: ap-northeast-2
  variable &quot;region&quot; {}

  # project name
  variable &quot;name&quot; {}

  # EC2 instance type
  variable &quot;ec2_type_public&quot; {}
  variable &quot;ec2_type_private&quot; {}

  # EC2 volume size
  variable &quot;ec2_volume_size&quot; {}

  # EC2 termination protection
  variable &quot;instance_disable_termination&quot; {}

  # VPC default CIDR
  variable &quot;vpc_cidr&quot; {}

  # Availability Zones
  variable &quot;az_names&quot; {}

  # public subnet list
  variable &quot;public_subnets&quot; {}

  # private subnet list
  variable &quot;private_subnets&quot; {}

  # Tag
  variable &quot;tags&quot; {}

  # public ingress IP list
  variable &quot;public_ingress_rules&quot; {}

  # private ingress IP list
  variable &quot;private_ingress_rules&quot; {}

  # DB port
  variable &quot;db_port&quot; {}</code></pre>
<ol start="5">
<li><p><code>output.tf</code> 파일 구성
최종 출력이 필요한 output을 모두 정의합니다.</p>
<pre><code class="language-bash">output &quot;vpc_id&quot; {
 value = module.vpc.vpc_id
}

output &quot;public_subnet_ids&quot; {
 value = module.vpc.public_subnet_ids
}

output &quot;private_subnet_ids&quot; {
 value = module.vpc.private_subnet_ids
}

output &quot;nat_eip&quot; {
 value = module.vpc.nat_eip
}

output &quot;key_pair&quot; {
 value = module.ec2.key_pair
}

output &quot;public_eip&quot; {
 value = module.ec2.public_eip
}

output &quot;ec2_private_id&quot; {
 value = module.ec2.ec2_private_id
}</code></pre>
</li>
</ol>
<hr>
<h2 id="terraform-실행">Terraform 실행</h2>
<h3 id="terraform-output-확인">terraform output 확인</h3>
<pre><code class="language-bash">terraform output</code></pre>
<ul>
<li><strong>instance id 확인</strong> : i-080061b0ee6c5e7bb</li>
<li><strong>key-pair 확인</strong> : T101-key</li>
<li><strong>public ip 확인</strong> : 15.164.88.41
<img src="https://velog.velcdn.com/images/imok-_/post/941004c5-1c86-4d2f-a3e8-089b337bcd0b/image.png" alt=""></li>
</ul>
<hr>
<h3 id="생성된-리소스-및-접속-확인">생성된 리소스 및 접속 확인</h3>
<ol>
<li><p>VPC - Subnet
<img src="https://velog.velcdn.com/images/imok-_/post/885cdcd6-98ca-4270-a029-2880ed3892f2/image.png" alt=""></p>
</li>
<li><p>IAM Role
<img src="https://velog.velcdn.com/images/imok-_/post/380ade46-b4f5-4a79-a2e2-3957e1b2564f/image.png" alt=""></p>
</li>
<li><p>EC2
<img src="https://velog.velcdn.com/images/imok-_/post/c3891ae8-13fa-4ab2-a80b-0067abd39be9/image.png" alt=""></p>
</li>
</ol>
<ul>
<li>Public EC2 접속 : pem key 사용<pre><code class="language-bash">ssh -i &quot;T101-key.pem&quot; ec2-user@15.164.88.41</code></pre>
<img src="https://velog.velcdn.com/images/imok-_/post/ea367254-299d-4de1-a200-f407c1033b52/image.png" alt=""></li>
</ul>
<ul>
<li>Private EC2 접속 및 S3 접근 : aws ssm, s3 정책 적용한 iam role 사용<pre><code class="language-bash">aws ssm start-session \
    --target i-080061b0ee6c5e7bb</code></pre>
<img src="https://velog.velcdn.com/images/imok-_/post/078441d1-7961-40ea-90a5-f4124f744685/image.png" alt=""></li>
</ul>
<blockquote>
<p>👶🏻 OK는 이제 <code>root module</code> 파일 한 곳에서 모든 리소스를 통제, 관리할 수 있게 되었습니다. 💜</p>
</blockquote>
<p>완성 코드는 제 깃허브에 올려놨습니다 참고 부탁드립니다 :)
<a href="https://github.com/euneun316/terraform-study/tree/main/Modules/default">https://github.com/euneun316/terraform-study/tree/main/Modules/default</a></p>
<hr>
<h1 id="마치며">마치며..</h1>
<p>사실 <strong>terraform module</strong>은 이미 잘 구성된 코드들이 많습니다. 하지만, 정확하게 module에 대해서 파악하지 않으면 처음부터 그 소스를 가져와 활용하기 무척 어렵습니다. </p>
<p>이번에 <strong>terraform module</strong>을 직접 구성하고 나니, 이제야 전반적으로 terraform이 동작하는 원리에 대해 더 정확히 파악되었습니다.</p>
<p>현재는 EC2, IAM, VPC, SG와 같은 기본 서비스 구성만 완료된 상태지만,</p>
<p><strong>앞으로 kinesis, glue, athena와 같은 타 서비스 생성에 대한 기본 코드도 작성해 놓고, 필요할 때마다 코드를 활용할 수 있도록 module을 고도화할 생각입니다.</strong></p>
<p>테라폼 모듈의 고수가 될 그날까지... 🏃🏻‍♀️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform for each 활용해 vpc 생성하기]]></title>
            <link>https://velog.io/@imok-_/Terraform-foreach-%ED%99%9C%EC%9A%A9%ED%95%B4-vpc-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@imok-_/Terraform-foreach-%ED%99%9C%EC%9A%A9%ED%95%B4-vpc-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 10 Dec 2022 18:45:41 GMT</pubDate>
            <description><![CDATA[<h1 id="for_each-활용해-vpc-생성하기">for_each 활용해 vpc 생성하기</h1>
<p><code>count</code> 매개변수 사용 시 아래와 같은 제약사항이 있습니다.</p>
<blockquote>
<ol>
<li><strong>인라인 블록에 <code>count</code> 사용 지원하지 않음</strong></li>
</ol>
</blockquote>
<ul>
<li>전체 리소스를 반복할 수는 있지만 리소스 내에서 인라인 블록을 반복할 때, count 사용은 지원하지 않습니다.</li>
</ul>
<blockquote>
<ol start="2">
<li><strong>수정 시 발생하는 에러</strong></li>
</ol>
</blockquote>
<ul>
<li>배열의 중간 항목을 제거하면 모든 항목이 1칸씩 앞으로 당겨집니다.</li>
<li>즉 count 사용 시, 목록 중간 항목을 제거하면 테라폼은 해당 항목 뒤에 있는 모든 리소스를 삭제한 다음 해당 리소스를 처음부터 다시 만듭니다.</li>
</ul>
<p><em><strong>리소스의 여러 복사본을 만들 때는 count 대신 for_each 를 사용하는 것이 바람직합니다.</strong></em>
따라서 <code>for each</code> 표현식을 사용해 vpc를 생성하는 실습을 진행했습니다.</p>
<h2 id="for-each-표현식">for each 표현식</h2>
<p><a href="https://developer.hashicorp.com/terraform/language/meta-arguments/for_each">for_each 공식문서</a></p>
<p>for_each 표현식을 사용하면 <code>리스트 lists</code>, <code>집합 sets</code>, <code>맵 maps</code> 를 사용하여 전체 리소스의 여러 복사본 또는 리소스 내 인라인 블록의 여러 복사본, 모듈의 복사본을 생성 할 수 있습니다.</p>
<pre><code class="language-bash">resource &quot;&lt;PROVIDER&gt;_&lt;TYPE&gt;&quot; &quot;&lt;NAME&gt;&quot; {
  for_each = &lt;COLLECTION&gt;

  [CONFIG ...]
}</code></pre>
<ul>
<li><p><strong>COLLECTION</strong> 은 루프를 처리할 집합 또는 맵</p>
</li>
<li><p><strong>리소스에 for_each 를 사용할 때, 리스트는 지원 안함</strong></p>
</li>
<li><p>CONFIG 는 해당 리소스와 관련된 하나 이상의 인수로 구성되는데, CONFIG 내에서 <code>each.key</code>또는 <code>each.value</code> 를 사용하여 COLLECTION 에서 현재 항목의 키와 값에 접근할 수 있음</p>
</li>
</ul>
<h3 id="for-each-사용-예시">for each 사용 예시</h3>
<pre><code class="language-bash"># public subnet
resource &quot;aws_subnet&quot; &quot;pub_sub&quot; {
  for_each                = var.public_subnet
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = each.value[&quot;cidr&quot;]
  availability_zone       = each.value[&quot;az&quot;]
  map_public_ip_on_launch = false

  tags = {
    Name = &quot;${var.tags}-${each.key}&quot;
  }
}</code></pre>
<p>맵에서 값만 반환하는 내장 함수 <code>values</code> 사용 가능</p>
<pre><code class="language-bash">output &quot;pub_sub_ids&quot; {
  value = aws_subnet.pub_sub.*
}

output &quot;dev_pri_sub_ids&quot; {
  value = values(aws_subnet.dev_pri_sub)[*].id
}
</code></pre>
<h3 id="terraform-outputs">terraform outputs</h3>
<p>for_each 를 사용한 후에는 하나의 리소스 또는 count 를 사용한 것과 같은 <strong>리소스 배열이 되는 것이 아니라</strong> 리소스 맵 <code>list into a set</code> 이 됩니다.</p>
<pre><code class="language-bash">Outputs:

dev_pri_sub_ids = [
  &quot;subnet-0457a9a5e265772e4&quot;,
  &quot;subnet-08b233055afe17048&quot;,
]
pub_sub_ids = [
  {
    pub-sub-2a = {
      &quot;availability_zone&quot; = &quot;ap-northeast-2a&quot;
      &quot;availability_zone_id&quot; = &quot;apne2-az1&quot;
      &quot;cidr_block&quot; = &quot;10.60.4.0/24&quot;
      &quot;id&quot; = &quot;subnet-0caaebfec80d80359&quot;
      &quot;tags&quot; = tomap({
        &quot;Name&quot; = &quot;T101-pub-sub-2a&quot;
      })
      &quot;vpc_id&quot; = &quot;vpc-0473c85bd926f6873&quot;
    },
    pub-sub-2c = {
      &quot;availability_zone&quot; = &quot;ap-northeast-2c&quot;
      &quot;availability_zone_id&quot; = &quot;apne2-az3&quot;
      &quot;cidr_block&quot; = &quot;10.60.5.0/24&quot;
      &quot;id&quot; = &quot;subnet-0aaaf57e88b761897&quot;
      &quot;tags&quot; = tomap({
        &quot;Name&quot; = &quot;T101-pub-sub-2c&quot;
      })
      &quot;vpc_id&quot; = &quot;vpc-0473c85bd926f6873&quot;
    },
  }
]
</code></pre>
<hr>
<p>완성 코드는 제 깃허브에 올려놨습니다 참고 부탁드립니다 :)
<a href="https://github.com/euneun316/terraform-study/tree/main/T101/WEEK5/vpc">https://github.com/euneun316/terraform-study/tree/main/T101/WEEK5/vpc</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform 반복문 Loops 사용하기]]></title>
            <link>https://velog.io/@imok-_/Terraform-Loops</link>
            <guid>https://velog.io/@imok-_/Terraform-Loops</guid>
            <pubDate>Sat, 10 Dec 2022 17:35:23 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하기-앞서">시작하기 앞서..</h1>
<p>CloudNet@ Terraform Study 101 을 진행하며 학습한 내용을 정리했습니다.
스터디 교재는 테라폼 업앤러닝을 사용했습니다. </p>
<h1 id="반복문-loops">반복문 Loops</h1>
<p>테라폼이 제공하는 반복문 구성</p>
<ul>
<li><p><code>count</code> <strong>매개변수 parameter</strong> : 리소스와 모듈의 반복</p>
</li>
<li><p><code>for_each</code> <strong>표현식 expressions</strong> : 리소스 내에서 리소스 및 인라인 블록, 모듈을 반복</p>
</li>
<li><p><code>for</code> <strong>표현식 expressions</strong> : 리스트 lists 와 맵 maps 을 반복</p>
</li>
<li><p><code>for 문자열 지시어</code> <strong>string directive</strong> : 문자열 내에서 리스트 lists 와 맵 maps 을 반복</p>
</li>
</ul>
<hr>
<h2 id="count-매개-변수">count 매개 변수</h2>
<p><a href="https://developer.hashicorp.com/terraform/language/meta-arguments/count">count 공식문서</a></p>
<h3 id="countindex-사용">count.index 사용</h3>
<p><code>count.index</code> 를 사용하여 반복문 안에 있는 각각의 반복 <code>iteration</code> 을 가리키는 인덱스를 얻을 수 있음</p>
<pre><code class="language-bash"># main.tf
resource &quot;aws_iam_user&quot; &quot;myiam&quot; {
count = 3
name  = &quot;imok.${count.index+1}&quot;
}</code></pre>
<ul>
<li><code>terraform state list</code> 확인<pre><code class="language-bash">aws_iam_user.myiam[0]
aws_iam_user.myiam[1]
aws_iam_user.myiam[2]</code></pre>
</li>
</ul>
<img src="https://user-images.githubusercontent.com/44595181/202712893-fb38a3a6-c9c8-47c4-92c8-d846c248d98d.png" width="900"/>

<h3 id="배열-조회-구문과-length-함수-사용">배열 조회 구문과 length 함수 사용</h3>
<p><strong>배열 조회 구문</strong> Array lookup syntax : <code>ARRAY[&lt;INDEX&gt;]</code></p>
<ul>
<li>테라폼에서 count 와 함께 배열 조회 구문과 length 함수를 사용해서 사용자들 생성 가능</li>
<li>리소스에 count 사용한 후에는 하나의 리소스가 아니라 <strong>리소스의 배열</strong>이 됨</li>
</ul>
<pre><code class="language-bash"># main.tf
resource &quot;aws_iam_user&quot; &quot;myiam&quot; {
count = length(var.user_names)
name  = var.user_names[count.index]
}</code></pre>
<ul>
<li><code>terraform apply</code> 결과<pre><code class="language-bash">aws_iam_user.myiam[1]: Creating...
aws_iam_user.myiam[2]: Creating...
aws_iam_user.myiam[0]: Creating...
aws_iam_user.myiam[2]: Creation complete after 2s [id=imok3]
aws_iam_user.myiam[1]: Creation complete after 2s [id=imok2]
aws_iam_user.myiam[0]: Creation complete after 2s [id=imok1]
</code></pre>
</li>
</ul>
<p>Apply complete! Resources: 3 added, 0 changed, 0 destroyed.</p>
<p>Outputs:</p>
<p>all_arns = [
&quot;arn:aws:iam::[Account ID]:user/imok1&quot;,
&quot;arn:aws:iam::[Account ID]:user/imok2&quot;,
&quot;arn:aws:iam::[Account ID]:user/imok3&quot;,
]
first_arn = &quot;arn:aws:iam::[Account ID]:user/imok1&quot;</p>
<pre><code>
### count 제약 사항

1. 전체 리소스를 반복할 수는 있지만 리소스 내에서 인라인 블록을 반복할 수는 없음. 인라인 블록 내에서는 count 사용은 지원하지 않음

2. 수정 시 발생하는 에러

- 중간에 있는 imok2 user를 제거하고 plan &amp; apply

```bash
default = [&quot;imok1&quot;, &quot;imok2&quot;, &quot;imok3&quot;]
</code></pre><ul>
<li><code>terraform plan</code> 결과</li>
</ul>
<pre><code class="language-bash">~ update in-place
- destroy

Terraform will perform the following actions:

# aws_iam_user.myiam[1] will be updated in-place
~ resource &quot;aws_iam_user&quot; &quot;myiam&quot; {
        id            = &quot;imok2&quot;
    ~ name          = &quot;imok2&quot; -&gt; &quot;imok3&quot;
        tags          = {}
        # (5 unchanged attributes hidden)
    }

# aws_iam_user.myiam[2] will be destroyed
# (because index [2] is out of range for count)
- resource &quot;aws_iam_user&quot; &quot;myiam&quot; {
    - arn           = &quot;arn:aws:iam::[Account ID]:user/imok3&quot; -&gt; null
    - force_destroy = false -&gt; null
    - id            = &quot;imok3&quot; -&gt; null
    - name          = &quot;imok3&quot; -&gt; null
    - path          = &quot;/&quot; -&gt; null
    - tags          = {} -&gt; null
    - tags_all      = {} -&gt; null
    - unique_id     = &quot;fgdgdfgdfgd&quot; -&gt; null
    }

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

Changes to Outputs:
~ all_arns = [
        # (1 unchanged element hidden)
        &quot;arn:aws:iam::[Account ID]:user/imok2&quot;,
    - &quot;arn:aws:iam::[Account ID]:user/imok3&quot;,
    ]</code></pre>
<ul>
<li>배열의 중간에 항목을 제거하면 모든 항목이 1칸씩 앞으로 당겨짐.</li>
<li>테라폼이 인덱스 번호를 리소스 식별자로 보기 때문에 ‘인덱스 1에서는 계정 생성, 인덱스2에서는 계정 삭제한다’라고 해석함</li>
<li>즉 count 사용 시 목록 중간 항목을 제거하면 테라폼은 해당 항목 뒤에 있는 모든 리소스를 삭제한 다음 해당 리소스를 처음부터 다시 만듬.</li>
<li>따라서 위 예시에서 <strong>imok2 user를 삭제했지만, imok3 user가 삭제되는 에러 발생</strong></li>
</ul>
<img src="https://user-images.githubusercontent.com/44595181/202724133-c1f14dfc-4e82-47d0-97b3-47f96e81cd61.png" width="900"/>

<hr>
<h2 id="for-each-표현식">for each 표현식</h2>
<p><a href="https://developer.hashicorp.com/terraform/language/meta-arguments/for_each">for_each 공식문서</a></p>
<p>for_each 표현식을 사용하면 <code>리스트 lists</code>, <code>집합 sets</code>, <code>맵 maps</code> 를 사용하여 전체 리소스의 여러 복사본 또는 리소스 내 인라인 블록의 여러 복사본, 모듈의 복사본을 생성 할 수 있음</p>
<pre><code class="language-bash">resource &quot;&lt;PROVIDER&gt;_&lt;TYPE&gt;&quot; &quot;&lt;NAME&gt;&quot; {
  for_each = &lt;COLLECTION&gt;

  [CONFIG ...]
}</code></pre>
<ul>
<li><p><strong>COLLECTION</strong> 은 루프를 처리할 집합 sets 또는 맵 maps</p>
</li>
<li><p><strong>리소스에 for_each 를 사용할 때 리스트는 지원 안함</strong></p>
</li>
<li><p>CONFIG 는 해당 리소스와 관련된 하나 이상의 인수로 구성되는데 CONFIG 내에서 <code>each.key</code>또는 <code>each.value</code> 를 사용하여 COLLECTION 에서 현재 항목의 키와 값에 접근할 수 있음</p>
</li>
</ul>
<h3 id="for_each-예제">for_each 예제</h3>
<p>for_each 를 사용하여 3명의 IAM 사용자를 생성</p>
<p><code>var.user_names</code> 리스트를 집합(set)으로 변환하기 위해 <code>toset</code> 사용. </p>
<pre><code class="language-bash"># main.tf
resource &quot;aws_iam_user&quot; &quot;myiam&quot; {
  for_each = toset(var.user_names)
  name     = each.value
}</code></pre>
<p>for_each 를 사용한 후에는 하나의 리소스 또는 count 를 사용한 것과 같은 리소스 배열이 되는 것이 아니리 <strong>리소스 맵</strong> <code>list into a set</code> 이 됩니다.</p>
<ul>
<li><code>terraform state list</code> 확인</li>
</ul>
<pre><code class="language-bash">aws_iam_user.myiam[&quot;imok1&quot;]
aws_iam_user.myiam[&quot;imok2&quot;]
aws_iam_user.myiam[&quot;imok3&quot;]</code></pre>
<ul>
<li><code>terraform apply</code> 확인</li>
</ul>
<pre><code class="language-bash">aws_iam_user.myiam[&quot;imok2&quot;]: Creating...
aws_iam_user.myiam[&quot;imok3&quot;]: Creating...
aws_iam_user.myiam[&quot;imok1&quot;]: Creating...
aws_iam_user.myiam[&quot;imok3&quot;]: Creation complete after 2s [id=imok3]
aws_iam_user.myiam[&quot;imok2&quot;]: Creation complete after 2s [id=imok2]
aws_iam_user.myiam[&quot;imok1&quot;]: Creation complete after 2s [id=imok1]

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

Outputs:

all_users = [
  &quot;arn:aws:iam::[Account ID]:user/imok1&quot;,
  &quot;arn:aws:iam::[Account ID]:user/imok2&quot;,
  &quot;arn:aws:iam::[Account ID]:user/imok3&quot;,
]</code></pre>
<h3 id="리소스-중간-값-제거-테스트">리소스 중간 값 제거 테스트</h3>
<p>for_each 를 사용해 <strong>리소스를 맵으로 처리</strong>하면 컬렉션 중간의 항목도 안전하게 제거할 수 있어서, count 로 리소스를 배열 처리보다 이점이 큽니다.</p>
<p>중간에 있는 imok2 user를 제거하고 plan &amp; apply</p>
<ul>
<li><p><code>terraform plan</code> 결과</p>
<p>이제 주변 모든 리소스를 옮기지 않고 정확히 목표한 리소스만 삭제가 됩니다.</p>
</li>
</ul>
<pre><code class="language-bash">  - destroy

Terraform will perform the following actions:

  # aws_iam_user.myiam[&quot;imok2&quot;] will be destroyed
  # (because key [&quot;imok2&quot;] is not in for_each map)
  - resource &quot;aws_iam_user&quot; &quot;myiam&quot; {
      - arn           = &quot;arn:aws:iam::[Account ID]:user/imok2&quot; -&gt; null
      - force_destroy = false -&gt; null
      - id            = &quot;imok2&quot; -&gt; null
      - name          = &quot;imok2&quot; -&gt; null
      - path          = &quot;/&quot; -&gt; null
      - tags          = {} -&gt; null
      - tags_all      = {} -&gt; null
      - unique_id     = &quot;dsdsdsdsdsd&quot; -&gt; null
    }

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

Changes to Outputs:
  ~ all_users = [
        &quot;arn:aws:iam::[Account ID]:user/imok1&quot;,
      - &quot;arn:aws:iam::[Account ID]:user/imok2&quot;,
        &quot;arn:aws:iam::[Account ID]:user/imok3&quot;,
    ]</code></pre>
<hr>
<h2 id="for-표현식">for 표현식</h2>
<p><a href="https://developer.hashicorp.com/terraform/language/expressions/for">for Expressions 공식문서</a></p>
<p>단일 값을 생성하기 위해 반복이 필요한 경우 사용</p>
<h3 id="for-expressions---list-예제">for Expressions - list 예제</h3>
<p>조건을 지정해서 결과 리스트를 필터링할 수 있음 </br>
list를 사용한 for 표현식 : <code>[for &lt;ITEM&gt; in &lt;LIST&gt; : &lt;OUTPUT&gt;]</code></p>
<ul>
<li>LIST : 반복할 리스트</li>
<li>ITEM : LIST의 각 항목에 할당할 변수의 이름</li>
<li>OUTPUT : ITEM을 어떤 식으로든 변환하는 표현식</li>
</ul>
<pre><code class="language-bash"># main.tf
variable &quot;names&quot; {
  description = &quot;A list of names&quot;
  type        = list(string)
  default     = [&quot;imok22&quot;, &quot;imok1&quot;, &quot;imok333&quot;]
}

output &quot;upper_names&quot; {
  value = [for name in var.names : upper(name)]
}

output &quot;short_upper_names&quot; {
  value = [for name in var.names : upper(name) if length(name) &lt; 6]
}</code></pre>
<ul>
<li><code>terraform output</code> 결과</li>
</ul>
<pre><code class="language-bash">short_upper_names = [
  &quot;IMOK1&quot;,
]
upper_names = [
  &quot;IMOK22&quot;,
  &quot;IMOK1&quot;,
  &quot;IMOK333&quot;,
]</code></pre>
<h3 id="for-expressions---map-예제">for Expressions - map 예제</h3>
<p>map을 사용한 for 표현식 : <code>[for &lt;KEY&gt;, &lt;VALUE&gt; in &lt;MAP&gt; : &lt;OUTPUT&gt;]</code></p>
<ul>
<li>MAP : 반복되는 맵</li>
<li>KEY, VALUE : MAP의 각 키-값 쌍에 할당할 로컬 변수의 이름</li>
<li>OUTPUT : KEY와 VALUE를 어떤 식으로든 변환하는 표현식</li>
</ul>
<pre><code class="language-bash">#main.tf
variable &quot;names&quot; {
  description = &quot;A list of names&quot;
  type        = list(string)
  default     = [&quot;imok1&quot;, &quot;imok22&quot;, &quot;imok333&quot;]
}

output &quot;upper_names&quot; {
  value = [for name in var.names : upper(name)]
}

output &quot;short_upper_names&quot; {
  value = [for name in var.names : upper(name) if length(name) &lt; 5]
}

variable &quot;hero_thousand_faces&quot; {
  description = &quot;map&quot;
  type        = map(string)
  default = {
    imok1   = &quot;hero&quot;
    imok22  = &quot;love interest&quot;
    imok333 = &quot;mentor&quot;
  }
}

output &quot;bios&quot; {
  value = [for name, role in var.hero_thousand_faces : &quot;${name} is the ${role}&quot;]
}</code></pre>
<ul>
<li><code>terraform output</code> 결과<pre><code class="language-bash">bios = [
&quot;imok1 is the hero&quot;,
&quot;imok22 is the love interest&quot;,
&quot;imok333 is the mentor&quot;,
]
short_upper_names = []
upper_names = [
&quot;IMOK1&quot;,
&quot;IMOK22&quot;,
&quot;IMOK333&quot;,
]</code></pre>
</li>
</ul>
<h3 id="for-expressions---출력-예제">for Expressions - 출력 예제</h3>
<p>for 표현식을 리스트가 아닌 맵을 출력하는 데 사용할 수도 있음</p>
<ul>
<li><p>리스트를 반복하고 맵을 출력 Loop over a list and output a map </br>
<code>{for &lt;ITEM&gt; in &lt;LIST&gt; : &lt;OUTPUT_KEY&gt; =&gt; &lt;OUTPUT_VALUE&gt;}</code></p>
</li>
<li><p>맵을 반복하고 리스트를 출력 Loop over a map and output a map </br>
<code>{for &lt;KEY&gt;, &lt;VALUE&gt; in &lt;MAP&gt; : &lt;OUTPUT_KEY&gt; =&gt; &lt;OUTPUT_VALUE&gt;}</code></p>
</li>
</ul>
<pre><code class="language-bash">#main.tf
variable &quot;names&quot; {
  description = &quot;A list of names&quot;
  type        = list(string)
  default     = [&quot;imok1&quot;, &quot;imok22&quot;, &quot;imok333&quot;]
}

output &quot;upper_names&quot; {
  value = [for name in var.names : upper(name)]
}

output &quot;short_upper_names&quot; {
  value = [for name in var.names : upper(name) if length(name) &lt; 6]
}

variable &quot;hero_thousand_faces&quot; {
  description = &quot;map&quot;
  type        = map(string)
  default = {
    imok1   = &quot;hero&quot;
    imok22  = &quot;love interest&quot;
    imok333 = &quot;mentor&quot;
  }
}

output &quot;bios&quot; {
  value = [for name, role in var.hero_thousand_faces : &quot;${name} is the ${role}&quot;]
}

output &quot;upper_roles&quot; {
  value = { for name, role in var.hero_thousand_faces : upper(name) =&gt; upper(role) }
}
</code></pre>
<ul>
<li><code>terraform output</code> 결과<pre><code class="language-bash">bios = [
&quot;imok1 is the hero&quot;,
&quot;imok22 is the love interest&quot;,
&quot;imok333 is the mentor&quot;,
]
short_upper_names = [
&quot;IMOK1&quot;,
]
upper_names = [
&quot;IMOK1&quot;,
&quot;IMOK22&quot;,
&quot;IMOK333&quot;,
]
upper_roles = {
&quot;IMOK1&quot; = &quot;HERO&quot;
&quot;IMOK22&quot; = &quot;LOVE INTEREST&quot;
&quot;IMOK333&quot; = &quot;MENTOR&quot;
}</code></pre>
</li>
</ul>
<hr>
<h2 id="문자열-지시자string-derective">문자열 지시자(String Derective)</h2>
<p>문자열 지시자를 사용하면 문자열 보간과 유사한 구문으로 문자열 내에서 for 반복문, if문 같은 제어문을 사용할 수 있음</br></p>
<ul>
<li><p>문자열 보간법 : <code>&quot;Hello, ${var.name}&quot;</code></p>
</li>
<li><p>달러 부호와 중괄호 <code>${..}</code> 대신 백분율 부호 <code>%{..}</code> 를 사용한다는 차이가 있음 </p>
</li>
</ul>
</br>

<p>for 문자열 지시자 : <code>%{ for &lt;ITEM&gt; in &lt;COLLECTION&gt; }&lt;BODY&gt;%{ endfor }</code></p>
<ul>
<li>COLLECTION : 반복할 리스트 또는 맵</li>
<li>ITEM : COLLECTION 의 각 항목에 할당할 로컬 변수의 이름</li>
<li>BODY : ITEM을 참조할 수 있는 각각의 반복을 렌더링하는 대상</li>
</ul>
<h3 id="string-derective-예제">String Derective 예제</h3>
<pre><code class="language-bash">variable &quot;names&quot; {
  description = &quot;Names to render&quot;
  type        = list(string)
  default     = [&quot;imok1&quot;, &quot;imok22&quot;, &quot;imok33&quot;]
}

output &quot;for_directive&quot; {
  value = &quot;%{ for name in var.names }${name}, %{ endfor }&quot;
}</code></pre>
<ul>
<li><code>terraform output</code> 결과</li>
</ul>
<pre><code class="language-bash">for_directive = &quot;imok1, imok22, imok33, &quot;</code></pre>
<ul>
<li>index 추가</li>
</ul>
<pre><code class="language-bash">variable &quot;names&quot; {
  description = &quot;Names to render&quot;
  type        = list(string)
  default     = [&quot;imok1&quot;, &quot;imok22&quot;, &quot;imok33&quot;]
}

output &quot;for_directive&quot; {
  value = &quot;%{ for name in var.names }${name}, %{ endfor }&quot;
}

output &quot;for_directive_index&quot; {
  value = &quot;%{ for i, name in var.names }(${i}) ${name}, %{ endfor }&quot;
}
</code></pre>
<ul>
<li><code>terraform output</code> 결과</li>
</ul>
<pre><code class="language-bash">for_directive = &quot;imok1, imok22, imok33, &quot;
for_directive_index = &quot;(0) imok1, (1) imok22, (2) imok33, &quot;</code></pre>
<hr>
<p>완성 코드는 제 깃허브에 올려놨습니다 참고 부탁드립니다 :)
<a href="https://github.com/euneun316/terraform-study/tree/main/T101/WEEK5">https://github.com/euneun316/terraform-study/tree/main/T101/WEEK5</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform 기본 인프라 구축하기]]></title>
            <link>https://velog.io/@imok-_/Terraform-AWS-Default-Infra</link>
            <guid>https://velog.io/@imok-_/Terraform-AWS-Default-Infra</guid>
            <pubDate>Thu, 10 Nov 2022 16:51:04 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하기-앞서">시작하기 앞서..</h1>
<p><strong>CloudNet@ Terraform Study 101</strong> 을 진행하며 학습한 내용을 바탕으로 업무에 활용한 내용을 작성했습니다.
따라서 AWS와 terraform에 대한 기본 개념 내용은 생략한 경우가 많습니다.</p>
<h2 id="terraform-스터디를-시작한-계기">Terraform 스터디를 시작한 계기</h2>
<p><strong>클라우드 서비스 공급자인 AWS는</strong> 고객이 인프라 구성을 간편하게 할 수 있도록 <strong>웹 콘솔</strong>을 지원하고 있습니다. </p>
<p>성향에 따라 이런 GUI 환경을 좋아하시는 분들이 있지만, 
저 같은 경우는 <strong>반복적인 작업이 필요한 경우 휴먼 에러를 방지할 수 있고</strong>,
생성하고자 하는 <strong>리소스를 한눈에 파악하기 쉽기 때문에</strong> 콘솔 작업보다 코드의 작업을 더 선호하는 편입니다.</p>
<p>처음 AWS를 접하고 <strong>VPC, EC2, SG, ELB</strong>과 같은 기본 서비스를 생성하고자 하면 어려울 수 있지만, 
한번 익히고 나면 지루한 반복 작업으로 느껴질 수 있습니다.</p>
<p>따라서 저는 간단하지만 자주 사용하는 기본 인프라 구성을 <strong>코드형 인프라(Infrastructure as Code, IaC)</strong>중 하나인 <strong>Terraform</strong>으로 작성해 업무에 활용하고자 스터디를 시작하게 되었습니다.</p>
<p>⚠️ <strong><em>본 내용은 개인적으로 Terraform을 처음 사용하면서 활용한 내용이기 때문에 틀릴 수 있습니다.</em></strong></p>
<h3 id="tldr-노가다-하기-싫어서-terraform-시작함-🙂-근데-너무-어렵다-그치만-재밌다"><strong>TL;DR: 노가다 하기 싫어서 Terraform 시작함 🙂 근데 너무 어렵다.. 그치만 재밌다</strong></h3>
<hr>
<h1 id="인프라-구축-전-체크-사항">인프라 구축 전 체크 사항</h1>
<blockquote>
<p>A 기업에서 새로운 프로젝트 도입에 앞서 AWS에 테스트 환경을 구축하고자, 주니어 사원 👶🏻 OK에게 다음과 같은 인프라를 생성해줄 것을 요청했다고 가정해봤습니다.</p>
</blockquote>
<h2 id="인프라-구축-요구-사항-체크-✅">인프라 구축 요구 사항 체크 ✅</h2>
<ol>
<li><p>서비스 개발을 위해 사용할 서버는 <strong>Private Subnet</strong> 환경으로 구성</p>
</li>
<li><p>서버는 <strong>개발(Dev) 환경과 운영(Prod) 환경</strong>으로 나눠서 구성 필요</p>
<ul>
<li>나중에 서버 환경은 추가될 수 있음</li>
<li>운영 서버 환경은 고가용성을 위해 <strong>2개의 가용영역(AZ)으로 이중화 구성</strong></li>
</ul>
</li>
<li><p><strong>SG(Security Group)</strong>에 다양한 <strong>Inbound Source</strong> 정보 등록 필요 </p>
<ul>
<li>환경에 따라 Port 및 Source 정보는 달라질 수 있음</li>
</ul>
</li>
<li><p>ALB에 HTTPS로 리스너 설정해 <strong>SSL Offload 수행</strong></p>
<ul>
<li>ALB는 개발 환경과 서비스별로 구분해서 생성</li>
<li>Target Group port는 언제든 변경, 추가될 수 있음</li>
</ul>
</li>
</ol>
<hr>
<blockquote>
<p>👶🏻 OK는 요구 사항을 확인하고, Terraform 구성 전에 미리 구축 내용을 작성했습니다.</p>
</blockquote>
<h2 id="구축-내용-✅">구축 내용 ✅</h2>
<p><strong>1. VPC(Virtual Private Cloud)</strong></p>
<ul>
<li>IP 대역 : <code>10.60.0.0/16</code></li>
<li>On-Premise 환경에서 AWS의 <strong>Private Subnet</strong> 환경에 접속하기 위해, <strong>인터넷 게이트웨이</strong>와 <strong>NAT 게이트웨이</strong>로 구성</li>
</ul>
<p><strong>2. Subnet 구성</strong></p>
<ul>
<li><p>Region : ap-northeast-2 [Asia Pacific (Seoul)]</p>
<table>
<thead>
<tr>
<th>Env</th>
<th>AZ</th>
<th>Subnet</th>
<th>IPv4 CIDR</th>
</tr>
</thead>
<tbody><tr>
<td>Common</td>
<td>ap-northeast-2a</td>
<td>imok-corp-pub-sub-2a</td>
<td>10.60.4.0/24</td>
</tr>
<tr>
<td>Common</td>
<td>ap-northeast-2c</td>
<td>imok-corp-pub-sub-2c</td>
<td>10.60.5.0/24</td>
</tr>
<tr>
<td>Dev</td>
<td>ap-northeast-2a</td>
<td>imok-corp-dev-pri-sub-2a</td>
<td>10.60.0.0/24</td>
</tr>
<tr>
<td>Dev</td>
<td>ap-northeast-2c</td>
<td>imok-corp-dev-pri-sub-2c</td>
<td>10.60.1.0/24</td>
</tr>
<tr>
<td>Prod</td>
<td>ap-northeast-2a</td>
<td>imok-corp-prd-pri-sub-2a</td>
<td>10.60.2.0/24</td>
</tr>
<tr>
<td>Prod</td>
<td>ap-northeast-2c</td>
<td>imok-corp-prd-pri-sub-2c</td>
<td>10.60.3.0/24</td>
</tr>
</tbody></table>
</li>
</ul>
<p><strong>3. SG(Security Group)</strong></p>
<ul>
<li><p>EC2 접속을 위한 On-Premise의 <strong>IP Inbound Source</strong> 정보 (IP는 보안을 위해 임의로 생성했습니다.)</p>
<table>
<thead>
<tr>
<th>CIDR</th>
<th>Desc</th>
</tr>
</thead>
<tbody><tr>
<td>18.164.239.93/32</td>
<td>ImOK Tower wireless ip</td>
</tr>
<tr>
<td>111.245.120.118/32</td>
<td>ImOK Tower lan</td>
</tr>
<tr>
<td>179.245.107.211/32</td>
<td>ImOK Corp VPN</td>
</tr>
</tbody></table>
</li>
</ul>
<p><strong>4. EC2</strong></p>
<ul>
<li><p>Private EC2에 ssh 접속은 <strong>Bastion Host를 통한 Tunneling 방식</strong>으로 접속</p>
</li>
<li><p>OS : Amazon Linux 2</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>용도</th>
<th>Name</th>
<th>CPU</th>
<th>Memory</th>
<th>Disk</th>
<th>Type</th>
</tr>
</thead>
<tbody><tr>
<td>Common</td>
<td>Bastion Host</td>
<td>imok-corp-bastion</td>
<td>2</td>
<td>2GiB</td>
<td>10GiB</td>
<td>t3.small</td>
</tr>
<tr>
<td>Dev</td>
<td>Management</td>
<td>imok-corp-dev-management-2a</td>
<td>2</td>
<td>16GiB</td>
<td>100GiB</td>
<td>r5.large</td>
</tr>
<tr>
<td>Dev</td>
<td>Service</td>
<td>imok-corp-dev-service-2a</td>
<td>2</td>
<td>4GiB</td>
<td>50GiB</td>
<td>t3.medium</td>
</tr>
<tr>
<td>Prd</td>
<td>Management</td>
<td>imok-corp-prd-management-2a</td>
<td>4</td>
<td>16GiB</td>
<td>100GiB</td>
<td>t3.xlarge</td>
</tr>
<tr>
<td>Prd</td>
<td>Service</td>
<td>imok-corp-prd-service-2a</td>
<td>4</td>
<td>16GiB</td>
<td>50GiB</td>
<td>t3.xlarge</td>
</tr>
<tr>
<td>Prd</td>
<td>Service</td>
<td>imok-corp-prd-service-2c</td>
<td>4</td>
<td>16GiB</td>
<td>50GiB</td>
<td>t3.xlarge</td>
</tr>
</tbody></table>
</li>
</ul>
<p><strong>5. ALB(Application Load Balancer)</strong></p>
<table>
<thead>
<tr>
<th align="center">ALB</th>
<th align="center">LB Listener Port</th>
<th align="center">Target Group port</th>
<th align="center">Target Instance</th>
</tr>
</thead>
<tbody><tr>
<td align="center">imok-corp-dev-alb-management</td>
<td align="center">80 -&gt; 443 Redirect</td>
<td align="center">8080</td>
<td align="center">imok-corp-dev-management-2a</td>
</tr>
<tr>
<td align="center">imok-corp-dev-alb-service</td>
<td align="center">80 -&gt; 443 Redirect</td>
<td align="center">8085, 8086</td>
<td align="center">imok-corp-dev-service-2a</td>
</tr>
<tr>
<td align="center">imok-corp-prd-alb-management</td>
<td align="center">80 -&gt; 443 Redirect</td>
<td align="center">8080</td>
<td align="center">imok-corp-prd-management-2a</td>
</tr>
<tr>
<td align="center">imok-corp-prd-alb-service</td>
<td align="center">80 -&gt; 443 Redirect</td>
<td align="center">8085, 8086</td>
<td align="center">imok-corp-prd-service-2a</td>
</tr>
<tr>
<td align="center">imok-corp-prd-service-2c</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p>*<em>6. ACM(AWS Certificate Manager) *</em></p>
<ul>
<li>AWS ACM 발급을 위해 외부 도메인 호스팅 업체에 <a href="https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html">DNS 검증 과정</a>을 진행</li>
<li>검증 완료 후 Status : ✅ Issued 상태
<img src="https://velog.velcdn.com/images/imok-_/post/a14069b3-b93a-4be1-8df1-92e3b5a09a8d/image.png" alt=""></li>
</ul>
<hr>
<blockquote>
<p>👶🏻 OK는 다음과 같이 구축하게 될 예상 아키텍처를 그려봤습니다.</p>
</blockquote>
<h2 id="구축-예상-아키텍처-✅">구축 예상 아키텍처 ✅</h2>
<p><img src="https://velog.velcdn.com/images/imok-_/post/b8e681d3-f709-41e3-b962-6819703c34a6/image.png" alt=""></p>
<hr>
<h1 id="인프라-구축">인프라 구축</h1>
<blockquote>
<p>👶🏻 OK는 향후 비슷한 업무를 맡게되었을 때, 미리 작성해 놓은 Terraform 코드를 <strong>재가공</strong>해 빠르게 인프라를 구축하고자 하는 목적이 있었습니다. 따라서 다음과 같은 목표를 세웠습니다.</p>
<blockquote>
<p><strong>💡 재사용 가능한 코드 작성을 위해 서비스별로 코드를 분리하기</strong>
<strong>💡 최대한 서비스 구축을 위한 코드는 수정하지않고, 요구사항의 변화에 맞춰 variables 파일만 수정해 사용하기</strong></p>
</blockquote>
</blockquote>
<hr>
<h2 id="terraform-사용-준비">Terraform 사용 준비</h2>
<p>terraform은 다음과 같은 <strong>workflow</strong>로 동작합니다.</p>
<p><img src="https://content.hashicorp.com/api/assets?product=tutorials&version=main&asset=public%2Fimg%2Fterraform%2Fterraform-iac.png" alt="">
<a href="https://learn.hashicorp.com/tutorials/terraform/infrastructure-as-code?in=terraform/aws-get-started">https://learn.hashicorp.com/tutorials/terraform/infrastructure-as-code?in=terraform/aws-get-started</a></p>
<h3 id="aws-credentials">AWS credentials</h3>
<p>저는 업무 특성상 여러 AWS 계정을 다뤄야하기 때문에 <strong>profile</strong>로 <strong>aws configure</strong>를 관리하고 있습니다.
따라서 <code>export AWS_PROFILE=&quot;imok&quot;</code>로 아래와 같이 profile을 미리 정의하고 시작합니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/c865c1c4-7676-4877-a005-229c2c9b13a2/image.png" alt=""></p>
<h3 id="variables">Variables</h3>
<p>보통 테라폼 코드를 작성할 때 <code>variables.tf</code> 파일을 만들어 해당 파일에 변수를 저장합니다.
company명, service명, service port 등 <strong>인프라 생성 시에 바뀔 수 있는 부분</strong>을 변수로 지정해 사용했습니다.</p>
<h3 id="terraformtfvars">terraform.tfvars</h3>
<p>정의한 변수에 값을 주입하기 위해 가장 일반적인 방법은 <code>terraform.tfvars</code> 파일을 생성하는 것입니다. <code>variable = value</code> 형태로 정의합니다.
저 같은 경우는 보안그룹에 추가해야할 Inbound Source 정보를 모두 <code>terraform.tfvars</code> 파일에 정의해 놓고 사용했습니다.</p>
<pre><code class="language-bash"># terraform.tfvars파일 작성 예시
bastion_ingress_rules = [
  {
    from_port = &quot;22&quot;,
    to_port   = &quot;22&quot;,
    cidr      = &quot;18.164.239.93/32&quot;
    desc      = &quot;ImOK Tower wireless ip&quot;
  },
  {
    from_port = &quot;22&quot;,
    to_port   = &quot;22&quot;,
    cidr      = &quot;111.245.120.118/32&quot;
    desc      = &quot;ImOK Tower lan&quot;
  }
]</code></pre>
<p>AWS ACM의 경우 보통 기업의 상황에 따라 도메인 호스팅 업체에 등록 후 사용하는 경우가 많기 때문에, Terraform 코드로 생성하는 것이 아닌, AWS Console에서 생성 후 ARN을 tfvars에 등록하는 방법을 택했습니다.</p>
<h3 id="terraform-init">terraform init</h3>
<p><code>terraform init</code> 명령어를 실행하면, local의 현재 디렉터리 아래에 아래 캡처 화면과 같이 <strong>미리 선언된 프로바이더(AWS) 플러그인</strong>을 설치해 줍니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/90d8489b-5090-4eee-aaac-4c4ffb2b48bd/image.png" alt=""></p>
<p>init 작업을 완료하면, 테라폼의 꽃🌺인 <code>.tfstate</code> 파일과 <strong>.tfstate</strong>에 정의된 내용을 담은 <code>.terraform</code> 파일이 생성됩니다. 
<code>.tfstate</code> 파일은 상태 저장을 위한 파일로, 기존에 다른 개발자가 이미 <strong>.tfstate</strong>에 인프라를 정의해 놓은 것이 있다면, 다른 개발자는 <strong>init작업을 통해서 local에 sync를 맞출 수 있습니다.</strong></p>
<pre><code>.terraform
├── environment
├── providers
│   └── registry.terraform.io
│       └── hashicorp
│           └── aws
│               └── 4.38.0
│                   └── darwin_arm64
│                       └── terraform-provider-aws_v4.38.0_x5
└── terraform.tfstate</code></pre><p>이제 인프라 생성을 위한 초기 준비가 완료됐습니다.</p>
<hr>
<h2 id="terraform-구성">Terraform 구성</h2>
<p>저는 다음과 같이 10개의 영역으로 구분해 코드를 작성했습니다.</p>
<pre><code>terraform_study
├── alb.tf
├── ami.tf
├── ec2.tf
├── key-pair.tf
├── output.tf
├── provider.tf
├── sg.tf
├── terraform.tfvars
├── variables.tf
└── vpc.tf</code></pre><h3 id="provider">Provider</h3>
<ol>
<li><code>variables.tf</code> 파일 구성</li>
</ol>
<p><strong>profile</strong>에 쓰일 account와 <strong>region</strong>을 미리 variables에 정의했습니다.</p>
<pre><code class="language-bash">  variable &quot;account&quot; {
    default     = &quot;imok&quot;
    description = &quot;aws account&quot;
  }

  variable &quot;region&quot; {
    type    = string
    default = &quot;ap-northeast-2&quot;
  }</code></pre>
<ol start="2">
<li><p><code>provider.tf</code> 파일 구성</p>
<pre><code class="language-bash">provider &quot;aws&quot; {
 region  = var.region
 profile = var.account
}</code></pre>
</li>
</ol>
<h3 id="key-pair-resource">key-pair Resource</h3>
<ol>
<li><p><code>variables.tf</code> 파일 구성
key에 붙는 이름은 기업명, 프로젝트명에 따라 달라지기 때문에 <strong>tags</strong>를 미리 variables에 정의했습니다.</p>
<pre><code class="language-bash">variable &quot;tags&quot; {
 type        = string
 default     = &quot;imok-corp&quot;
 description = &quot;Additional company tags&quot;
}</code></pre>
</li>
<li><p><code>key-pair.tf</code> 파일 구성</p>
<pre><code class="language-bash"># Generates a secure private key and encodes it as PEM
resource &quot;tls_private_key&quot; &quot;key_pair&quot; {
 algorithm = &quot;RSA&quot;
 rsa_bits  = 4096
}
# Create the Key Pair
resource &quot;aws_key_pair&quot; &quot;key_pair&quot; {
 key_name   = &quot;${var.tags}-key&quot;
 public_key = tls_private_key.key_pair.public_key_openssh
}
# Save Pem Key
resource &quot;local_file&quot; &quot;ssh_key&quot; {
 filename = &quot;${aws_key_pair.key_pair.key_name}.pem&quot;
 content  = tls_private_key.key_pair.private_key_pem
}</code></pre>
</li>
</ol>
<h3 id="vpc-resouece">VPC Resouece</h3>
<ol>
<li><p><code>variables.tf</code> 파일 구성
vpc에 사용할 <strong>ip 대역대와 subnet 대역대, az 정보</strong>를 미리 variables에 정의했습니다.</p>
<pre><code class="language-bash">variable &quot;aws_az&quot; {
 type    = list(any)
 default = [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2c&quot;]
}

variable &quot;aws_az_des&quot; {
 type    = list(any)
 default = [&quot;2a&quot;, &quot;2c&quot;]
}

variable &quot;vpc_cidr&quot; {
 type    = string
 default = &quot;10.60.0.0/16&quot;
}

variable &quot;dev_private_subnet&quot; {
 type    = list(any)
 default = [&quot;10.60.0.0/24&quot;, &quot;10.60.1.0/24&quot;]
}

variable &quot;prd_private_subnet&quot; {
 type    = list(any)
 default = [&quot;10.60.2.0/24&quot;, &quot;10.60.3.0/24&quot;]
}

variable &quot;public_subnet&quot; {
 type    = list(any)
 default = [&quot;10.60.4.0/24&quot;, &quot;10.60.5.0/24&quot;]
}</code></pre>
</li>
</ol>
<ol start="2">
<li><code>vpc.tf</code> 파일 구성
subnet은 보통 az 2개의 쌍으로 구성하기 때문에, 코드의 반복을 줄이고자 <strong>count</strong> 라는 메타 변수를 사용했습니다. 
variables에서 정의한 aws_az의 개수가 2 이기 때문에, <strong>length</strong> 함수를 이용해 길이를 계산해 count 변수에서 정의했습니다.</li>
</ol>
<p><strong>따라서 <code>count = length(var.aws_az)</code> 로 count = 2가 되어 총 2개의 리소스가 생성됩니다.</strong></p>
<p>  count가 설정된 스탠자(stanza)에서 <strong>index</strong> 라는 객체를 사용할 수 있기 때문에, variables에서 정의한 public_subnet 변수에 <code>count.index</code>를 사용했습니다. </br></p>
<p> 💡_ 저는 terraform 초보이고 생성할 리소스가 2개뿐이어서 count를 사용했지만, 생성할 리소스가 많을 때는  <code>for each</code> 문을 사용하는 것이 더 올바른 방법이라는 의견이 많습니다. 따라서 좀 더 학습 후 <code>for each</code> 문으로 변경해볼 예정입니다._</p>
<p> <strong><em>2022.12.11 for_each 문으로 변경 완료</em></strong> 😘 <a href="https://velog.io/@imok-_/Terraform-foreach-%ED%99%9C%EC%9A%A9%ED%95%B4-vpc-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0">for_each 활용해 vpc 생성하기</a></p>
<pre><code class="language-bash">  ## vpc
  resource &quot;aws_vpc&quot; &quot;vpc&quot; {
    cidr_block           = var.vpc_cidr
    enable_dns_support   = true
    enable_dns_hostnames = true

    tags = {
      Name = &quot;${var.tags}-vpc&quot;
    }
  }
  ## Subnet
  # public subnet
  resource &quot;aws_subnet&quot; &quot;pub_sub&quot; {
    count                   = length(var.aws_az)
    vpc_id                  = aws_vpc.vpc.id
    cidr_block              = var.public_subnet[count.index]
    availability_zone       = var.aws_az[count.index]
    map_public_ip_on_launch = false

    tags = {
      Name = &quot;${var.tags}-pub-sub-${var.aws_az_des[count.index]}&quot;
    }
  }</code></pre>
<h3 id="ec2-ami-data-정의">EC2 AMI data 정의</h3>
<p>OS 요구에 따라 다양한 EC2 AMI를 활용하기 위해 최신 AMI 정보를 미리 구성했습니다.</p>
<p>*<em>1. Amazon Linux2 *</em></p>
<pre><code class="language-bash"># amazon linux2 ami latest
data &quot;aws_ami&quot; &quot;amazon-linux-2&quot; {
  most_recent = true
  owners      = [&quot;amazon&quot;]

  filter {
    name = &quot;name&quot;
    # values = [&quot;amzn2-ami-hvm*&quot;]
    values = [&quot;amzn2-ami-hvm-*-gp2&quot;]
  }
  filter {
    name   = &quot;root-device-type&quot;
    values = [&quot;ebs&quot;]
  }
  filter {
    name   = &quot;architecture&quot;
    values = [&quot;x86_64&quot;]
  }
  filter {
    name   = &quot;virtualization-type&quot;
    values = [&quot;hvm&quot;]
  }
}</code></pre>
<p><strong>2. Amazon Linux2 kernel-5</strong></p>
<pre><code class="language-bash"># amazon linux2 ami latest kernel-5
data &quot;aws_ami&quot; &quot;amazon-linux-2-kernel-5&quot; {
  most_recent = true
  owners      = [&quot;amazon&quot;]

  filter {
    name   = &quot;name&quot;
    values = [&quot;amzn2-ami-kernel-5*&quot;]
  }
}</code></pre>
<p><strong>3. Ubuntu</strong></p>
<pre><code class="language-bash"># ubuntu ami latest
data &quot;aws_ami&quot; &quot;ubuntu_latest&quot; {
  most_recent = true
  owners      = [&quot;099720109477&quot;] # Canonical(owner account id)

  filter {
    name   = &quot;name&quot;
    values = [&quot;ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*&quot;]
    # values = [&quot;ubuntu/images/hvm-ssd/ubuntu-xenial-18.04-amd64-server-*&quot;]
    # values = [&quot;ubuntu/images/hvm-ssd/ubuntu-xenial-22.04-arm64-server-*&quot;]
  }
  filter {
    name   = &quot;virtualization-type&quot;
    values = [&quot;hvm&quot;]
  }
}</code></pre>
<h3 id="ec2-resouece">EC2 Resouece</h3>
<p>EC2의 인스턴스 타입을 결정할 때 보통 <strong>cpu 와 memory 스펙을 정하고 워크로드에 적합한 타입을 선정</strong>합니다.
저는 아래 명령어를 통해 인스턴스 타입을 결정하는 편입니다.</p>
<ul>
<li>참고 : <a href="https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/instance-discovery.html">Amazon EC2 인스턴스 유형 찾기</a></li>
</ul>
<pre><code class="language-bash">aws ec2 describe-instance-types --filters \
&quot;Name=current-generation,Values=true&quot; \
&quot;Name=memory-info.size-in-mib,Values=4096&quot; \
&quot;Name=vcpu-info.default-vcpus,Values=2&quot; \
--query &quot;InstanceTypes[*].[InstanceType]&quot; --output text | sort</code></pre>
<p><img src="https://velog.velcdn.com/images/imok-_/post/898c6f68-dd1b-4847-b522-95c4150f7a07/image.png" alt=""></p>
<ol>
<li><p><code>ec2.tf</code> 파일 구성
요구사항에 맞춰 미리 위에서 정의한 ami 중 <strong>최신 amazon linux2의 이미지</strong>를 선택했고, 인스턴스 타입, 볼륨 등을 설정했습니다.</p>
<pre><code class="language-bash">## EC2
# bastion
resource &quot;aws_instance&quot; &quot;bastion&quot; {
 ami                    = data.aws_ami.amazon_linux2_kernel_5.id
 instance_type          = &quot;t3.small&quot;
 key_name               = aws_key_pair.key_pair.key_name
 subnet_id              = element(aws_subnet.pub_sub.*.id, 0) # public-subnet-2a
 vpc_security_group_ids = [aws_security_group.bastion_sg.id]

 root_block_device {
   volume_type = &quot;gp3&quot;
   volume_size = &quot;10&quot;
 }

 tags = {
   Name = &quot;${var.tags}-bastion&quot;
 }
}

# dev-management
resource &quot;aws_instance&quot; &quot;dev_management_2a&quot; {
 ami                    = data.aws_ami.amazon_linux2_kernel_5.id
 instance_type          = &quot;r5.large&quot;
 key_name               = aws_key_pair.key_pair.key_name
 subnet_id              = element(aws_subnet.dev_pri_sub.*.id, 0) # dev-private-subnet-2a
 vpc_security_group_ids = [aws_security_group.dev_management_sg.id]

 root_block_device {
   volume_type = &quot;gp3&quot;
   volume_size = &quot;100&quot;
 }

 tags = {
   Name = &quot;${var.tags}-dev-management-2a&quot;
 }
}</code></pre>
</li>
</ol>
<h3 id="sg-resouece">SG Resouece</h3>
<ol>
<li><p><code>variables.tf</code> 파일 구성
SG에서 사용하기 위한 <strong>ingress rules</strong>을 미리 variables에 정의했습니다.
default를 비워둔 이유는 위에서 언급한 <strong><code>terraform.tfvars</code> 파일에 정의한 변수</strong>를 사용하기 위함입니다.</p>
<pre><code class="language-bash">variable &quot;bastion_ingress_rules&quot; {
 type        = list(map(string))
 default     = []
 description = &quot;bastion sg rule&quot;
}

variable &quot;service_ingress_rules&quot; {
 type        = list(map(string))
 default     = []
 description = &quot;service sg rule&quot;
}

variable &quot;management_ingress_rules&quot; {
 type        = list(map(string))
 default     = []
 description = &quot;management sg rule&quot;
}</code></pre>
</li>
<li><p><code>sg.tf</code> 파일 구성
aws_security_group 리소스의 내부에 <strong>ingress block</strong>을 생성하는 데 <strong>dynamic block</strong>을 활용했습니다.
<code>for_each</code>에서 block을 생성할 정보를 담은 for 표현식을 사용한 <strong>collection</strong> 을 전달받고, 이 collection 의 <strong>item</strong> 수만큼 block이 생성됩니다.</p>
<p>for 표현식 : <code>[for &lt;ITEM&gt; in &lt;LIST&gt; : &lt;OUTPUT&gt;]</code></p>
<p><code>content</code>는 실제로 정의하려는 block 안에 전달되는 값들을 명시하는 곳입니다. 따라서 원래 <strong>ingress block</strong>을 생성할 때 필요한 값을 넣어줍니다.</p>
<pre><code class="language-bash">## Security group
# bastion-sg
resource &quot;aws_security_group&quot; &quot;bastion_sg&quot; {
 # count = length(var.bastion_ip)
 name        = &quot;${var.tags}-bastion-sg&quot;
 description = &quot;${var.tags}-bastion-sg&quot;
 vpc_id      = aws_vpc.vpc.id

 # inbound rule
 dynamic &quot;ingress&quot; {
   for_each = [for s in var.bastion_ingress_rules : {
     from_port = s.from_port
     to_port   = s.to_port
     desc      = s.desc
     cidrs     = [s.cidr]
   }]
   content {
     from_port   = ingress.value.from_port
     to_port     = ingress.value.to_port
     cidr_blocks = ingress.value.cidrs
     protocol    = &quot;tcp&quot;
     description = ingress.value.desc
   }
 }

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

 tags = {
   Name = &quot;${var.tags}-bastion-sg&quot;
 }
}</code></pre>
</li>
</ol>
<h3 id="alb-resouece">ALB Resouece</h3>
<p>ALB 구성이 개인적으로 제일 힘들었습니다. 처음엔 ALB의 <strong>Listener rule</strong>의 조건을 코드로 모두 작성하려고 했습니다. 하지만 아래 캡처와 같이 <strong>생각해야 할 분기 조건들이 너무 많았고,</strong> 처음부터 코드를 간결히 작성하는 데에만 집중해 count와 list로 해결하려다 보니 <strong>의존성에 문제가 생겨 오류가 계속 발생</strong>했습니다. 결국 룰 분기 조건은 콘솔창에서 작성하는 게 더 빠를 것 같다는 판단을 내렸습니다.
<img src="https://velog.velcdn.com/images/imok-_/post/88d0c4df-1584-4cb6-a8c3-ca2e9d36ee82/image.png" alt=""></p>
<p>제 개인적인 생각으로는 ALB는 수고스럽지만, <strong>코드가 반복되더라도 하나하나 작성하는 게 더 좋지않았을까</strong>라는 생각을 합니다.
(아직 제가 좋은 방법이 있지만 찾지 못해서 그럴 수도..더 좋은 방법을 찾으면 개선하겠습니다. 🙇🏻‍♀️)</p>
<ol>
<li><p><code>variables.tf</code> 파일 구성
ALB에서 Target Group에 사용하기 위한 <strong>서비스 포트 및 certificate arn</strong>을 미리 variables에 정의했습니다.</p>
<pre><code class="language-bash">variable &quot;env&quot; {
 type        = list(any)
 default     = [&quot;dev&quot;, &quot;prd&quot;]
 description = &quot;Additional env tags&quot;
}

variable &quot;service_server_port&quot; {
 type    = list(any)
 default = [8085, 8086]
}

variable &quot;management_server_port&quot; {
 type    = list(any)
 default = [8080]
}

variable &quot;acm_certi&quot; {
type    = string
default = &quot;&quot;
}</code></pre>
</li>
<li><p><code>alb.tf</code> 파일 구성</p>
</li>
</ol>
<ul>
<li><p>alb는 개발 환경에 따라 구성돼야하기 때문에 variables에 정의한 환경 변수의 개수만큼 생성되도록 했습니다.</p>
<pre><code class="language-bash"># ALB 생성
resource &quot;aws_lb&quot; &quot;service_alb&quot; {
  count              = length(var.env)
  name               = &quot;${var.tags}-${var.env[count.index]}-alb-service&quot;
  load_balancer_type = &quot;application&quot;
  security_groups    = [aws_security_group.service_alb_sg.id]
  subnets            = [element(aws_subnet.pub_sub.*.id, 0), element(aws_subnet.pub_sub.*.id, 1)]

  tags = {
    Name = &quot;${var.tags}-${var.env[count.index]}-alb-service&quot;
  }
}</code></pre>
</li>
<li><p>alb의 target group을 생성하고, target인 인스턴스를 붙입니다.</p>
<pre><code class="language-bash">## ALB target group &amp; attachment
# dev service_tg
resource &quot;aws_lb_target_group&quot; &quot;dev_service_tg&quot; {
  count    = length(var.service_server_port)
  name     = &quot;${var.tags}-${var.env[0]}-service-tg-${var.service_server_port[count.index]}&quot;
  port     = var.service_server_port[count.index]
  protocol = &quot;HTTP&quot;
  vpc_id   = aws_vpc.vpc.id
}

resource &quot;aws_alb_target_group_attachment&quot; &quot;dev_service_tg_attach_2a&quot; {
  count            = length(aws_lb_target_group.dev_service_tg)
  target_group_arn = element(aws_lb_target_group.dev_service_tg.*.arn, count.index)
  target_id        = aws_instance.dev_service_2a.id
  port             = var.service_server_port[count.index]
}</code></pre>
</li>
<li><p>ALB listener를 http와 https로 나눠서 생성 후, https의 리스너 설정에서 <code>terraform.tfvars</code>에 정의한 <strong>AWS ACM ARN</strong>을 사용했습니다.</p>
<pre><code class="language-bash">## ALB listener Redirect Action
# service alb listener_http
resource &quot;aws_lb_listener&quot; &quot;service_alb_listener_http&quot; {
  count             = length(var.env)
  load_balancer_arn = element(aws_lb.service_alb.*.arn, count.index)
  port              = 80
  protocol          = &quot;HTTP&quot;

  default_action {
    type = &quot;redirect&quot;
    redirect {
      port        = &quot;443&quot;
      protocol    = &quot;HTTPS&quot;
      status_code = &quot;HTTP_301&quot;
    }
  }
}

# service alb listener_https
resource &quot;aws_lb_listener&quot; &quot;service_alb_listener_https&quot; {
  count             = length(var.env)
  load_balancer_arn = element(aws_lb.service_alb.*.arn, count.index)
  port              = 443
  protocol          = &quot;HTTPS&quot;
  ssl_policy        = &quot;ELBSecurityPolicy-2016-08&quot;
  certificate_arn   = var.acm_certi

  default_action {
    type = &quot;fixed-response&quot;

    fixed_response {
      content_type = &quot;text/plain&quot;
      message_body = &quot;Fixed response content&quot;
      status_code  = &quot;503&quot;
    }
  }
}</code></pre>
</li>
</ul>
<hr>
<h2 id="terraform-실행">Terraform 실행</h2>
<h3 id="terraform-plan">terraform plan</h3>
<p>앞에 작성한 리소스들이 실제 AWS에서 생성할 수 있는지 <code>terraform plan</code> 명령어를 통해 확인합니다.
plan 명령어를 사용하면 현재 정의되어있는 리소스들을 실제 프로바이더에 적용했을 때, <strong>테라폼이 어떤 작업을 수행할지 계획</strong>을 보여줍니다.
개인적으로 apply를 실행하기 전에 미리 내가 작성한 코드에 문제가 없는지 알 수 있어서 꼭 필요한 명령어라고 생각했습니다. (AWS 리소스 함부로 생성했다간 돌이킬 수 없는 일이 생길 수도 있기 때문이죠 😢)</p>
<p>아래 명령어 수행 결과를 보면, 총 71개의 리소스가 추가될 예정임을 알 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/9ae45cc3-890f-41d5-a3b5-eff612cee374/image.png" alt=""></p>
<h3 id="terraform-apply">terraform apply</h3>
<p>plan을 통해 확인한 내용을, 실제로 AWS 프로바이더에 <code>terraform apply</code> 명령어를 통해 적용합니다.
&quot;정말 적용해도 괜찮으시겠어요?&quot; 라는 마치 이 명령어에 책임을 질 수 있겠냐는 질문에 <strong>yes</strong>를 치고서야 최종적으로 리소스들을 생성할 수 있습니다. 
(물론, 생략하고 바로 생성할 수 있는 <code>terraform apply -auto-approve</code> 라는 명령어가 있습니다.)
<img src="https://velog.velcdn.com/images/imok-_/post/c7e7fb91-8cb4-4fb3-bbdf-0dfca57bf1d5/image.png" alt=""></p>
<p>아래 캡처 화면과 같이 <code>Creating..</code> 이라는 문구가 계속 올라가면서 리소스들이 생성되는 과정을 확인할 수 있습니다. 마지막으로 <code>Apply complete!</code>라는 문구가 나오면, 이제 <strong>AWS에 콘솔에서 리소스들이 정상적으로 생성되었음을 확인</strong>할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/imok-_/post/f111f12c-922b-4a30-b25a-c7a980fe372f/image.png" alt=""></p>
<ol>
<li><p>VPC
<img src="https://velog.velcdn.com/images/imok-_/post/a6c934a5-deaf-43d1-a0b9-5a7d187b9421/image.png" alt=""></p>
</li>
<li><p>EC2
<img src="https://velog.velcdn.com/images/imok-_/post/8546cda1-5579-4f0a-b4a5-dbf1ce0971a7/image.png" alt=""></p>
</li>
</ol>
<ol start="3">
<li>SG
<img src="https://velog.velcdn.com/images/imok-_/post/a157afdf-92c2-4f48-91de-c5bae8581642/image.png" alt=""></li>
</ol>
<ul>
<li>가장 반복 작업이라고 생각하는 inboud rule 한 번에 생성!🥳
<img src="https://velog.velcdn.com/images/imok-_/post/c3cb9241-e255-42dd-9991-40e0feeadcda/image.png" alt=""></li>
</ul>
<ol start="4">
<li>ALB
<img src="https://velog.velcdn.com/images/imok-_/post/68ec77ec-fd43-45b1-b25e-c70f6fd49d82/image.png" alt=""></li>
</ol>
<blockquote>
<p>👶🏻 OK는 3분 만에 구축 요구 사항에 맞는 인프라 구성을 완료했고, <del>룰루랄라 놀 수</del> 새로운 일을 시작할 수 있었습니다. 😂</p>
</blockquote>
<p>완성 코드는 제 깃허브에 올려놨습니다 참고 부탁드립니다 :)
<a href="https://github.com/euneun316/terraform-study/tree/main/Default_Infra">https://github.com/euneun316/terraform-study/tree/main/Default_Infra</a></p>
<hr>
<h1 id="마치며">마치며</h1>
<p>처음 테라폼으로 인프라 구축을 시작했을 때 받았던 느낌은 <strong>&#39;와 이걸 왜 지금 알았지..?&#39;</strong> 였습니다.
명령어 몇 개로 쉽게 인프라가 뚝딱 만들어졌기 때문이죠. 하지만 계속 스터디하면서 느끼게 된 점은 쓰면 쓸수록 어렵고, 정말 <strong>&#39;잘&#39;</strong> 써야겠다는 생각입니다. 아무것도 없는 상태에서 기본 인프라 구축을 하는 데 사용하기에는 정말 좋습니다. </p>
<p>하지만 저는 위의 기본 인프라를 생성하는데 테라폼을 사용하면서도 <strong>많은 에러를 겪었습니다.</strong> subnet에서 리스트로 생성하다 꼬여서 6개가 만들어져야 하는데, 12개씩 생성되기도 하고, 만들어 놓은 ALB가 삭제가 안 되기도 하고, 잘 만들어진 EC2였는데, 글자 몇 개 수정했더니 삭제되고 다시 생성되기도 하고.. 그런데 이 리소스가 운영 중이던 서비스였다면..? 생각만 해도 아찔합니다.</p>
<p>따라서 앞으로는 <em>** <code>.tfstate</code> (상태 저장 파일)을 활용하는 부분, 테라폼을 사용해 협업하는 부분**</em> 에 중점을 두고 스터디할 예정입니다. 
테라폼 고수가 되어 다시 이 글을 고칠 그날까지... 🏃🏻‍♀️</p>
<hr>
<h1 id="사담">사담..</h1>
<p>이때의 느낌을 잊지 않기 위한 기록.</p>
<p>처음 <strong>CloudNet@ Terraform Study 101</strong> 모집 공고를 보고 스터디 신청을 한 후, 운영진 가시다님과의 전화 인터뷰에서 &quot;스터디 탈락 인원이 꽤 많은데, 스터디 완주 가능하실까요?&quot; 라는 질문을 받았습니다.
&quot;물론이죠!!&quot; 라고 당차게 외친지 불과 첫 주 만에 자신감이 와르르 무너졌습니다.😢 </p>
<p>분명 101 스터디인데.. <strong>심화 과정이 아닌가 싶을 만큼 스터디 내용이 방대했고, 스터디원분들은 이미 고수들뿐인 것 같았습니다.</strong> 다들 이미 경지에 오르신 분들인데도 이렇게 열심히 노력한다는 사실에 엄청난 자극을 얻었고, 따라가려면 다른 분들이 걸을 때 나는 뛰어야겠다는 라는 생각으로 주말 밤을 새워 스터디 과제를 매주 제출했습니다. 그랬더니 조금 이해가 되는 것 같아서 점점 재밌어졌습니다. 😆</p>
<p>또 매주 <strong>테라폼 장인님들이 테라폼을 사용하면서 얻은 경험을 공유</strong>해주시는데, 모두 알차고 박수가 절로 나왔습니다. 그래서 꼭 스터디 완주를 해야겠다 다시 다짐했죠.</p>
<p>그런데..! (두둥 효과음) <code>중간과제 : 스터디 내용 혹은 테라폼 내용을 정리하여 글 작성 후 외부 공개!</code> 
중간과제 내용을 듣고 전 엄청난 부담감에 사로잡혔습니다. 다른 분들께 도움이 되는 내용을 과연 내가 작성할 수 있을지 자신이 없었습니다. 😢 </p>
<p>하지만 그렇다고 포기할 순 없었기에, <strong>할 수 있는 범위내에서 최선을 다하자고 결론 내리고</strong> 이 글을 작성하게 되었습니다.
저와 같이 <strong>처음 Terraform 스터디 중</strong>이신 분들이나, <strong>기본 인프라 구성을 코드로 자동화</strong>하고 싶어 하시는 분들에게 꼭 도움이 됐으면 좋겠습니다. 제발(?) 🙏</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS EC2 Docker GPU 환경 구성]]></title>
            <link>https://velog.io/@imok-_/AWS-EC2-Docker-GPU-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@imok-_/AWS-EC2-Docker-GPU-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Sun, 24 Apr 2022 09:03:21 GMT</pubDate>
            <description><![CDATA[<h1 id="aws-ec2에서-docker-gpu-환경-구동-시-발생-error-해결과정">AWS EC2에서 Docker GPU 환경 구동 시 발생 ERROR 해결과정</h1>
<p>*<em>AWS EC2 안에서 Docker로 딥러닝 GPU 환경 구성 과정에서 발생한 ERROR 해결 과정을 기록합니다. *</em></p>
<blockquote>
<p>인스턴스 유형 : g4dn.4xlarge</p>
</blockquote>
<h2 id="1-nvidia-확인">1. NVIDIA 확인</h2>
<pre><code class="language-bash">$lspci | grep -i nvidia
00:1e.0 3D controller: NVIDIA Corporation Device 1eb8 (rev a1)</code></pre>
<h2 id="2-docker-run---gpus-error">2. docker run --gpus ERROR</h2>
<blockquote>
<p>ubuntu: 18.04
cuda: 11.3.1
torch: 1.7.1</p>
</blockquote>
<h3 id="21-docker-run-error-발생">2.1. docker run error 발생</h3>
<pre><code class="language-bash">$docker run -it --name pytorch --gpus &#39;&quot;device=0&quot;&#39; --network airflownet -v $PWD/notebooks:/notebooks -p 8888:8888 cuda11.3:pytorch1.7.1

docker: Error response from daemon: could not select device driver &quot;&quot; with capabilities: [[gpu]].
ERRO[0000] error waiting for container: context canceled</code></pre>
<h3 id="21-해결-과정--nvidia-container-toolkit을-설치">2.1. 해결 과정 : nvidia-container-toolkit을 설치</h3>
<pre><code class="language-bash"># nvidia-container-toolkit 설치
$distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
   &amp;&amp; curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
   &amp;&amp; curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

$sudo apt-get update &amp;&amp; sudo apt-get install -y nvidia-container-toolkit

# docker service 재시작
$sudo systemctl restart docker</code></pre>
<h3 id="22-다시-docker-run-error-발생">2.2. 다시 docker run error 발생</h3>
<pre><code class="language-bash">$docker run -it --name pytorch --gpus &#39;&quot;device=0&quot;&#39; --network airflownet -v $PWD/notebooks:/notebooks -p 8888:8888 cuda11.3:pytorch1.7.1

docker: Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: Running hook #0:: error running hook: exit status 1, stdout: , stderr: nvidia-container-cli: initialization error: load library failed: libnvidia-ml.so.1: cannot open shared object file: no such file or directory: unknown.
ERRO[0000] error waiting for container: context canceled</code></pre>
<h3 id="22-해결-과정-1--nvidia-utils-설치">2.2. 해결 과정 1 : nvidia-utils 설치</h3>
<ul>
<li><code>nvidia-smi</code> 명령어 확인</li>
</ul>
<pre><code class="language-bash">$nvidia-smi

Command &#39;nvidia-smi&#39; not found, but can be installed with:

sudo apt install nvidia-340
sudo apt install nvidia-utils-390

$sudo apt install nvidia-utils-390</code></pre>
<ul>
<li>error 발생</li>
</ul>
<pre><code class="language-bash">$nvidia-smi

NVIDIA-SMI has failed because it couldn&#39;t communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running.</code></pre>
<h3 id="22-해결과정-2--nvidia-driver-설치">2.2 해결과정 2 : nvidia-driver 설치</h3>
<pre><code class="language-bash">$sudo apt install -y nvidia-driver-470
# 재시작해줘야 함
$sudo reboot now</code></pre>
<h3 id="23-해결-완료">2.3 해결 완료</h3>
<pre><code class="language-bash">$nvidia-smi
Sun Apr 24 08:09:45 2022
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.103.01   Driver Version: 470.103.01   CUDA Version: 11.4     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla T4            Off  | 00000000:00:1E.0 Off |                    0 |
| N/A   47C    P0    28W /  70W |      0MiB / 15109MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+</code></pre>
<h2 id="3-cuda-버전-확인">3. CUDA 버전 확인</h2>
<pre><code class="language-bash">$nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2021 NVIDIA Corporation
Built on Mon_May__3_19:15:13_PDT_2021
Cuda compilation tools, release 11.3, V11.3.109
Build cuda_11.3.r11.3/compiler.29920130_0</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[M1 catboost 설치 오류 해결]]></title>
            <link>https://velog.io/@imok-_/install-catboost-on-Apple-Silicon-M1</link>
            <guid>https://velog.io/@imok-_/install-catboost-on-Apple-Silicon-M1</guid>
            <pubDate>Tue, 29 Mar 2022 02:01:04 GMT</pubDate>
            <description><![CDATA[<p>build <code>catboost 1.0.5</code> from source with miniconda using the following steps</p>
<blockquote>
<p>conda version : conda 4.11.0 (homebrew miniconda)
OS: Apple Silicon M1
python version : 3.9.10
catboost version : 1.0.5</p>
</blockquote>
<h3 id="install-error">install error</h3>
<pre><code class="language-bash">$pip install catboost
ERROR: Could not find a version that satisfies the requirement catboost (from versions: none)
ERROR: No matching distribution found for catboost</code></pre>
<p><img src="https://images.velog.io/images/imok-_/post/4f8aff7e-808e-4424-b617-29fdeae7beeb/image.png" alt=""></p>
<h3 id="install-catboost">install catboost</h3>
<pre><code class="language-bash">$git clone https://github.com/catboost/catboost.git

$cd catboost/catboost/python-package/catboost

# DPYTHON_CONFIG에 본인 python3 config 경로 넣기
$../../../ya make -r -DUSE_ARCADIA_PYTHON=no -DOS_SDK=local \
-DPYTHON_CONFIG=/opt/homebrew/Caskroom/miniconda/base/envs/imok/bin/python3-config \
--target-platform=CLANG12-DARWIN-ARM64

# DPYTHON_BIN에 본인 python3 bin 경로 넣기
$python3 ../mk_wheel.py --target-platform=CLANG12-DARWIN-ARM64 \
-DPYTHON_BIN=/opt/homebrew/Caskroom/miniconda/base/envs/imok/bin/python3 \
-DPYTHON_CONFIG=/opt/homebrew/Caskroom/miniconda/base/envs/imok/bin/python3-config \
-DHAVE_CUDA=no --build-widget=no

$cd ..

$pip install catboost-1.0.5-cp39-none-macosx_11_0_arm64.macosx_12_0_arm64.whl</code></pre>
<h3 id="catboost-version-check">catboost version check</h3>
<pre><code class="language-python">import catboost
catboost.__version__</code></pre>
<hr>
<h3 id="reference">Reference</h3>
<p><a href="https://github.com/catboost/catboost/issues/1526">https://github.com/catboost/catboost/issues/1526</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[M1 Docker arm64 아키텍처]]></title>
            <link>https://velog.io/@imok-_/M1-Docker</link>
            <guid>https://velog.io/@imok-_/M1-Docker</guid>
            <pubDate>Wed, 23 Mar 2022 06:21:35 GMT</pubDate>
            <description><![CDATA[<h2 id="linuxamd64-아키텍처-vs-linuxarm64v8-아키텍처">linux/amd64 아키텍처 vs linux/arm64/v8 아키텍처</h2>
<h3 id="1intel-cpu-기반의-macos나-linux">1.Intel CPU 기반의 macOS나 Linux</h3>
<blockquote>
<ul>
<li>linux/amd64 아키텍처의 이미지를 사용</li>
</ul>
</blockquote>
<h3 id="2-apple-silicon-m1">2. Apple Silicon M1</h3>
<blockquote>
<ul>
<li><p>linux/arm64/v8 아키텍처의 이미지를 우선적으로 사용
<code>--platform linux/arm64/v8</code>이 기본으로 설정</p>
</li>
<li><p>Rosetta 2를 통해 Intel CPU에서 실행할 수 있도록 linux/amb64로 빌드된 애플리케이션도 실행할 수 있다.
<code>--platform linux/amd64</code> 옵션을 사용</p>
</li>
</ul>
</blockquote>
<hr>
<p><em><code>linux/arm64/v8</code> 없는 경우 <code>linux/amd64</code> 아키텍처 이미지를 대신 사용할 수 있지만, 아직 여러가지 문제가 있는 것을 알려져있으며, Docker 사에서는 가능하면 <code>arm64</code> 이미지를 사용할 것을 권장함</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[M1 docker run & docker start, stop, rm]]></title>
            <link>https://velog.io/@imok-_/docker-run</link>
            <guid>https://velog.io/@imok-_/docker-run</guid>
            <pubDate>Wed, 23 Mar 2022 03:10:48 GMT</pubDate>
            <description><![CDATA[<h2 id="docker-run">docker run</h2>
<ul>
<li>Run a command in a new container</li>
<li><a href="https://docs.docker.com/engine/reference/commandline/run/">docker run 공식문서</a></li>
</ul>
<h3 id="usage">Usage</h3>
<blockquote>
<p><code>docker run [OPTIONS] IMAGE [COMMAND] [ARG...]</code></p>
</blockquote>
<h3 id="options">Options</h3>
<table>
<thead>
<tr>
<th align="center">Name, shorthand</th>
<th align="center">Description</th>
</tr>
</thead>
<tbody><tr>
<td align="center">--attach , -a</td>
<td align="center">Attach to STDIN, STDOUT or STDERR</td>
</tr>
<tr>
<td align="center">--detach , -d</td>
<td align="center">Run container in background and print container ID</td>
</tr>
<tr>
<td align="center">--env , -e</td>
<td align="center">Set environment variables</td>
</tr>
<tr>
<td align="center">--publish , -p</td>
<td align="center">Publish a container&#39;s port(s) to the host</td>
</tr>
<tr>
<td align="center">--volume , -v</td>
<td align="center">Bind mount a volume [ -v host dir : container volume dir ]</td>
</tr>
<tr>
<td align="center">--privileged</td>
<td align="center">Give extended privileges to this container</td>
</tr>
<tr>
<td align="center">--name</td>
<td align="center">Assign a name to the container</td>
</tr>
<tr>
<td align="center">--network</td>
<td align="center">Connect a container to a network</td>
</tr>
<tr>
<td align="center">--platform</td>
<td align="center">Set platform if server is multi-platform capable</td>
</tr>
<tr>
<td align="center">--tty , -t</td>
<td align="center">Allocate a pseudo-TTY</td>
</tr>
<tr>
<td align="center">--interactive , -i</td>
<td align="center">Keep STDIN open even if not attached</td>
</tr>
</tbody></table>
<h3 id="examples">Examples</h3>
<pre><code class="language-bash"># docker 로 zeppelin 이미지 실행 
# 구동 directory 내에 logs 폴더와 notebook 폴더를 생성하고 docker path 에 마운트
# zeppelin:0.8.2
$docker run -p 4040:4040 -p 8080:8080 --platform linux/amd64 --name zeppelin --privileged=true -v $PWD/logs:/logs -v $PWD/notebook:/notebook -e ZEPPELIN_NOTEBOOK_DIR=&#39;/notebook&#39; -e ZEPPELIN_LOG_DIR=&#39;/logs&#39; -d apache/zeppelin:0.8.2 /zeppelin/bin/zeppelin.sh

# zeppelin:0.10.0
$docker run -p 4040:4040 -p 8080:8080 --platform linux/amd64 --name zeppelin --privileged=true -v $PWD/logs:/logs -v $PWD/notebook:/notebook -e ZEPPELIN_NOTEBOOK_DIR=&#39;/notebook&#39; -e ZEPPELIN_LOG_DIR=&#39;/logs&#39; -d apache/zeppelin:0.10.0 /opt/zeppelin/bin/zeppelin.sh</code></pre>
<hr>
<h2 id="docker-stop">docker stop</h2>
<ul>
<li>Stop one or more running containers</li>
<li><a href="https://docs.docker.com/engine/reference/commandline/stop/">docker stop 공식문서</a></li>
</ul>
<h3 id="usage-1">Usage</h3>
<blockquote>
<p><code>docker stop [OPTIONS] CONTAINER [CONTAINER...]</code></p>
</blockquote>
<h3 id="examples-1">Examples</h3>
<pre><code class="language-bash">$docker stop my_container</code></pre>
<hr>
<h2 id="docker-start">docker start</h2>
<ul>
<li>Start one or more stopped containers</li>
<li><a href="https://docs.docker.com/engine/reference/commandline/start/">docker start 공식문서</a></li>
</ul>
<h3 id="usage-2">Usage</h3>
<blockquote>
<p><code>docker start [OPTIONS] CONTAINER [CONTAINER...]</code></p>
</blockquote>
<h3 id="examples-2">Examples</h3>
<pre><code class="language-bash">$docker start my_container</code></pre>
<hr>
<h2 id="docker-rm">docker rm</h2>
<ul>
<li>Remove one or more containers</li>
<li><a href="https://docs.docker.com/engine/reference/commandline/rm/">docker rm 공식문서</a></li>
</ul>
<h3 id="usage-3">Usage</h3>
<blockquote>
<p><code>docker rm [OPTIONS] CONTAINER [CONTAINER...]</code></p>
</blockquote>
<h3 id="examples-3">Examples</h3>
<pre><code class="language-bash">$docker rm my_container</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS EC2 설치 및 배포]]></title>
            <link>https://velog.io/@imok-_/AWS-EC2-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@imok-_/AWS-EC2-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Mon, 14 Mar 2022 11:41:01 GMT</pubDate>
            <description><![CDATA[<h1 id="1-aws-접속-ec2-생성">1. AWS 접속 EC2 생성</h1>
<ol>
<li><p>AWS 접속 <a href="https://aws.amazon.com/ko/">AWS</a></p>
</li>
<li><p><code>컴퓨팅 &gt;EC2 &gt; EC2 대시보드 &gt; 인스턴스 시작</code></p>
</li>
<li><p>Amazon Machine Image(AMI) 선택 </p>
</li>
</ol>
<ul>
<li>Ubuntu Server 18.04 LTS (HVM), SSD Volume Type</li>
</ul>
<ol start="4">
<li>인스턴스 유형 선택</li>
</ol>
<p><img src="https://images.velog.io/images/imok-_/post/fb1cae8a-7c6d-4154-9435-69170c03fcf2/image.png" alt=""></p>
<ol start="5">
<li>인스턴스 시작 검토</li>
</ol>
<ul>
<li>새 키 페어 생성</li>
</ul>
<p><img src="https://images.velog.io/images/imok-_/post/8fd69220-24de-4cfe-bd2c-b9ec57772d33/image.png" alt=""></p>
<ol start="6">
<li>EC2 생성 완료</li>
</ol>
<p><img src="https://images.velog.io/images/imok-_/post/5fd264a2-0aa8-4a38-96e4-0c5a1274eff0/image.png" alt=""></p>
<ol start="7">
<li>보안 규칙 설정</li>
</ol>
<ul>
<li>인스턴스 ID 클릭 &gt; 보안 &gt; 보안 그룹 클릭 </li>
</ul>
<p><img src="https://images.velog.io/images/imok-_/post/a3dcf066-b16f-46be-be0d-a65445b5e6d0/image.png" alt=""></p>
<ul>
<li>인바운드 규칙 편집 클릭
<img src="https://images.velog.io/images/imok-_/post/d3ff1548-fdfc-4e6e-a93a-a6c5a77bcf8c/image.png" alt=""></li>
</ul>
<hr>
<h1 id="2-ssh-로-ec2-인스턴스에-접속">2. ssh 로 EC2 인스턴스에 접속</h1>
<ol>
<li><code>ssh 연결할 인스턴스 체크 &gt; 연결</code> 버튼을 클릭한다.</li>
</ol>
<p><img src="https://images.velog.io/images/imok-_/post/e24798cc-5895-4736-aa5a-86bd9ec2aa89/image.png" alt=""></p>
<ul>
<li>인스턴스 엑세스 방법  확인</li>
<li>ssh 명령어 복사 후 사용</li>
</ul>
<pre><code class="language-bash">$chmod 400 mykey.pem 
# key 프라이빗 키 파일 설치 경로로 이동 후 실행
$ssh -i &quot;mykey.pem&quot; ubuntu@[퍼블릭 IPv4 주소]</code></pre>
<ol start="2">
<li>정상 접속 확인</li>
</ol>
<p><img src="https://images.velog.io/images/imok-_/post/ffeeb246-d648-49e2-8446-d43e321f5934/image.png" alt=""></p>
<ol start="3">
<li>퍼블릭 IPv4 주소 확인 
<img src="https://images.velog.io/images/imok-_/post/d915886e-8aeb-44fd-a25c-931676500074/image.png" alt=""></li>
</ol>
<pre><code class="language-bash"># 디렉토리 파일 복사 (실행할 py 파일)
$scp -i &quot;mykey.pem&quot; -r [복사할 py 파일] ubuntu@[퍼블릭 IPv4 주소]:/home/ubuntu</code></pre>
<pre><code class="language-bash"># 필요 패키지 설치
$python3 --version
$pip3 list
$sudo apt-get update
$sudo apt-get install python3-pip
$pip3 install flask
$pip3 install pymysql
$pip3 show flask
$pip3 show pymysql</code></pre>
]]></description>
        </item>
    </channel>
</rss>