<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>chori.log</title>
        <link>https://velog.io/</link>
        <description>전부인 것처럼, 전부가 아닌 것처럼</description>
        <lastBuildDate>Sun, 16 Nov 2025 13:07:37 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>chori.log</title>
            <url>https://velog.velcdn.com/images/dev_chori/profile/1c8f6ca5-8f79-4c23-a701-567f93efb523/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. chori.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_chori" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[쿠버네티스 Secret]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Secret</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Secret</guid>
            <pubDate>Sun, 16 Nov 2025 13:07:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<ul>
<li>ConfigMap과 유사하지만 보안 정보를 관리하기 위해 Secret을 별도로 제공</li>
<li>데이터가 base64로 저장된다는 것이 ConfigMap과의 차이점</li>
<li>주의사항으로 Secret이 보안 정보를 저장하기 위해 만들어졌지만 실제로 데이터가 암호화되지는 않음, etcd에 접근이 가능하다면 누구나 저장된 Secret을 확인할 수 있기 때문에 별도의 암호화 솔루션을 쓰는 것이 좋음</li>
</ul>
<h2 id="secret-만들기">Secret 만들기</h2>
<ul>
<li>사용자명이 작성된 텍스트 파일 생성</li>
</ul>
<pre><code class="language-plain">admin</code></pre>
<ul>
<li>비밀번호가 작성된 텍스트 파일 생성</li>
</ul>
<pre><code class="language-plain">1q2w3e4r</code></pre>
<ul>
<li>Secret을 만들고 확인</li>
</ul>
<pre><code class="language-shell"># Secret 생성
kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt

# Secret 상세 조회
kubectl describe secret/db-user-pass

# -o yaml로 상세 조회
kubectl describe secret/db-user-pass -o yaml

# 저장된 데이터를 base64 decode
echo &#39;MXEydzNlNHIK&#39; | base64 --decode</code></pre>
<h2 id="secret을-환경변수로-사용">Secret을 환경변수로 사용</h2>
<ul>
<li>설정한 Secret을 환경변수로 연결</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: alpine-env
spec:
  containers:
    - name: alpine
      image: alpine
      command: [&quot;sleep&quot;]
      args: [&quot;10000&quot;]
      env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-user-pass
              key: username.txt
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-user-pass
              key: password.txt</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 ConfigMap]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-ConfigMap</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-ConfigMap</guid>
            <pubDate>Sun, 16 Nov 2025 12:49:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<ul>
<li>컨테이너에서 설정 파일을 관리하는 방법은 이미지를 빌드할 때 복사하거나 컨테이너를 실행할 때 외부 파일을 연결하는 방법이 있음</li>
<li>쿠버네티스는 ConfigMap으로 설정을 관리</li>
</ul>
<h2 id="configmap-만들기">ConfigMap 만들기</h2>
<h3 id="파일을-configmap으로-만들기">파일을 ConfigMap으로 만들기</h3>
<h4 id="설정-파일">설정 파일</h4>
<pre><code class="language-yaml">global:
  scrape_interval: 15s

scrape_configs:
  - job_name: prometheus
    metrics_path: /prometheus/metrics
    static_configs:
      - targets:
        - localhost: 9090</code></pre>
<ul>
<li><code>--from-file</code> 옵션을 사용하여 파일을 설정으로 만듦</li>
</ul>
<pre><code class="language-shell"># ConfigMap 생성 configmap -&gt; cm
kubectl create cm my-config --from-file=config-file.yaml

# ConfigMap 조회
kubectl get cm

# ConfigMap 내용 상세 조회
kubectl describe cm/my-config</code></pre>
<ul>
<li>생성한 ConfigMap을 <code>/etc/config</code> 디렉터리에 연결</li>
</ul>
<pre><code class="language-shell">apiVersion: v1
kind: Pod
metadata:
  name: alpine
spec:
  containers:
    - name: alpine
      image: alpine
      command: [&quot;sleep&quot;]
      args: [&quot;10000&quot;]
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: my-config</code></pre>
<h4 id="env-파일">env 파일</h4>
<ul>
<li>env 형식을 사용해서 YAML 파일 생성</li>
</ul>
<pre><code class="language-plain">hello=world
haha=hoho</code></pre>
<ul>
<li><code>--from-env-file</code> 옵션을 사용하여 파일을 설정으로 만듦</li>
</ul>
<pre><code class="language-shell"># env 형식으로 생성
kubectl create cm env-config --from-env-file=config-env.yaml

# env-config 조회
kubectl describe cm/env-config</code></pre>
<h3 id="yaml-선언하기">YAML 선언하기</h3>
<ul>
<li>ConfigMap을 YAML 파일로 정의</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
data:
  hello: world
  kuber: netes
  multiline: |-
    first
    second
    thrid</code></pre>
<pre><code class="language-shell"># configmap 생성
kubectl apply -f config-map.yaml</code></pre>
<h2 id="configmap을-환경변수로-사용">ConfigMap을 환경변수로 사용</h2>
<ul>
<li>ConfigMap을 Volume이 아닌 환경변수로 설정</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: alpine-env
spec:
  containers:
    - name: alpine
      image: alpine
      command: [&quot;sleep&quot;]
      args: [&quot;10000&quot;]
      env:
        - name: hello
          valueFrom:
            configMapKeyRef:
              name: my-config
              key: hello</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 Volume (local)]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Volume-local-a75tqjuj</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Volume-local-a75tqjuj</guid>
            <pubDate>Sun, 16 Nov 2025 12:15:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<ul>
<li>Pod를 제거하면 컨테이너 내부에 저장했던 데이터도 모두 사라짐</li>
<li>데이터가 유실되지 않으려면 별도의 저장소에 데이터를 저장하고 컨테이너를 새로 만들 때 이전 데이터를 가져와야 함</li>
<li>쿠버네티스는 Volume을 이용하여 컨테이너의 디렉터리를 외부 저장소와 연결하고 다양한 플러그인을 지원하여 흔히 사용하는 대부분의 스토리지를 사용할 수 있음</li>
</ul>
<h2 id="empty-dir">empty-dir</h2>
<ul>
<li>Pod 안에 속한 컨테이너 간 디렉터리를 공유하는 방법</li>
<li>보통 사이드카라는 패턴에서 사용, 예를 들어 특정 컨테이너에서 생성되는 로그 파일을 별도의 컨테이너(사이드카)가 수집할 수 있음</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: sidecar
spec:
  containers:
    - name: app
      image: busybox
      args:
        - /bin/sh
        - -c
        - &gt;
          while true;
          do
            echo &quot;$(date)\n&quot; &gt;&gt; /var/log/example.log;
            sleep 1;
          done
        volumeMounts:
          - name: varlog
            mountPath: /var/log
      - name: sidecar
        image: busybox
        args: [/bin/sh, -c, &quot;tail -f /var/log/example.log&quot;]
        volumeMounts:
          - name: varlog
            mountPath: /var/log
  volumes:
    - name: varlog
      emptyDir: {}</code></pre>
<h2 id="hostpath">hostpath</h2>
<ul>
<li>호스트(Node) 디렉터리를 컨테이너 디렉터리에 연결하는 방법</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: host-log
spec:
  containers:
    - name: log
      image: busybox
      args: [&quot;/bin/sh&quot;, &quot;-c&quot;, &quot;sleep infinity&quot;]
      volumeMounts:
        - name: varlog
          mountPath: /host/var/log
  volumes:
    - name: varlog
      hostPath:
        path: /var/log</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 Ingress]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Ingress</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Ingress</guid>
            <pubDate>Sun, 16 Nov 2025 11:46:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<ul>
<li>Service는 서비스 하나당 하나의 포트만 오픈할 수 있어서 서비스가 늘어나면 관리가 어려워짐</li>
<li>Ingress는 포트를 하나만 오픈하고 도메인이나 경로에 따라서 서비스를 분기</li>
</ul>
<h2 id="ingress-만들기">Ingress 만들기</h2>
<ul>
<li>첫 번째 서비스</li>
</ul>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-v1
spec:
  rules:
    - host: v1.echo.192.168.64.5.sslip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: echo-v1
                port:
                  number: 3000

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-v1
spec:
  replicas: 3
  selector:
    matchLables:
      app: echo
      tier: app
      version: v1
  template:
    metadata:
      lables:
        app: echo
        tier: app
        version: v1
    spec:
      containers:
        - name: echo
          image: ghcr.io/subicura/echo:V1
          livenessProbe:
            httpGet:
              path: /
              port: 3000

---
apiVersion: v1
kind: Service
metadata:
  name: echo-v1
spec:
  ports:
    - port: 3000
      protocol: TCP
  selector:
    app: echo
    tier: app
    version: v1</code></pre>
<ul>
<li>두 번째 서비스</li>
</ul>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-v2
spec:
  rules:
    - host: v2.echo.192.168.64.5.sslip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: echo-v2
                port:
                  number: 3000

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-v2
spec:
  replicas: 3
  selector:
    matchLables:
      app: echo
      tier: app
      version: v2
  template:
    metadata:
      lables:
        app: echo
        tier: app
        version: v2
    spec:
      containers:
        - name: echo
          image: ghcr.io/subicura/echo:V2
          livenessProbe:
            httpGet:
              path: /
              port: 3000

---
apiVersion: v1
kind: Service
metadata:
  name: echo-v2
spec:
  ports:
    - port: 3000
      protocol: TCP
  selector:
    app: echo
    tier: app
    version: v2</code></pre>
<ul>
<li>IP 주소는 하나이지만 도메인에 따라 서로 다른 서비스에 접근</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 Service]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Service</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Service</guid>
            <pubDate>Sun, 16 Nov 2025 09:29:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<ul>
<li>Pod는 자체 IP 주소를 가지고 다른 Pod와 통신할 수 있지만 쉽게 사라지고 생성되는 특징 때문에 직접 통신하는 방법은 권장하지 않음</li>
<li>쿠버네티스는 Pod와 직접 통신하는 방법 대신에 별도의 고정된 IP 주소를 가진 Service를 만들고 이를 통해 Pod에 접근하는 방식을 사용</li>
<li>노출 범위에 따라 ClusterIP, NodePort, LoadBalancer 유형으로 나뉨</li>
</ul>
<h2 id="clusterip-만들기">ClusterIP 만들기</h2>
<ul>
<li>외부에서 Pod로 접속하는 것이 아니라 클러스터 내부에 있는 Pod가 또 다른 Pod에 접속하기 위해 만드는 Service</li>
<li>ClusterIP는 클러스터 내부에서 새로운 IP 주소를 할당하고 여러 개의 Pod를 바라보는 로드밸런서 기능을 제공</li>
<li>Service 이름을 내부 도메인 서버에 등록하여 Pod 간에 Service 이름으로 통신할 수 있음</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  selector:
    metchLables:
      app: counter
      tier: db
  template:
    metadata:
      labels:
        app: counter
        tier: db
    spec:
      containers:
        - name: redis
          image: redis
          ports:
            - containerPorts: 6379
              protocol: TCP

---
apiVersion: v1
kind: Service
metadata:
  name: redis
spec:
  ports:
    - port: 6379
      protocol: TCP
  selector:
    app: counter
    tier: db</code></pre>
<ul>
<li>같은 클러스터에서 생성된 Pod라면 <code>redis</code>라는 도메인으로 redis Pod에 접근할 수 있음</li>
<li>spec.ports.port: Service가 생성할 Port</li>
<li>spec.ports.targetPort: Service가 접근할 Pod의 Port (기본값: port와 동일)</li>
<li>spec.selector: Service가 접근할 Pod의 label 조건</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: counter
spec:
  selector:
    matchLables:
      app: counter
      tier: app
  template:
    metadata:
      labels:
        app: counter
        tier: app
    spec:
      containers:
        - name: counter
          image: ghcr.io/subicura/counter:latest
          env:
            - name: REDIS_HOST
              value: &quot;redis&quot;
            - name: REDIS_PORT
              value: &quot;6379&quot;</code></pre>
<ul>
<li>Redis에 접근하는 Counter 앱에서 <code>REDIS_HOST</code>의 값을 <code>redis</code>로 주었음</li>
</ul>
<h2 id="nodeport-만들기">NodePort 만들기</h2>
<ul>
<li>클러스터 외부에서 접근할 때 필요한 Service는 NodePort</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: counter-np
spec:
  type: NodePort
  ports:
    - port: 3000
      protocol: TCP
      nodePort: 31000
  selector:
    app: counter
    tier: app</code></pre>
<ul>
<li>spec.ports.nodePort: Node에 오픈할 Port (미지정 시 30000-32768 중에 자동 할당)</li>
<li>NodePort는 클러스터의 모든 Node에서 포트를 오픈, 여러 개의 Node가 있을 때 아무 Node로 접근해도 지정한 Pod로 접근하게 됨</li>
<li>NodePort는 ClusterIP의 기능을 기본적으로 포함</li>
</ul>
<h2 id="loadbalancer-만들기">LoadBalancer 만들기</h2>
<ul>
<li>NodePort의 단점은 Node가 사라졌을 때 자동으로 다른 Node를 통해 접근이 불가능하다는 점, 예를 들어 3개의 Node가 있다면 3개 중에 아무 Node로 접근해도 NodePort로 연결할 수 있지만 어떤 Node가 정상 작동하는지 알 수 없음</li>
<li>자동으로 정상 작동하는 Node에 접근하기 위해 모든 Node를 바라보는 LoadBalancer가 필요, LoadBalancer는 NodePort가 정상 작동하는지 계속 확인하며 만약 Node에 문제가 생기면 다른 Node로 요청을 보냄</li>
<li>브라우저는 NodePort에 직접 요청을 보내는 것이 아니라 LoadBalancer에 요청을 하고 LoadBalancer가 알아서 정상 작동하는 Node에 접근하면 NodePort의 단점을 없앨 수 있음</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: counter-lb
spec:
  type: LoadBalancer
  ports:
    - port: 30000
      targetPort: 3000
      protocol: TCP
  selector:
    app: counter
    tier: app</code></pre>
<ul>
<li>일반적인 경우에는 NodePort보다 LoadBalancer를 많이 사용</li>
<li>그러나 LoadBalancer는 하나의 서비스밖에 바라보지 못한다는 문제가 있음, 예를 들어 쿠버네티스 클러스터에 여러 개의 앱이 있다면 각각의 LoadBalancer가 필요, 서비스가 늘어날 때마다 LoadBalancer를 추가해야 함, 그래서 Ingress를 주로 사용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 Deployment]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Deployment</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Deployment</guid>
            <pubDate>Sun, 16 Nov 2025 08:28:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<ul>
<li>Deployment는 쿠버네티스에서 가장 널리 사용되는 오브젝트, ReplicaSet을 이용하여 Pod를 업데이트하고 이력을 관리하여 롤백하거나 특정 버전으로 돌아갈 수 있음</li>
</ul>
<h2 id="deployment-만들기">Deployment 만들기</h2>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-deploy
spec:
  replicas: 4
  selector:
    matchLabels:
      app: echo
      tier: app
  template:
    metadata:
      labels:
        app: echo
        tier: app
    spec:
      containers:
        - name: echo
          image: ghrc.io/subicura/echo:v1</code></pre>
<ul>
<li>기존 설정에서 이미지 태그만 변경하고 다시 적용</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-deploy
spec:
  replicas: 4
  selector:
    matchLabels:
      app: echo
      tier: app
  template:
    metadata:
      labels:
        app: echo
        tier: app
    spec:
      containers:
        - name: echo
          image: ghrc.io/subicura/echo:v2 # v1 -&gt; v2</code></pre>
<pre><code class="language-shell"># 새로운 이미지로 업데이트
kubectl apply -f echo-deployment-v2.yaml

# 리소스 확인
kubectl get po, rs, deploy</code></pre>
<ul>
<li>Deployment는 새로운 이미지로 업데이트하기 위해 ReplicaSet을 이용, 버전을 업데이트하면 새로운 ReplicaSet을 생성하고 해당 ReplicaSet이 새로운 버전의 Pod를 생성</li>
<li>새로운 ReplicaSet에서 Pod를 0개에서 1개로 조정하고 정상적으로 Pod가 동작하면 기존 ReplicaSet에서 Pod를 하나 줄임</li>
<li>최종적으로 새로운 ReplicaSet에서 원하는 개수의 Pod가 모두 정상적으로 동작하면 기존 ReplicaSet의 Pod는 0개로 조정되며 업데이트가 완료됨</li>
</ul>
<h2 id="버전-관리">버전 관리</h2>
<ul>
<li>Deployment는 변경된 상태를 기록</li>
</ul>
<pre><code class="language-shell"># 히스토리 확인
kubectl rollout history deploy/echo-deploy

# revision 1 히스토리 상세 확인
kubectl rollout history deploy/echo-deploy --revision=1

# 바로 전으로 롤백
kubectl rollout undo deploy/echo-deploy

# 특정 버전으로 롤백
kubectl rollout undo deploy/echo-deploy --to-revision=2</code></pre>
<h2 id="배포-전략-설정">배포 전략 설정</h2>
<ul>
<li>Deployment에는 다양한 배포 전략이 있음</li>
<li>롤링 업데이트 방식을 사용할 때 동시에 업데이트하는 Pod의 개수를 다음과 같이 변경할 수 있음</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-deploy
spec:
  replicas: 4
  selector:
    matchLabels:
      app: echo
      tier: app
  minReadySeconds: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 3
      maxUnavailable: 3
  template:
    metadata:
      labels:
        app: echo
        tier: app
    spec:
      containers:
        - name: echo
          image: ghrc.io/subicura/echo:v2 # v1 -&gt; v2</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 ReplicaSet]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-ReplicaSet</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-ReplicaSet</guid>
            <pubDate>Sun, 16 Nov 2025 08:00:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<ul>
<li>Pod는 단독으로 만들면 어떠한 문제가 생겼을 때 자동으로 복구되지 않음, 이러한 Pod를 정해진 수만큼 복제하고 관리하는 것이 ReplicaSet</li>
<li>ReplicaSet은 원하는 개수의 Pod를 유지하는 역할을 담당</li>
<li>label을 이용하여 Pod를 체크하기 때문에 겹치지 않게 주의해야 함</li>
<li>ReplicaSet이 단독으로 쓰이는 경우는 거의 없음, Deployment가 ReplicaSet을 이용</li>
</ul>
<h2 id="replicaset-만들기">ReplicaSet 만들기</h2>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: echo-rs
spec:
  replicas: 1
  selector:
    matchLables:
      app: echo
      tier: app
    template:
      metadata:
        labels:
          app: echo
          tier: app
      spec:
        containers:
          - name: echo
            image: ghcr.io/subicura/echo:v1</code></pre>
<ul>
<li>spec.replicas: 원하는 Pod의 개수</li>
<li>spec.selector: label 체크 조건</li>
<li>spec.template: 생성할 Pod의 명세</li>
</ul>
<h2 id="scale-out">Scale out</h2>
<ul>
<li>ReplicaSet을 이용하면 손쉽게 Pod를 여러 개로 복제할 수 있음</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: echo-rs
spec:
  replicas: 4 # 1 -&gt; 4
  selector:
    matchLables:
      app: echo
      tier: app
    template:
      metadata:
        labels:
          app: echo
          tier: app
      spec:
        containers:
          - name: echo
            image: ghcr.io/subicura/echo:v1</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 Pod]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Pod</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Pod</guid>
            <pubDate>Sun, 16 Nov 2025 07:39:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="개념">개념</h2>
<ul>
<li>Pod은 쿠버네티스에서 관리하는 가장 작은 배포 단위</li>
<li>도커는 컨테이너를 만들지만, 쿠버네티스는 컨테이너 대신 Pod를 만듦</li>
<li>Pod는 한 개 또는 여러 개의 컨테이너를 포함</li>
</ul>
<h2 id="pod-만들기">Pod 만들기</h2>
<h3 id="yaml로-설정-파일-작성">YAML로 설정 파일 작성</h3>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: echo
  labels:
    app: echo
spec:
  containers:
    - name: app
      image: ghcr.io/subicura/echo:v1</code></pre>
<pre><code class="language-shell"># Pod 생성
kubectl apply -f echo-pod.yaml

# Pod 목록 조회
kubectl get po

# Pod 로그 조회
kubectl logs echo
kubectl logs -f echo

# Pod 컨테이너 접속
kubectl exec -it echo -- sh

# Pod 제거
kubectl delete -f echo-pod.yaml</code></pre>
<h2 id="컨테이너-상태-모니터링">컨테이너 상태 모니터링</h2>
<ul>
<li>컨테이너 생성과 실제 서비스 준비에는 차이가 있음, 서버를 실행하면 바로 접속할 수 없고 짧게는 수 초에서 길게는 수 분의 초기화 시간이 필요한데 서비스에 접속이 가능할 때 서비스가 준비되었다고 할 수 있음</li>
<li>쿠버네티스는 컨테이너가 생성되고 서비스가 준비되었다는 것을 확인하는 옵션을 제공</li>
</ul>
<h3 id="첫-번째-방법-livenessprobe">첫 번째 방법: livenessProbe</h3>
<ul>
<li>컨테이너가 정상적으로 동작하는지 확인하고 비정상이라면 컨테이너를 재시작하여 문제를 해결</li>
<li>정상이라는 것은 여러 가지 방식으로 확인할 수 있는데 <code>http get</code> 요청을 보내서 확인할 수도 있음</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: echo-lp
  labels:
    app: echo
spec:
  containers:
    - name: app
      image: ghcr.io/subicura/echo:v1
      livenessProbe:
        httpGet:
          path: /not/exist
          port: 8080
        initialDelaySeconds: 5
        timeoutSeconds: 2   # Default 1
        periodSeconds: 5    # Default 10
        failureThreshold: 1 # Default 3</code></pre>
<h3 id="두-번째-방법-readinessprobe">두 번째 방법: readinessProbe</h3>
<ul>
<li>컨테이너가 준비되었는지 확인하고 비정상이라면 Pod으로 들오어는 요청을 제외시킴</li>
<li>livenessProbe와 달리 문제가 있어도 Pod을 재시작하지 않고 요청만 제외시킴</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: echo-rp
  labels:
    app: echo
spec:
  containers:
    - name: app
      image: ghcr.io/subicura/echo:v1
      readinessProbe:
        httpGet:
          path: /not/exist
          port: 8080
        initialDelaySeconds: 5
        timeoutSeconds: 2   # Default 1
        periodSeconds: 5    # Default 10
        failureThreshold: 1 # Default 3</code></pre>
<h3 id="세-번째-방법-livenessprobe--readinessprobe">세 번째 방법: livenessProbe + readinessProbe</h3>
<ul>
<li>일반적으로 livenessProbe와 readindessProbe를 같이 적용</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: echo-health
  labels:
    app: echo
spec:
  containers:
    - name: app
      image: ghcr.io/subicura/echo:v1
      livenessProbe:
        httpGet:
          path: /
          port: 3000
      readinessProbe:
        httpGet:
          path: /
          port: 3000</code></pre>
<h2 id="다중-컨테이너">다중 컨테이너</h2>
<ul>
<li>하나의 Pod에 속한 컨테이너는 서로 네트워크를 localhost로 공유하고 동일한 디렉터리를 공유할 수 있음</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: counter
  labels:
    app: counter
spec:
  containers:
    - name: app
      image: ghrc.io/subicura/counter:latest
      env:
        - name: REDIS_HOST
          value: &quot;localhost&quot;
    - name: db
      image: redis</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 기본 명령어]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EA%B8%B0%EB%B3%B8-%EB%AA%85%EB%A0%B9%EC%96%B4</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EA%B8%B0%EB%B3%B8-%EB%AA%85%EB%A0%B9%EC%96%B4</guid>
            <pubDate>Sun, 16 Nov 2025 04:34:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<p><code>kubectl</code>은 쿠버네티스 CLI 도구, 쿠버네티스 클러스터에 명령어를 전달하는 일반적인 방법</p>
<h2 id="apply">apply</h2>
<ul>
<li>원하는 상태를 적용, 보통 <code>-f</code> 옵션으로 파일과 함께 사용</li>
<li>원하는 리소스의 상태를 YAML로 작성하고 apply 명령어 사용</li>
<li>파일명뿐만 아니라 URL도 입력 가능</li>
</ul>
<pre><code class="language-shell">kubectl apply -f https://subicura.com/k8s/code/guide/index/wordpress-k8s.yml</code></pre>
<h2 id="get">get</h2>
<ul>
<li>쿠버네티스에 선언된 리소스를 확인하는 명령어</li>
<li><code>-o</code> 옵션으로 출력 형식을 변경할 수 있음</li>
<li><code>--show-labels</code> 옵션으로 레이블을 확인할 수 있음</li>
</ul>
<pre><code class="language-shell"># Pod 조회
kubectl get pod
kubectl get pods
kubectl get po

# Service 조회
kubectl get service
kubectl get svc

# 여러 유형 조회
kubectl get po, svc

# Pod, ReplicaSet, Deployment, Service, Job 조회
kubectl get all

# 추가 정보 조회
kubectl get po -o wide

# yaml 형식으로 출력
kubectl get po -o yaml

# JSON 형식으로 출력
kubectl get po -o json

# Lable을 같이 출력
kubectl get po --show-labels</code></pre>
<h2 id="describe">describe</h2>
<ul>
<li>리소스의 상태를 자세하게 보여줌</li>
<li>특정 리소스의 상태가 궁금하거나 생성이 실패한 이유를 확인할 때 주로 사용</li>
</ul>
<pre><code class="language-shell"># Pod 조회로 이름 검색
kubectl get po

# 조회한 이름으로 상세 확인
kubectl describe po/wordpress-5f59577d4d-Bt2dg</code></pre>
<h2 id="delete">delete</h2>
<ul>
<li>쿠버네티스에 선언된 리소스를 제거</li>
</ul>
<pre><code class="language-shell"># Pod 조회로 이름 검색
kubectl get po

#조회한 Pod 제거
kubectl delete po/wordpress-5f59577d4d-Bt2dg</code></pre>
<h2 id="logs">logs</h2>
<ul>
<li>컨테이너의 로그 확인</li>
<li>실시간 로그를 보려면 <code>-f</code> 옵션 사용</li>
<li>하나의 Pod에 여러 개의 컨테이너가 있는 경우 <code>-c</code> 옵션으로 컨테이너를 지정해야 함</li>
</ul>
<pre><code class="language-shell"># Pod 조회로 이름 검색
kubectl get po

# 조회한 Pod 로그 조회
kubectl logs wordpress-5f59577d4d-Bt2dg

# 실시간 로그 보기
kubectl logs -f wordpress-5f59577d4d-Bt2dg</code></pre>
<h2 id="exec">exec</h2>
<ul>
<li>컨테이너에 명령어 전달, 컨테이너에 접근할 때 주로 사용</li>
<li>셸로 접속하여 컨테이너 상태를 확인하는 경우 <code>-it</code> 옵션을 사용</li>
<li>여러 개의 컨테이너가 있는 경우 <code>-c</code> 옵션으로 컨테이너를 지정해야 함</li>
</ul>
<pre><code class="language-shell"># Pod 조회로 이름 검색
kubectl get po

# 조회한 Pod의 컨테이너에 접속
kubectl exec -it wordpress-5f59577d4d-Bt2dg -- bash</code></pre>
<h2 id="config">config</h2>
<ul>
<li>kubectl 설정을 관리</li>
<li>kubectl은 여러 개의 쿠버네티스 클러스터를 컨텍스트로 설정하고 필요에 따라 선택할 수 있음</li>
<li>현재 어떤 컨텍스트로 설정되어 있는지 확인하고 원하는 컨텍스트를 지정</li>
</ul>
<pre><code class="language-shell"># 현재 컨텍스트 확인
kubectl config current-context

# 컨텍스트 설정
kubectl config use-context minikube</code></pre>
<h2 id="그-외">그 외</h2>
<pre><code class="language-shell"># 전체 오브젝트 종류 확인
kubectl api-resources

# 특정 오브젝트 설명 보기
kubectl explain pod</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 아키텍처 - API 호출]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-API-%ED%98%B8%EC%B6%9C</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-API-%ED%98%B8%EC%B6%9C</guid>
            <pubDate>Fri, 14 Nov 2025 15:17:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<p>Desired state를 다양한 오브젝트로 정의하고 API Server에 YAML 형식으로 전달</p>
<h2 id="object-spec">Object Spec</h2>
<ul>
<li>YAML 형식으로 작성</li>
</ul>
<h3 id="aipversion">aipVersion</h3>
<ul>
<li>apps/v1, v1, batch/v1, networking.k8s.io/v1, ...</li>
</ul>
<h3 id="kind">kind</h3>
<ul>
<li>Pod, Deployment, Service, Ingress, ...</li>
</ul>
<h3 id="metadata">metaData</h3>
<ul>
<li>name, label, namespace</li>
</ul>
<h3 id="spec">spec</h3>
<ul>
<li>각종 설정</li>
</ul>
<h3 id="statusread-only">status(read-only)</h3>
<ul>
<li>시스템에서 관리하는 최신 상태</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 아키텍처 - 오브젝트]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Fri, 14 Nov 2025 15:00:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<h2 id="pod">Pod</h2>
<ul>
<li>쿠버네티스에서 컨테이너를 관리하는 가장 작은 배포 단위</li>
<li>쿠버네티스는 컨테이너를 직접 관리하지 않고 Pod로 감싸서 관리</li>
<li>컨테이너를 배포하는 것이 아니라 Pod를 배포</li>
<li>Pod마다 IP 주소를 부여 받음, IP 주소를 통해서 내부적으로 통신할 수 있음</li>
<li>Pod 안에 컨테이너가 하나 이상 존재</li>
<li>Pod 안에 있는 컨테이너끼리 폴더를 공유할 수 있음</li>
<li>Pod 안에 있는 컨테이너끼리 포트를 localhost로 공유할 수 있음</li>
</ul>
<h2 id="replicaset">ReplicaSet</h2>
<ul>
<li>여러 개의 Pod를 관리</li>
<li>새로운 Pod는 Template을 참고하여 생성</li>
</ul>
<h2 id="deployment">Deployment</h2>
<ul>
<li>배포 버전 관리</li>
<li>내부적으로 ReplicaSet을 이용</li>
<li>무중단으로 배포 버전을 바꿀 때 기존의 ReplicaSet과 다른 새로운 ReplicaSet을 만듦</li>
</ul>
<h2 id="service">Service</h2>
<h3 id="clusterip">ClusterIP</h3>
<ul>
<li>클러스터 내부에서 사용하는 프록시, 외부에서는 접근 불가</li>
<li>Pod에 로드 밸런싱</li>
<li>Pod는 동적이지만 Service는 고유한 IP를 가짐</li>
<li>Pod가 사라지고 새로 생성되면서 Pod의 IP도 언제든 사라지고 바뀌게 됨, 요청을 Pod로 바로 보내지 않고 Service에 요청을 보내면 Pod에 전달됨</li>
<li>클러스터 내부에서 Service 연결은 DNS를 이용</li>
</ul>
<h3 id="nodeport">NodePort</h3>
<ul>
<li>Node에 노출되어 외부에서 접근 가능한 Service</li>
<li>Node에 포트가 생기고 이를 통해 접속하면 해당 요청이 ClusterIP를 거쳐 Pod로 전달됨</li>
<li>모든 Node에 동일한 포트로 생성됨, 어느 노드로 요청을 보내도 알맞은 ClusterIP를 찾아 요청을 전달</li>
</ul>
<h3 id="loadbalancer">LoadBalancer</h3>
<ul>
<li>도메인이 연결된 특정 노드가 중단됐을 때를 대비하여 NodePort 앞에 LoadBalancer를 둠</li>
<li>LoadBalancer로 보낸 요청은 NodePort와 ClusterIP를 거쳐 Pod로 전달됨</li>
</ul>
<h2 id="ingress">Ingress</h2>
<ul>
<li>도메인 또는 경로별 라우팅</li>
<li>도메인 이름과 도메인 뒤에 붙는 경로에 따라서 내부에 있는 ClusterIP에 연결할 수 있음</li>
</ul>
<h2 id="일반적인-구성">일반적인 구성</h2>
<ul>
<li>Deployment를 생성하면 Deployment가 ReplicaSet을 자동으로 생성하고 ReplicaSet이 자동으로 Pod를 생성</li>
<li>외부에 노출하기 위해 ClusterIP와 Ingress 생성, Ingress를 만들면 NodePort와 LoadBalancer가 자동으로 생성됨</li>
<li>클라이언트는 도메인으로 접속하면 LoadBalancer, NodePort, ClusterIP를 거쳐서 Pod에 연결됨</li>
</ul>
<h2 id="그-외-기본-오브젝트">그 외 기본 오브젝트</h2>
<ul>
<li>Volume: 스토리지</li>
<li>Namespace: 논리적인 리소스 구분</li>
<li>ConfigMap / Secret: 설정</li>
<li>ServiceAccount: 권한 계정</li>
<li>Role / ClusterRole: 권한 설정</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 아키텍처 - 구성 / 설계]]></title>
            <link>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_chori/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 14 Nov 2025 14:06:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<p>중간에서 상태를 확인하고 실행하는 영역은 Master, 실제로 컨테이너가 실행되는 영역은 Node</p>
<h2 id="master">Master</h2>
<ul>
<li>상태 확인(Observe) -&gt; 차이점 발견(Diff) -&gt; 조치(Act)<ul>
<li>Current State와 Desired State가 일치하는지 확인, 차이점이 발견되면 Current State가 Desired State가 되도록 조치, 이 과정을 반복</li>
</ul>
</li>
<li>API Server<ul>
<li>상태를 바꾸거나 조회</li>
<li>etcd와 유일하게 통신하는 모듈</li>
<li>REST API 형태로 제공</li>
<li>권한을 확인하여 적절한 권한이 없을 경우 요청을 차단</li>
<li>관리자 요청 뿐 아니라 다양한 내부 모듈과 통신</li>
<li>수평으로 확장되도록 디자인</li>
</ul>
</li>
<li>etcd<ul>
<li>모든 상태와 데이터를 저장</li>
<li>분산 시스템으로 구성하여 안전성을 높임 (고가용성)</li>
<li>가볍고 빠르면서 정확하게 설계 (일관성)</li>
<li>Key-Value 형태로 데이터 저장</li>
<li>백업 필수</li>
</ul>
</li>
<li>Scheduler<ul>
<li>새로 생성된 pod를 감지하고 실행할 Node를 선택</li>
<li>Node의 현태 상태와 pod의 요구사항을 확인</li>
</ul>
</li>
<li>Controller<ul>
<li>끊임없이 상태를 확인하고 원하는 상태를 유지</li>
<li>복잡성을 낮추기 위해 하나의 프로세스로 실행</li>
<li>논리적으로 다양한 컨트롤러가 존재</li>
</ul>
</li>
<li>조회 흐름<ol>
<li>Controller가 API Server에 정보 조회 요청</li>
<li>API Server는 요청을 보낸 Controller가 정보 조회 권한이 있는지 확인</li>
<li>권한이 있으면 etcd에서 정보를 조회하여 Controller에게 전달</li>
</ol>
</li>
<li>기본 흐름<ol>
<li>원하는 상태가 변경되면 API Server는 Controller한테 알림</li>
<li>Controller는 원하는 상태와 현재 상태가 불일치하기 때문에 조치를 해야 함, 원하는 상태로 리소스를 변경</li>
<li>Controller는 리소스가 변경된 결과를 API Server한테 전달</li>
<li>API Server는 Controller에게 정보 갱신 권한이 있는지 확인</li>
<li>권한이 있으면 etcd에 정보를 갱신</li>
</ol>
</li>
</ul>
<blockquote>
<p>API Server 통신
<img src="https://velog.velcdn.com/images/dev_chori/post/8c2e8ba1-c3e5-4fa3-8747-6f6b7a71ec76/image.png" alt=""></p>
</blockquote>
<h2 id="node">Node</h2>
<ul>
<li>Node에는 크게 Proxy와 Kubelet이라는 두 개의 컴포넌트가 있음</li>
<li>Proxy와 Kubelet도 Master와 통신할 때 API Server만 바라봄</li>
<li>Proxy<ul>
<li>네트워크 프록시와 부하 분산 역할</li>
</ul>
</li>
<li>Kubelet<ul>
<li>각 Node에서 실행</li>
<li>Pod을 실행 / 중지하고 상태를 확인</li>
</ul>
</li>
</ul>
<h2 id="pod이-생성되는-과정">Pod이 생성되는 과정</h2>
<ol>
<li>관리자가  API Server에 Pod 생성 요청</li>
<li>API Server는 요청을 etcd에 상태를 생성 요청으로 기록</li>
<li>Controller는 새로 생긴 Pod가 있는지 계속 확인하다가 Pod 생성 요청을 발견</li>
<li>Controller는 API Server에 실제 Pod를 할당하는 요청 보냄</li>
<li>API Server는 해당 요청을 받아서 etcd에 상태를 할당 요청으로 변경</li>
<li>Scheduler는 할당 요청이 들어온 Pod이 있는지 계속 확인하다가 Pod 할당 요청을 발견</li>
<li>Sechduler가 특정 Node에 Pod를 할당</li>
<li>API Server는 etcd에 Pod를 특정 노드에 할당하는데 아직 실행되기 전 상태라고 변경</li>
<li>Kubelet이 자신의 Node에 할당된 Pod 중에서 아직 실행이 안 된 것이 있는지 계속 확인하다가 새로 추가된 Pod를 확인</li>
<li>해당 정보를 가지고 와서 Node에 Pod를 생성</li>
<li>API Server가 etcd에 Pod가 특정 Node에 할당되었고 실행 중이라는 상태로 변경</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[컨테이너 오케스트레이션]]></title>
            <link>https://velog.io/@dev_chori/%EC%84%9C%EB%B2%84%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%9C%EB%8B%A4%EB%8A%94-%EA%B2%83</link>
            <guid>https://velog.io/@dev_chori/%EC%84%9C%EB%B2%84%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%9C%EB%8B%A4%EB%8A%94-%EA%B2%83</guid>
            <pubDate>Thu, 13 Nov 2025 14:21:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8">초보를 위한 쿠버네티스 안내서</a>를 수강하며 정리한 내용입니다.</p>
</blockquote>
<hr>
<h2 id="서버의-상태를-관리하기-위한-노력">서버의 상태를 관리하기 위한 노력</h2>
<h3 id="첫-번째-아이디어-문서화">첫 번째 아이디어: 문서화</h3>
<ul>
<li>방법: 프로그램을 설치하는 방법을 세세하게 문서로 작성</li>
<li>문제점: 아무리 문서를 잘 만들어도 환경(OS, 버전 등)이 바뀌면 잘 되지 않는 경우가 발생할 수 있음</li>
</ul>
<h3 id="두-번째-아이디어-서버-관리-도구">두 번째 아이디어: 서버 관리 도구</h3>
<ul>
<li>방법: Ansible, Puppet, Chef와 같은 도구를 사용, 사용자가 직접 서버에 접속해서 명령어를 입력하는 대신에 서버 관리자가 서버 상태를 어떻게 관리하고 싶은지 설정해두면 도구가 그에 맞춰서 수행</li>
<li>문제점: 관리 도구에 대한 학습 필요, 서버 관리가 복잡해지면 도구 사용 방법도 어려워짐</li>
</ul>
<h3 id="세-번째-아이디어-가상-머신">세 번째 아이디어: 가상 머신</h3>
<ul>
<li>방법: 새로 생성한 가상 머신에 프로그램 설치</li>
<li>장점: 하나의 가상 머신에 하나의 프로그램만 실행되어 충돌과 같은 문제가 발생 가능성을 낮출 수 있음</li>
<li>단점: 속도가 느리고 특정 벤더에 의존성이 생김, 클라우드 환경에 적합하지 않을 수 있음</li>
</ul>
<h3 id="네-번째-아이디어-도커">네 번째 아이디어: 도커</h3>
<ul>
<li>방법: 모든 실행 환경을 컨테이너로 구성</li>
<li>장점<ul>
<li>가상 머신과 비교하여 컨테이너 생성이 쉽고 효율적</li>
<li>컨테이너 이미지를 이용한 배포와 롤백이 간단</li>
<li>언어나 프레임워크에 상관없이 애플리케이션을 동일한 방식으로 관리</li>
<li>개발, 테스팅, 운영 환경은 물론 로컬 PC와 클라우드까지 동일한 환경 구축</li>
<li>특정 클라우드 벤더에 종속되지 않음</li>
</ul>
</li>
<li>도커를 사용하는 흐름: 코드 작성 -&gt; 도커 이미지 생성 -&gt; 도커 허브나 별도의 저장소에 보관 -&gt; 도커 이미지를 컨테이너로 실행</li>
<li>한계: 관리해야 할 컨테이너가 너무 많아지면 운영하기 어려워짐</li>
</ul>
<h2 id="도커-그-이후">도커 그 이후</h2>
<h3 id="문제-상황">문제 상황</h3>
<ul>
<li>여러 서버에 직접 접속해서 컨테이너를 관리하기에는 번거로움</li>
<li>컨테이너를 실행하기에 여유가 있는 서버를 찾아야 함</li>
<li>다량의 컨테이너에서 롤아웃/롤백을 할 때 중앙에서 관리하고 싶은 요구</li>
<li>새로 컨테이너와 도메인을 등록하면 관련 요청이 해당 컨테이너로 전달되도록 설정 자동화 요구</li>
<li>서비스 이상, 부하 모니터링과 대응 자동화 요구</li>
</ul>
<blockquote>
<p>복잡한 컨테이너 환경을 효과적으로 관리하기 위한 도구의 필요성이 높아졌고 여기에 사용되는 것이 컨테이너 오케스트레이션 기술</p>
</blockquote>
<h2 id="컨테이너-오케스트레이션">컨테이너 오케스트레이션</h2>
<h3 id="첫-번째-특징-클러스터">첫 번째 특징: 클러스터</h3>
<ul>
<li>노드를 각각 관리하지 않고 클러스터 단위로 추상화하여 관리</li>
<li>노드에 일일이 접속하기 어렵하기 때문에 마스터 서버를 두고 관리자는 마스터 서버에 명령어 사용, 마스터 서버가 노드들에게 명령어 전달</li>
<li>클러스터에 속한 노드 간에 네트워크 통신이 잘 되어야 함</li>
<li>노드의 개수가 수 만개가 되더라도 잘 동작해야 함, 부하를 잘 견디도록 설계해야 함</li>
</ul>
<h3 id="두-번째-특징-상태-관리">두 번째 특징: 상태 관리</h3>
<ul>
<li>관리자가 상태를 변경하면 컨테이너 오케스트레이션이 자동으로 그에 맞춰서 작업 수행</li>
</ul>
<h3 id="세-번째-특징-배포-관리">세 번째 특징: 배포 관리</h3>
<ul>
<li>어떤 서버에 여유가 있는지를 자동으로 파악해서 애플리케이션 배포</li>
</ul>
<h3 id="네-번째-특징-버전-관리">네 번째 특징: 버전 관리</h3>
<ul>
<li>직접 애플리케이션의 버전을 관리하지 않고 중앙에서 관리</li>
</ul>
<h3 id="다섯-번째-특징-서비스-등록-및-조회">다섯 번째 특징: 서비스 등록 및 조회</h3>
<ul>
<li>애플리케이션이 실행되고 특정 주소가 부여되면 중앙 서버에 등록</li>
<li>프록시 서버는 서비스가 등록된 저장소를 관찰하다가 변경이 감지되면 내부적으로 설정을 바꾸고 프로세스 재시작</li>
<li>관리자가 IP 주소를 일일이 바꿀 필요 없이 프로그램이 자동으로 재설정</li>
</ul>
<h3 id="여섯-번째-특징-볼륨-스토리지">여섯 번째 특징: 볼륨 스토리지</h3>
<ul>
<li>노드에 볼륨을 마운트 할 때 직접 관리하지 않고 설정을 이용해 추상적으로 관리</li>
</ul>
<h2 id="쿠버네티스-소개">쿠버네티스 소개</h2>
<ul>
<li>컨테이너를 쉽고 빠르게 배포 / 확장하고 관리를 자동화하는 오픈소스 플랫폼</li>
<li>컨테이너를 쉽게 관리하고 연결하기 위해 논리적인 단위로 그룹화</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스의 Read replica 생성 및 웹 서버 연결]]></title>
            <link>https://velog.io/@dev_chori/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%9D%98-Read-replica-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%9B%B9-%EC%84%9C%EB%B2%84-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@dev_chori/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%9D%98-Read-replica-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%9B%B9-%EC%84%9C%EB%B2%84-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Tue, 11 Feb 2025 05:40:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/aws-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B8%B0%EB%B3%B8">스스로 구축하는 AWS 클라우드 인프라 - 기본편</a>을 수강하며 AWS 인프라를 Terraform으로 작성한 내용입니다.</p>
</blockquote>
<hr>
<h2 id="read-replica-생성">Read Replica 생성</h2>
<ul>
<li><code>rds.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL">resource &quot;aws_db_instance&quot; &quot;rds_rr&quot; {
  identifier                 = &quot;rds-rr&quot;
  replicate_source_db        = aws_db_instance.rds.identifier
  instance_class             = &quot;db.t3.micro&quot;
  storage_type               = &quot;gp2&quot;
  max_allocated_storage      = 0
  auto_minor_version_upgrade = true
  multi_az                   = false
  availability_zone          = var.availability_zones[1]
  vpc_security_group_ids     = [aws_security_group.rds_sg.id]
  publicly_accessible        = false
  copy_tags_to_snapshot      = false
  deletion_protection        = false
  skip_final_snapshot        = true
}</code></pre>
<hr>
<h2 id="ec2와-read-replica-연결">EC2와 Read Replica 연결</h2>
<ul>
<li><code>template_file.tf</code> 파일을 아래와 같이 변경</li>
</ul>
<pre><code class="language-HCL">data &quot;template_file&quot; &quot;dbinfo&quot; {
  template = &lt;&lt;-EOT
  &lt;?php
  define(&#39;DB_SERVER&#39;, &#39;${aws_db_instance.rds_rr.address}&#39;);
  define(&#39;DB_USERNAME&#39;, &#39;${aws_db_instance.rds.username}&#39;);
  define(&#39;DB_PASSWORD&#39;, &#39;${aws_db_instance.rds.password}&#39;);
  define(&#39;DB_DATABASE&#39;, &#39;${aws_db_instance.rds.db_name}&#39;);
  ?&gt;
  EOT
}

resource &quot;local_file&quot; &quot;dbinfo_file&quot; {
  content  = data.template_file.dbinfo.rendered
  filename = &quot;${path.module}/dbinfo.inc&quot;
}</code></pre>
<ul>
<li>Private EC2에서 RDS에 접속한 뒤 만든 데이터가 Read Replica와 연결된 웹 서버에서 올바르게 조회되는지 확인</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 서버와 데이터베이스 인스턴스 연결]]></title>
            <link>https://velog.io/@dev_chori/%EC%9B%B9-%EC%84%9C%EB%B2%84%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@dev_chori/%EC%9B%B9-%EC%84%9C%EB%B2%84%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Tue, 11 Feb 2025 03:44:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/aws-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B8%B0%EB%B3%B8">스스로 구축하는 AWS 클라우드 인프라 - 기본편</a>을 수강하며 AWS 인프라를 Terraform으로 작성한 내용입니다.</p>
</blockquote>
<hr>
<h2 id="db-정보-작성">DB 정보 작성</h2>
<ul>
<li>웹 서버에서 DB에 접근할 수 있도록 <code>dbinfo.inc</code> 파일 필요</li>
<li><code>template_file.tf</code> 파일을 만들고 아래와 같이 작성하여 Terraform이 <code>dbinfo.inc</code> 파일을 생성하도록 함</li>
</ul>
<pre><code class="language-HCL">data &quot;template_file&quot; &quot;dbinfo&quot; {
  template = &lt;&lt;-EOT
  &lt;?php
  define(&#39;DB_SERVER&#39;, &#39;${aws_db_instance.rds.address}&#39;);
  define(&#39;DB_USERNAME&#39;, &#39;${aws_db_instance.rds.username}&#39;);
  define(&#39;DB_PASSWORD&#39;, &#39;${aws_db_instance.rds.password}&#39;);
  define(&#39;DB_DATABASE&#39;, &#39;${aws_db_instance.rds.db_name}&#39;);
  ?&gt;
  EOT
}

resource &quot;local_file&quot; &quot;dbinfo_file&quot; {
  content  = data.template_file.dbinfo.rendered
  filename = &quot;${path.module}/dbinfo.inc&quot;
}</code></pre>
<ul>
<li><code>dbinfo.inc</code> 파일이 Private EC2에 저장되도록 <code>terraform_data.tf</code> 파일과 <code>ami_from_instance.tf</code> 파일을 아래와 같이 수정</li>
</ul>
<pre><code class="language-HCL">resource &quot;terraform_data&quot; &quot;copy_php&quot; {
  depends_on = [aws_instance.public_ec2, local_file.dbinfo_file]

  count = length(var.availability_zones)

  connection {
    type        = &quot;ssh&quot;
    user        = &quot;ec2-user&quot;
    host        = element(aws_eip.public_ec2.*.public_ip, count.index)
    private_key = tls_private_key.ec2_private_key.*.private_key_pem[0]
  }

  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;while [ ! -d /var/www/html ]; do sleep 5; done&quot;,
      &quot;echo &#39;/var/www/html is ready&#39;&quot;,
      &quot;sudo chown -R ec2-user:apache /var/www/html&quot;,
      &quot;sudo chmod -R 775 /var/www/html&quot;
    ]
  }

  provisioner &quot;file&quot; {
    source      = &quot;${path.module}/index.php&quot;
    destination = &quot;/var/www/html/index.php&quot;
  }

  provisioner &quot;file&quot; {
    source      = &quot;${path.module}/dbinfo.inc&quot;
    destination = &quot;/var/www/html/dbinfo.inc&quot;
  }
}

resource &quot;terraform_data&quot; &quot;delete_dbinfo_file&quot; {
  depends_on = [aws_ami_from_instance.public_ec2_ami]
  provisioner &quot;local-exec&quot; {
    command = &quot;rm -f ${path.module}/dbinfo.inc&quot;
  }
}

resource &quot;terraform_data&quot; &quot;copy_key&quot; {
  depends_on = [local_file.private_ec2_key, aws_ami_from_instance.public_ec2_ami]

  count = length(var.availability_zones)

  connection {
    type        = &quot;ssh&quot;
    user        = &quot;ec2-user&quot;
    host        = element(aws_eip.public_ec2.*.public_ip, count.index)
    private_key = tls_private_key.ec2_private_key.*.private_key_pem[0]
  }

  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;mkdir /home/ec2-user/keys&quot;
    ]
  }

  provisioner &quot;file&quot; {
    source      = &quot;${path.module}/${local_file.private_ec2_key.filename}&quot;
    destination = &quot;/home/ec2-user/keys/${local_file.private_ec2_key.filename}&quot;
  }

  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;chmod 600 /home/ec2-user/keys/${local_file.private_ec2_key.filename}&quot;
    ]
  }
}</code></pre>
<pre><code class="language-HCL">resource &quot;aws_ami_from_instance&quot; &quot;public_ec2_ami&quot; {
  depends_on = [terraform_data.copy_php]

  name                    = &quot;public-ec2-ami&quot;
  source_instance_id      = aws_instance.public_ec2.*.id[0]
  snapshot_without_reboot = false
}</code></pre>
<hr>
<h2 id="mysql-접속">MySQL 접속</h2>
<ul>
<li>Private EC2에서 MySQL에 접속하기 위해 아래 명령어 입력</li>
</ul>
<pre><code class="language-shell">mysql -h &lt;RDS Endpoint&gt; -P 3306 -u admin -p</code></pre>
<ul>
<li>MySQL에 접속 후 데이터베이스 목록을 보려면 아래 명령어 입력</li>
</ul>
<pre><code class="language-shell">show databases;</code></pre>
<ul>
<li>특정 데이터베이스를 사용하려면 아래 명령어 입력</li>
</ul>
<pre><code class="language-shell">use &lt;DatabaseToUse&gt;;</code></pre>
<ul>
<li>데이터베이스에 <code>SAMPLE</code> 테이블 생성</li>
</ul>
<pre><code class="language-SQL">CREATE TABLE SAMPLE (
ID INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(45),
ADDRESS VARCHAR(90)
);</code></pre>
<ul>
<li>테이블에 데이터 추가</li>
</ul>
<pre><code class="language-SQL">INSERT INTO SAMPLE (NAME, ADDRESS) VALUES (&#39;KIM&#39;, &#39;SEOUL&#39;);</code></pre>
<ul>
<li>테이블에 추가된 데이터 확인</li>
</ul>
<pre><code class="language-SQL">SELECT * FROM SAMPLE;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Amazon RDS를 통한 MySQL 데이터베이스 이중화(Multi-AZ) 구성]]></title>
            <link>https://velog.io/@dev_chori/Amazon-RDS%EB%A5%BC-%ED%86%B5%ED%95%9C-MySQL-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9D%B4%EC%A4%91%ED%99%94Multi-AZ-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@dev_chori/Amazon-RDS%EB%A5%BC-%ED%86%B5%ED%95%9C-MySQL-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%9D%B4%EC%A4%91%ED%99%94Multi-AZ-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Mon, 10 Feb 2025 12:28:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/aws-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B8%B0%EB%B3%B8">스스로 구축하는 AWS 클라우드 인프라 - 기본편</a>을 수강하며 AWS 인프라를 Terraform으로 작성한 내용입니다.</p>
</blockquote>
<hr>
<h2 id="복수의-가용-영역에-생성하는-rds">복수의 가용 영역에 생성하는 RDS</h2>
<ul>
<li>서로 다른 가용 영역의 Private Subnet에 3개의 RDS를 배치</li>
<li>하나는 Master 데이터베이스, 또 다른 하나는 Standby 데이터베이스, 마지막은 Read Replica 데이터베이스</li>
<li>기본적으로 Master 데이터베이스와 서버가 연결되어서 시스템이 운영되는데 만약 Master 데이터베이스에 장애가 발생하면 자동으로 서버와 Standby 데이터베이스가 연결됨<ul>
<li>이 과정에서 Standby 데이터베이스가 Master 데이터베이스로 변경되고 Master 데이터베이스가 Standby 데이터베이스가 되어 서로 역할이 바뀜</li>
</ul>
</li>
<li>읽기 전용 복제본인 Read Replica는 Master 데이터베이스와 싱크를 맞추며 DB의 변동 내역을 실시간으로 업데이트</li>
</ul>
<hr>
<h2 id="security-group-생성">Security group 생성</h2>
<ul>
<li>데이터베이스 인스턴스에 대한 Security group 만들기</li>
<li><code>security_group.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL"># Security group for RDS
resource &quot;aws_security_group&quot; &quot;rds_sg&quot; {
  name = &quot;rds-sg&quot;
  description = &quot;Security group for RDS&quot;
  vpc_id = aws_vpc.main.id

  tags = {
    Name = &quot;rds-sg&quot;
  }
}

# Inbound rule allowing MySQL for RDS
resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;allow_mysql_for_rds&quot; {
  security_group_id = aws_security_group.rds_sg.id
  cidr_ipv4 = aws_vpc.main.cidr_block
  from_port = &quot;3306&quot;
  ip_protocol = &quot;tcp&quot;
  to_port = &quot;3306&quot;
}

# Outbound rule allowing all traffic for RDS
resource &quot;aws_vpc_security_group_egress_rule&quot; &quot;allow_all_outbound_traffic_for_rds&quot; {
  security_group_id = aws_security_group.rds_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  ip_protocol = &quot;-1&quot;
}</code></pre>
<ul>
<li>특정 VPC에서 출발한 트래픽을 허용하도록 인바운드 규칙 설정</li>
</ul>
<hr>
<h2 id="db-subnet-group-생성">DB Subnet group 생성</h2>
<ul>
<li>데이터베이스 인스턴스가 위치할 서브넷 그룹 만들기</li>
<li><code>subnet.tf</code> 파일을 아래와 같이 변경</li>
</ul>
<pre><code class="language-HCL"># Public Subnet for ec2
# Subnet will use cidr with /24 -&gt; The number of availability IP is 256
resource &quot;aws_subnet&quot; &quot;public_for_ec2&quot; {
  count = length(var.availability_zones)
  vpc_id = aws_vpc.main.id

  cidr_block = &quot;10.0.${var.cidr_numeral_public_ec2[count.index]}.0/24&quot;
  availability_zone = element(var.availability_zones, count.index)

  tags = {
    Name = &quot;public-${count.index}-${var.vpc_name}&quot;
  }
}

# Private Subnet for ec2
# Subnet will use cidr with /24 -&gt; The number of availability IP is 256
resource &quot;aws_subnet&quot; &quot;private_for_ec2&quot; {
  count = length(var.availability_zones)
  vpc_id = aws_vpc.main.id

  cidr_block = &quot;10.0.${var.cidr_numeral_private_ec2[count.index]}.0/24&quot;
  availability_zone = element(var.availability_zones, count.index)

  tags = {
    Name = &quot;private-${count.index}-${var.vpc_name}&quot;
  }
}

# Private Subnet for rds
# Subnet will use cidr with /24 -&gt; The number of availability IP is 256
resource &quot;aws_subnet&quot; &quot;private_for_rds&quot; {
  count = length(var.availability_zones)
  vpc_id = aws_vpc.main.id

  cidr_block = &quot;10.0.${var.cidr_numeral_private_rds[count.index]}.0/24&quot;
  availability_zone = element(var.availability_zones, count.index)

  tags = {
    Name = &quot;private-${count.index}-${var.vpc_name}&quot;
  }
}</code></pre>
<ul>
<li><code>db_subnet.tf</code> 파일을 만들고 아래와 같이 작성</li>
</ul>
<pre><code class="language-HCL">resource &quot;aws_db_subnet_group&quot; &quot;rds_subnet_group&quot; {
  name = &quot;rds-subnet-group&quot;
  description = &quot;Subnet group for rds-subnet-group&quot;
  subnet_ids = aws_subnet.private_for_rds.*.id

  tags = {
    Name = &quot;rds-subnet-group&quot;
  }
}</code></pre>
<hr>
<h2 id="kms-key-생성-및-암호화">KMS Key 생성 및 암호화</h2>
<h3 id="kms-key-만들기">KMS Key 만들기</h3>
<ul>
<li><code>kms_key.tf</code> 파일을 만들고 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL">data &quot;aws_caller_identity&quot; &quot;current&quot; {}

resource &quot;aws_kms_key&quot; &quot;rds_password_key&quot; {
  description             = &quot;KMS key for encrypting RDS password&quot;
  enable_key_rotation     = true
  deletion_window_in_days = 7
  rotation_period_in_days = 90
}

resource &quot;aws_kms_key_policy&quot; &quot;rds_password_key_policy&quot; {
  key_id = aws_kms_key.rds_password_key.id
  policy = jsonencode({
    Version = &quot;2012-10-17&quot;
    Id      = &quot;key-default-1&quot;
    Statement = [
      {
        Sid    = &quot;Enable IAM User Permissions&quot;
        Effect = &quot;Allow&quot;
        Principal = {
          AWS = &quot;arn:aws:iam::${data.aws_caller_identity.current.account_id}:root&quot;
        },
        Action   = &quot;kms:*&quot;
        Resource = &quot;*&quot;
      }
    ]
  })
}

resource &quot;aws_kms_alias&quot; &quot;rds_password_key&quot; {
  name          = &quot;alias/rds-password-key&quot;
  target_key_id = aws_kms_key.rds_password_key.key_id
}</code></pre>
<h3 id="rds에-사용할-비밀번호-암호화">RDS에 사용할 비밀번호 암호화</h3>
<ul>
<li><code>terraform.dec.json</code> 파일을 만들고 비밀번호 작성</li>
</ul>
<pre><code class="language-json">{
    &quot;secret&quot;: &quot;Write-your-own-password&quot;
}</code></pre>
<ul>
<li><code>SOPS</code> 명령어를 사용하여 비밀번호 암호화</li>
</ul>
<pre><code class="language-shell">sops --encrypt --kms=arn:aws:kms:&lt;AWS_REGION&gt;:&lt;AWS_ACCOUNT_ID&gt;:key/&lt;AWS_KMS_KEY_ID&gt; terraform.dec.json &gt; terraform.enc.json</code></pre>
<ul>
<li><code>terraform.dec.json</code> 파일은 Git에서 관리되지 않도록 삭제하거나 <code>.gitignore</code>에 추가</li>
</ul>
<hr>
<h2 id="rds-생성">RDS 생성</h2>
<ul>
<li><code>rds.tf</code> 파일을 만들고 아래와 같이 작성</li>
</ul>
<pre><code class="language-HCL">data &quot;external&quot; &quot;sops_secret&quot; {
  program = [&quot;sh&quot;, &quot;-c&quot;, &quot;sops -d terraform.enc.json | jq -r &#39;{secret: .secret}&#39;&quot;]
}

resource &quot;aws_db_instance&quot; &quot;rds&quot; {
  identifier                      = &quot;rds&quot;
  db_name                         = &quot;vpcrds&quot;
  instance_class                  = &quot;db.t3.micro&quot;
  storage_type                    = &quot;gp2&quot;
  allocated_storage               = 20
  max_allocated_storage           = 0
  engine                          = &quot;mysql&quot;
  engine_version                  = &quot;8.0&quot;
  auto_minor_version_upgrade      = true
  username                        = &quot;admin&quot;
  password                        = data.external.sops_secret.result[&quot;secret&quot;]
  multi_az                        = true
  db_subnet_group_name            = aws_db_subnet_group.rds_subnet_group.name
  vpc_security_group_ids          = [aws_security_group.rds_sg.id]
  publicly_accessible             = false
  parameter_group_name            = &quot;default.mysql8.0&quot;
  backup_retention_period         = 7
  copy_tags_to_snapshot           = true
  enabled_cloudwatch_logs_exports = [&quot;general&quot;]
  deletion_protection             = false
  skip_final_snapshot             = true
}</code></pre>
<ul>
<li><code>multi_az</code>를 <code>true</code>로 설정하여 복수의 가용 영역에 데이터베이스를 생성<ul>
<li>Subnet group에 등록된 Subnet 중 하나에는 Master 데이터베이스가 만들어지고 다른 Subnet에는 Standby 데이터베이스가 만들어짐</li>
</ul>
</li>
<li><code>parameter_group</code>은 데이터베이스 인스턴스 엔진의 기본적인 구성값들이 설정되어 있는 집합</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Application Load Balancer를 통한 이중화 네트워크 구성 (2)]]></title>
            <link>https://velog.io/@dev_chori/Application-Load-Balancer%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%9D%B4%EC%A4%91%ED%99%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%84%B1-2</link>
            <guid>https://velog.io/@dev_chori/Application-Load-Balancer%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%9D%B4%EC%A4%91%ED%99%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%84%B1-2</guid>
            <pubDate>Sat, 04 Jan 2025 05:10:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/aws-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B8%B0%EB%B3%B8">스스로 구축하는 AWS 클라우드 인프라 - 기본편</a>을 수강하며 AWS 인프라를 Terraform으로 작성한 내용입니다.</p>
</blockquote>
<hr>
<ul>
<li>웹 서버를 외부와 직접적인 통신이 가능한 Public Subnet에 두는 것은 보안 측면에서 적절하지 않음</li>
<li>웹 서버를 외부와 직접적인 통신에 제한되어 있는 Private Subnet에 두고 이 영역에 있는 EC2 인스턴스를 타겟으로 하는 Application Load Balancer를 통해 트래픽을 분산하고 웹 서비스를 제공하도록 구성</li>
</ul>
<hr>
<h2 id="target-group-생성">Target group 생성</h2>
<ul>
<li>Private EC2 인스턴스를 타겟으로 하는 Target group 만들기</li>
<li><code>target_group.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL"># Target group for private ec2 instances
resource &quot;aws_lb_target_group&quot; &quot;alb_private_tg&quot; {
  name = &quot;alb-private-tg-${var.vpc_name}&quot;
  vpc_id = aws_vpc.main.id
  target_type = &quot;instance&quot;
  port = 80
  protocol = &quot;HTTP&quot;
  protocol_version = &quot;HTTP1&quot;

  health_check {
    protocol = &quot;HTTP&quot;
    path = &quot;/&quot;
    enabled = true
    healthy_threshold = 5
    unhealthy_threshold = 2
    timeout = 5
    interval = 30
    matcher = &quot;200&quot;
  }

  tags = {
    Name = &quot;alb-private-tf-${var.vpc_name}&quot;
  }
}

# Register private ec2 instances with Target group
resource &quot;aws_lb_target_group_attachment&quot; &quot;private_ec2&quot; {
  for_each = {
    for k, v in aws_instance.private_ec2 : k =&gt; v # type casting: list to map 
  }

  target_group_arn = aws_lb_target_group.alb_private_tg.arn
  target_id = each.value.id
  port = 80
}</code></pre>
<hr>
<h2 id="security-group-생성">Security group 생성</h2>
<ul>
<li>Application Load Balancer에 적용할 Security group 만들기</li>
<li><code>security_group.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL"># Security group for private ALB
resource &quot;aws_security_group&quot; &quot;alb_private_sg&quot; {
  name = &quot;alb-private-sg&quot;
  description = &quot;Secutiry group for private alb&quot;
  vpc_id = aws_vpc.main.id
  tags = {
    Name = &quot;alb-private-sg&quot;
  }
}

# Inbound rule allowing HTTP for private ALB
resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;allow_http_for_private_alb&quot; {
  security_group_id = aws_security_group.alb_private_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  from_port = 80
  ip_protocol = &quot;tcp&quot;
  to_port = 80
}

# Outbound rule allowing all traffic for private ALB
resource &quot;aws_vpc_security_group_egress_rule&quot; &quot;allow_all_outbound_traffic_for_private_alb&quot; {
  security_group_id = aws_security_group.alb_private_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  ip_protocol = &quot;-1&quot;
}</code></pre>
<hr>
<h2 id="application-load-balancer-생성">Application Load Balancer 생성</h2>
<ul>
<li><code>application_load_balancer.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL">resource &quot;aws_lb&quot; &quot;alb_private&quot; {
  name = &quot;alb-private&quot;
  load_balancer_type = &quot;application&quot;
  internal = false
  ip_address_type = &quot;ipv4&quot;
  security_groups = [aws_security_group.alb_private_sg.id]

  # Indicate which subnet in the availability zone will receive traffic
  dynamic &quot;subnet_mapping&quot; {
    for_each = toset(aws_subnet.public_for_ec2)
    content {
      subnet_id = subnet_mapping.value.id
    }
  }
}

resource &quot;aws_lb_listener&quot; &quot;alb_private_listener&quot; {
  load_balancer_arn = aws_lb.alb_private.arn
  port = 80
  protocol = &quot;HTTP&quot;

  default_action {
    type = &quot;forward&quot;
    target_group_arn = aws_lb_target_group.alb_private_tg.arn
  }

  tags = {
    Name = &quot;alb-private&quot;
  }
}</code></pre>
<ul>
<li>Application Load Balancer가 인터넷에서 트래픽을 받을 수 있도록 Public Subnet에 배치</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Bastion host와 NAT Gateway를 통한 Private EC2 인스턴스의 외부 통신 구성]]></title>
            <link>https://velog.io/@dev_chori/Bastion-host%EC%99%80-NAT-Gateway%EB%A5%BC-%ED%86%B5%ED%95%9C-Private-EC2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EC%9D%98-%EC%99%B8%EB%B6%80-%ED%86%B5%EC%8B%A0-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@dev_chori/Bastion-host%EC%99%80-NAT-Gateway%EB%A5%BC-%ED%86%B5%ED%95%9C-Private-EC2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EC%9D%98-%EC%99%B8%EB%B6%80-%ED%86%B5%EC%8B%A0-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Fri, 03 Jan 2025 12:41:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/aws-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B8%B0%EB%B3%B8">스스로 구축하는 AWS 클라우드 인프라 - 기본편</a>을 수강하며 AWS 인프라를 Terraform으로 작성한 내용입니다.</p>
</blockquote>
<hr>
<h2 id="public-subnet에-있는-ec2-인스턴스-구성-변경">Public Subnet에 있는 EC2 인스턴스 구성 변경</h2>
<h3 id="public-ec2의-구성을-위한-provisioner">Public EC2의 구성을 위한 Provisioner</h3>
<ul>
<li>Public EC2 인스턴스가 생성되면 <code>index.php</code> 파일과 Private EC2에 접속하기 위한 Private Key를 로컬에서 인스턴스로 복사</li>
<li><code>terraform_data.tf</code> 파일을 만들고 아래와 같이 작성</li>
</ul>
<pre><code class="language-HCL">resource &quot;terraform_data&quot; &quot;copy_index&quot; {
  depends_on = [aws_instance.public_ec2]

  count = length(var.cidr_numeral_public)

  connection {
    type = &quot;ssh&quot;
    user = &quot;ec2-user&quot;
    host = element(aws_eip.public_ec2.*.public_ip, count.index)
    private_key = tls_private_key.ec2_private_key.*.private_key_pem[0]
  }

  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;while [ ! -d /var/www/html ]; do sleep 5; done&quot;,
      &quot;echo &#39;/var/www/html is ready&#39;&quot;,
      &quot;sudo chown -R ec2-user:apache /var/www/html&quot;,
      &quot;sudo chmod -R 775 /var/www/html&quot;
    ]
  }

  provisioner &quot;file&quot; {
    source = &quot;${path.module}/index.php&quot;
    destination = &quot;/var/www/html/index.php&quot;
  }
}

resource &quot;terraform_data&quot; &quot;copy_key&quot; {
  depends_on = [local_file.private_ec2_key, aws_ami_from_instance.public_ec2_ami]

  count = length(var.cidr_numeral_public)

  connection {
    type = &quot;ssh&quot;
    user = &quot;ec2-user&quot;
    host = element(aws_eip.public_ec2.*.public_ip, count.index)
    private_key = tls_private_key.ec2_private_key.*.private_key_pem[0]
  }

  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;mkdir /home/ec2-user/keys&quot;
    ]
  }

  provisioner &quot;file&quot; {
    source = &quot;${path.module}/${local_file.private_ec2_key.filename}&quot;
    destination = &quot;/home/ec2-user/keys/${local_file.private_ec2_key.filename}&quot;
  }

  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;chmod 600 /home/ec2-user/keys/${local_file.private_ec2_key.filename}&quot;
    ]
  }
}</code></pre>
<ul>
<li>Public EC2 인스턴스는 Private EC2 인스턴스에 접속하기 위한 중개 서버 역할을 하게 되며 Public EC2 인스턴스가 Bastion host가 됨</li>
<li>Bastion host는 외부 네트워크와 외부 네트워크를 연결하는 게이트웨이 역할을 함</li>
</ul>
<h2 id="private-subnet에-ec2-인스턴스-생성">Private Subnet에 EC2 인스턴스 생성</h2>
<h3 id="key-pair-만들기">Key Pair 만들기</h3>
<ul>
<li>Private EC2 인스턴스에 사용할 Key Pair 생성</li>
<li><code>key_pair.tf</code> 파일을 아래와 같이 변경</li>
</ul>
<pre><code class="language-HCL"># Create a PEM formatted private key
resource &quot;tls_private_key&quot; &quot;ec2_private_key&quot; {
  count = 2
  algorithm = &quot;RSA&quot;
  rsa_bits = 4096
}

# Public ec2 key pair
resource &quot;aws_key_pair&quot; &quot;public_ec2_key_pair&quot; {
  key_name = &quot;public_ec2_key_pair&quot;
  public_key = tls_private_key.ec2_private_key[0].public_key_openssh
}

# Generate a local file about public ec2 key
resource &quot;local_file&quot; &quot;public_ec2_key&quot; {
  filename = &quot;public_ec2_key_pair.pem&quot;
  content = tls_private_key.ec2_private_key[0].private_key_pem
}

# Private ec2 key pair
resource &quot;aws_key_pair&quot; &quot;private_ec2_key_pair&quot; {
  key_name = &quot;private_ec2_key_pair&quot;
  public_key = tls_private_key.ec2_private_key[1].public_key_openssh
}

# Generate a local file about private ec2 key
resource &quot;local_file&quot; &quot;private_ec2_key&quot; {
  filename = &quot;private_ec2_key_pair.pem&quot;
  content = tls_private_key.ec2_private_key[1].private_key_pem
}</code></pre>
<h3 id="security-group-만들기">Security group 만들기</h3>
<ul>
<li><code>security_group.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL"># Security group for private ec2
resource &quot;aws_security_group&quot; &quot;private_ec2_sg&quot; {
  name = &quot;private-ec2-sg&quot;
  description = &quot;Security group for private ec2 instance&quot;
  vpc_id = aws_vpc.main.id

  tags = {
    Name = &quot;private-ec2-sg&quot;
  }
}

# Inbound rule allowing SSH for private ec2
resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;allow_ssh_for_private_ec2&quot; {
  security_group_id = aws_security_group.private_ec2_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  from_port = 22
  ip_protocol = &quot;tcp&quot;
  to_port = 22
}

# Inbound rule allowing HTTP for private ec2
resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;allow_http_for_private_ec2&quot; {
  security_group_id = aws_security_group.private_ec2_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  from_port = 80
  ip_protocol = &quot;tcp&quot;
  to_port = 80
}

# Inbound rule allowing HTTPS for private ec2
resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;allow_https_for_private_ec2&quot; {
  security_group_id = aws_security_group.private_ec2_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  from_port = 443
  ip_protocol = &quot;tcp&quot;
  to_port = 443
}

# Outbound rule allowing all traffic for private ec2
resource &quot;aws_vpc_security_group_egress_rule&quot; &quot;allow_all_outbound_traffic_for_private_ec2&quot; {
  security_group_id = aws_security_group.private_ec2_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  ip_protocol = &quot;-1&quot;
}</code></pre>
<h3 id="ec2-인스턴스-만들기">EC2 인스턴스 만들기</h3>
<ul>
<li><code>ami_from_instance.tf</code> 파일을 아래와 같이 변경</li>
</ul>
<pre><code class="language-HCL">resource &quot;aws_ami_from_instance&quot; &quot;public_ec2_ami&quot; {
  depends_on = [terraform_data.copy_index]

  name = &quot;public-ec2-ami&quot;
  source_instance_id = aws_instance.public_ec2.*.id[0]
  snapshot_without_reboot = false
}</code></pre>
<ul>
<li><code>ec2.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL"># Private ec2
resource &quot;aws_instance&quot; &quot;private_ec2&quot; {
  count = length(var.cidr_numeral_private)
  ami = aws_ami_from_instance.public_ec2_ami.id # public ec2 ami
  instance_type = &quot;t2.micro&quot;
  key_name = aws_key_pair.private_ec2_key_pair.key_name
  vpc_security_group_ids = [aws_security_group.private_ec2_sg.id]
  subnet_id = element(aws_subnet.private.*.id, count.index)

  tags = {
    Name = &quot;private-ec2-${count.index}-${var.vpc_name}&quot;
  }

  metadata_options {
    http_endpoint = &quot;enabled&quot;
    http_put_response_hop_limit = 1
    http_tokens = &quot;optional&quot;
    instance_metadata_tags = &quot;enabled&quot;
  }
}</code></pre>
<h3 id="nat-gateway-생성">NAT Gateway 생성</h3>
<ul>
<li>Private EC2 인스턴스가 외부 네트워크와 통신하기 위해서 NAT Gateway가 필요</li>
<li>NAT Gateway에 Elastic IP를 할당하기 위해 <code>eip.tf</code> 파일에 다음을 추가</li>
</ul>
<pre><code class="language-HCL"># Elastic IP for nat gateway
resource &quot;aws_eip&quot; &quot;nat_gateway&quot; {
  count = length(var.cidr_numeral_public)
  instance = element(aws_nat_gateway.main.*.id, count.index)
  domain = &quot;vpc&quot;

  tags = {
    Name = &quot;eip-nat-gateway-${count.index}&quot;
  }
}</code></pre>
<ul>
<li>NAT Gateway를 만들기 위해 <code>nat_gateway.tf</code> 파일을 생성하고 아래와 같이 작성</li>
</ul>
<pre><code class="language-HCL">resource &quot;aws_nat_gateway&quot; &quot;main&quot; {
  count = length(var.cidr_numeral_public)
  subnet_id = element(aws_subnet.public.*.id, count.index)

  tags = {
    Name = &quot;nat-gw-${count.index}-${var.vpc_name}&quot;
  }
}</code></pre>
<h3 id="private-subnet의-route-table-수정">Private Subnet의 Route Table 수정</h3>
<ul>
<li>Route Table에 Nat Gateway 경로 추가</li>
<li><code>route_table.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL"># Route for private subnets
resource &quot;aws_route&quot; &quot;private_nat&quot; {
  count = length(var.cidr_numeral_private)
  route_table_id = element(aws_route_table.private.*.id, count.index)
  destination_cidr_block = &quot;0.0.0.0/0&quot;
  gateway_id = aws_nat_gateway.main.id
}</code></pre>
<ul>
<li>외부로 향하는 트래픽은 Public Subnet에 생성한 NAT Gateway를 통과하게 됨</li>
</ul>
<hr>
<h2 id="트러블슈팅">트러블슈팅</h2>
<ul>
<li>Public EC2 인스턴스가 생성된 후에 <code>provisioner &quot;file&quot;</code>로 로컬에 있는 <code>index.php</code> 파일을 EC2 인스턴스에 복사할 때 발생한 문제는 다음과 같음</li>
</ul>
<h3 id="1-no-such-file-or-directory">1. No such file or directory</h3>
<ul>
<li>원인: Provisioner가 실행되는 시점에 Public EC2 인스턴스가 아직 <code>user_data</code> 스크립트가 실행 중이어서 <code>/var/www/html</code> 디렉토리가 아직 생성되지 않았음</li>
<li>해결책: <code>remote-exec</code> Privisioner로 디렉토리가 생성될 때까지 대기하다가 디렉토리가 만들어진 것을 확인하면 파일 복사</li>
</ul>
<h3 id="2-upload-failed-scp-varwwwhtmlindexphp-permission-denied">2. Upload failed: scp: /var/www/html/index.php: Permission denied</h3>
<ul>
<li>원인: <code>/var/www/html</code> 디렉토리에 대한 쓰기 권한이 없음</li>
<li>해결책: <code>/var/www/html</code> 디렉토리의 소유권을 <code>apache</code> 그룹의 <code>ec2-user</code>로 변경하고 디렉토리의 권한을 <code>775</code>로 변경<ul>
<li>775: 소유자와 그룹 사용자는 해당 파일에 대해 읽기, 쓰기, 실행 권한을 가지며 그 외 사용자는 읽기, 실행 권한을 가짐</li>
</ul>
</li>
</ul>
<pre><code class="language-HCL">  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;while [ ! -d /var/www/html ]; do sleep 5; done&quot;,
      &quot;echo &#39;/var/www/html is ready&#39;&quot;,
      &quot;sudo chown -R ec2-user:apache /var/www/html&quot;,
      &quot;sudo chmod -R 775 /var/www/html&quot;
    ]
  }

  provisioner &quot;file&quot; {
    source = &quot;${path.module}/index.php&quot;
    destination = &quot;/var/www/html/index.php&quot;
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Application Load Balancer를 통한 이중화 네트워크 구성 (1)]]></title>
            <link>https://velog.io/@dev_chori/Application-Load-Balancer%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%9D%B4%EC%A4%91%ED%99%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%84%B1-1</link>
            <guid>https://velog.io/@dev_chori/Application-Load-Balancer%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%9D%B4%EC%A4%91%ED%99%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%84%B1-1</guid>
            <pubDate>Wed, 01 Jan 2025 11:26:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/aws-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B8%B0%EB%B3%B8">스스로 구축하는 AWS 클라우드 인프라 - 기본편</a>을 수강하며 AWS 인프라를 Terraform으로 작성한 내용입니다.</p>
</blockquote>
<hr>
<h2 id="target-group-생성">Target group 생성</h2>
<ul>
<li>Load Balancer는 트래픽을 받으면 리스너를 통해 Target group으로 전달</li>
<li>Target group은 Load Balancer를 통해 들어온 트래픽을 받을 대상</li>
<li><code>target_group.tf</code> 파일을 생성하고 아래와 같이 작성</li>
</ul>
<pre><code class="language-HCL"># Target group
resource &quot;aws_vpclattice_target_group&quot; &quot;main&quot; {
  name = &quot;alb-public-tf-${var.vpc_name}&quot;
  type = &quot;INSTANCE&quot;

  config {
    vpc_identifier = aws_vpc.main.id

    port = 80
    protocol = &quot;HTTP&quot;
    protocol_version = &quot;HTTP1&quot;

    health_check {
      enabled = true
      health_check_interval_seconds = 30
      health_check_timeout_seconds = 5
      protocol = &quot;HTTP&quot;
      path = &quot;/&quot;
      healthy_threshold_count = 5
      unhealthy_threshold_count = 2
      matcher {
        value = &quot;200&quot;
      }
    }
  }

  tags = {
    Name = &quot;alb-public-tf-${var.vpc_name}&quot;
  }
}

# Register targets with Target group
resource &quot;aws_vpclattice_target_group_attachment&quot; &quot;public_ec2&quot; {
  count = length(var.cidr_numeral_private)
  target_group_identifier = aws_vpclattice_target_group.main.id

  target {
    id = element(aws_instance.public_ec2.*.id, count.index)
  }
}</code></pre>
<ul>
<li>여기서 설정하는 프로토콜과 포트는 Application Load Balancer와 타겟이 되는 EC2 인스턴스 사이의 통신에 대한 것</li>
<li>EC2 인스턴스들이 지정된 프로토콜과 포트로 오는 요청만 받아들이게 됨</li>
<li>Application Load Balancer는 Health Check를 통해 타겟이 되는 인스턴스의 상태가 정상적인지 지속적으로 확인</li>
</ul>
<hr>
<h2 id="application-load-balancer-생성">Application Load Balancer 생성</h2>
<h3 id="security-group-만들기">Security group 만들기</h3>
<ul>
<li>Application Load Balancer가 타겟이 되는 인스턴스와 통신할 때 Security group이 사용됨</li>
<li><code>security_group.tf</code> 파일에 아래 내용 추가</li>
</ul>
<pre><code class="language-HCL"># Security group for ALB
resource &quot;aws_security_group&quot; &quot;alb_public_sg&quot; {
  name = &quot;alb-public-sg&quot;
  description = &quot;Security group for alb&quot;
  vpc_id = aws_vpc.main.id

  tags = {
    Name = &quot;alb-public-sg&quot;
  }
}

# Inbound rule allowing HTTP for ALB
resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;allow_http_for_alb&quot; {
  security_group_id = aws_security_group.alb_public_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  from_port = 80
  ip_protocol = &quot;tcp&quot;
  to_port = 80
}

# Outbound rule allowing all traffic for ALB
resource &quot;aws_vpc_security_group_egress_rule&quot; &quot;allow_all_outbound_traffic_for_alb&quot; {
  security_group_id = aws_security_group.alb_public_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  ip_protocol = &quot;-1&quot;
}</code></pre>
<h3 id="application-load-balancer-만들기">Application Load Balancer 만들기</h3>
<pre><code class="language-HCL">resource &quot;aws_lb&quot; &quot;alb_public&quot; {
  name = &quot;alb-public&quot;
  load_balancer_type = &quot;application&quot;
  internal = false
  ip_address_type = &quot;ipv4&quot;

  # Indicate which subnet in the availability zone will receive traffic
  dynamic &quot;subnet_mapping&quot; {
    for_each = toset(aws_subnet.public)
    content {
      subnet_id = subnet_mapping.value.id
    }
  }

  security_groups = [aws_security_group.alb_public_sg.id]
}

resource &quot;aws_lb_listener&quot; &quot;alb_public_listener&quot; {
  load_balancer_arn = aws_lb.alb_public.arn
  port = 80
  protocol = &quot;HTTP&quot;

  default_action {
    type = &quot;forward&quot;
    target_group_arn = aws_lb_target_group.alb_public_tg.arn
  }

  tags = {
    Name = &quot;alb-public&quot;
  }
}</code></pre>
<ul>
<li>리스너에서 설정하는 프로토콜과 포트는 외부 또는 클라이언트와 Application Load Balancer 사이의 통신에 대한 것</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[EFS를 통한 네트워크 파일 시스템 구성]]></title>
            <link>https://velog.io/@dev_chori/EFS%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@dev_chori/EFS%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Tue, 31 Dec 2024 04:32:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.inflearn.com/course/aws-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B8%B0%EB%B3%B8">스스로 구축하는 AWS 클라우드 인프라 - 기본편</a>을 수강하며 AWS 인프라를 Terraform으로 작성한 내용입니다.</p>
</blockquote>
<hr>
<h2 id="security-group-생성">Security group 생성</h2>
<ul>
<li>EC2 인스턴스와 EFS 사이에 트래픽이 이동할 수 있도록 Security group이 있어야 함</li>
<li>EC2 인스턴스와 EFS의 마운트 타겟이 파일 시스템과 관련된 프로토콜 및 포트에 대해 통신을 허용할 수 있도록 하는 규칙이 있어야 함</li>
<li><code>security_group.tf</code> 파일에 아래 내용을 추가</li>
</ul>
<pre><code class="language-HCL"># Security group for EFS
resource &quot;aws_security_group&quot; &quot;efs_sg&quot; {
  name = &quot;efs-sg&quot;
  description = &quot;Security group for efs&quot;
  vpc_id = aws_vpc.main.id

  tags = {
    Name = &quot;efs-sg&quot;
  }
}

# Inbound rule allowing NFS
resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;allow_nfs&quot; {
  security_group_id = aws_security_group.efs_sg.id
  # Fill in below value with security group whose instance will connect to EFS
  referenced_security_group_id = aws_security_group.public_ec2_sg.id
  from_port = 2049
  ip_protocol = &quot;tcp&quot;
  to_port = 2049
}

# Outbound rule allowing all traffic for EFS
resource &quot;aws_vpc_security_group_egress_rule&quot; &quot;allow_all_outbound_traffic_for_nfs&quot; {
  security_group_id = aws_security_group.efs_sg.id
  cidr_ipv4 = &quot;0.0.0.0/0&quot;
  ip_protocol = &quot;-1&quot;
}</code></pre>
<ul>
<li>특정 보안 그룹에서 발생하는 트래픽을 허용하기 위해 <code>referenced_security_group_id</code> 지정</li>
<li>트래픽 유형은 <code>NFS</code>로 <code>2049</code> 포트 사용</li>
<li>종합하면 지정한 보안 그룹에서 나오는 NFS 트래픽을 EFS의 마운트 타겟이 허용하겠다는 의미</li>
</ul>
<hr>
<h2 id="efs-생성">EFS 생성</h2>
<ul>
<li><code>efs.tf</code> 파일을 만들고 아래와 같이 작성</li>
</ul>
<pre><code class="language-HCL"># EFS
resource &quot;aws_efs_file_system&quot; &quot;main&quot; {
  performance_mode = &quot;generalPurpose&quot;
  throughput_mode = &quot;bursting&quot;

  lifecycle_policy {
    transition_to_ia = &quot;AFTER_30_DAYS&quot;
  }

  lifecycle_policy {
    transition_to_primary_storage_class = &quot;AFTER_1_ACCESS&quot;
  }

  tags = {
    Name = &quot;vpc-efs-${var.vpc_name}&quot;
  }
}

# EFS backup policy
resource &quot;aws_efs_backup_policy&quot; &quot;efs_backup_policy&quot; {
  file_system_id = aws_efs_file_system.main.id

  backup_policy {
    status = &quot;DISABLED&quot;
  }
}

# EFS mount target
resource &quot;aws_efs_mount_target&quot; &quot;public_subnet&quot; {
  count = length(var.cidr_numeral_public)
  file_system_id = aws_efs_file_system.main.id
  subnet_id = element(aws_subnet.public.*.id, count.index)
  security_groups = [ aws_security_group.efs_sg.id ]
}</code></pre>
<ul>
<li>EFS 파일 시스템이 마운트될 수 있도록 <code>aws_efs_mount_target</code> 리소스 사용</li>
</ul>
<hr>
<h2 id="efs를-하나의-ec2-인스턴스에-마운트">EFS를 하나의 EC2 인스턴스에 마운트</h2>
<ul>
<li>EC2 인스턴스에 접속해서 아래 명령어를 입력하면 파일 시스템의 디스크 공간을 확인할 수 있음</li>
</ul>
<pre><code class="language-shell">df -f</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/37249569-5f7f-46e5-827c-1048612e73c5/image.png" alt=""></p>
<ul>
<li>아직 EFS가 마운트되지 않았음</li>
<li>EFS를 마운트하려면 NFS 클라이언트 또는 EFS 마운트 헬퍼를 사용<ul>
<li>본 예제에서는 EFS 마운트 헬퍼를 이용</li>
</ul>
</li>
<li>아래 명령어로 EFS 마운트 헬퍼 설치</li>
</ul>
<pre><code class="language-shell">sudo yum install amazon-efs-utils -y</code></pre>
<ul>
<li><code>/var/www/html/efs</code>를 마운트 포인트로 설정</li>
</ul>
<pre><code class="language-shell">cd     /var/www/html
mkdir efs</code></pre>
<ul>
<li>AWS 콘솔에서 EFS 리소스를 찾고 연결 클릭</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/ac31405b-b0b0-4b77-91d4-88d3ec4f6deb/image.png" alt=""></p>
<ul>
<li>EFS 마운트 헬퍼를 사용하기 위한 명령어를 복사</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/4dbebee4-c01d-4ac1-816d-ae40d2801430/image.png" alt=""></p>
<ul>
<li>EC2 인스턴스에서 명령어를 붙여넣고 실행</li>
</ul>
<pre><code class="language-shell">sudo mount -t efs -o tls fs-071eafc94bc1e05df:/ efs</code></pre>
<ul>
<li>다시 파일 시스템의 디스크 공간을 확인하면 마운트 포인트로 설정한 경로로 EFS가 마운트가 되어 있는 것을 확인할 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/332edece-ff60-403b-8b8c-5d830d8801ba/image.png" alt=""></p>
<ul>
<li>이전 예제에서 S3 버켓의 정적 웹사이트 호스팅에 사용했던 파일을 EFS로 다운로드</li>
</ul>
<pre><code class="language-shell">cd efs
sudo wget https://s3-web-hosting-chori.s3.us-east-1.amazonaws.com/car.jpg</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/9c6090df-6ffc-4733-acc7-c0c5164e0e13/image.png" alt=""></p>
<ul>
<li>웹 브라우저에서 <code>ip 주소/efs/mycar.html</code>로 접속하면 웹페이지가 표시됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/5a7cc77f-6879-418e-a14b-9f87c055753c/image.png" alt=""></p>
<hr>
<h2 id="efs를-또-다른-ec2-인스턴스에-마운트">EFS를 또 다른 EC2 인스턴스에 마운트</h2>
<ul>
<li>EC2 인스턴스에 접속하여 파일 시스템의 디스크 공간을 확인</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/f6c58cda-3649-4918-88df-6e378df68abd/image.png" alt=""></p>
<ul>
<li>EFS 마운트 헬퍼 설치 후 마운트 포인트 설정</li>
</ul>
<pre><code class="language-shell">sudo yum install amazon-efs-utils -y
cd     /var/www/html
mkdir efs</code></pre>
<ul>
<li>마운트 헬퍼를 사용하기 위한 명령어 입력</li>
</ul>
<pre><code class="language-shell">sudo mount -t efs -o tls fs-071eafc94bc1e05df:/ efs</code></pre>
<ul>
<li><code>efs</code> 디렉토리로 이동하면 다른 EC2 인스턴스에서 다운로드했던 파일을 확인할 수 있음</li>
</ul>
<pre><code class="language-shell">cd efs
ls -al</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/1960d9b0-afdc-48ab-99b3-e086ebbe704b/image.png" alt=""></p>
<ul>
<li>마찬가지로 웹 브라우저에서 <code>ip 주소/efs/mycar.html</code>로 접속하면 웹페이지가 표시됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/6dbe44cf-4839-4f2b-9c81-d695a06d51f3/image.png" alt=""></p>
<hr>
<h2 id="efs에-있는-파일-수정">EFS에 있는 파일 수정</h2>
<ul>
<li>하나의 EC2 인스턴스에서 <code>mycar.html</code> 파일을 수정</li>
</ul>
<pre><code class="language-shell">sudo vi mycar.html</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/d5303cec-afae-4f1b-abf7-7ac7460e5400/image.png" alt=""></p>
<ul>
<li>웹 브라우저로 접속하면 웹페이지가 수정되어 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/42c73117-ce91-4a5b-ade7-b64b1208c10c/image.png" alt=""></p>
<ul>
<li>또 다른 EC2 인스턴스의 ip 주소로 접속해도 수정된 웹페이지가 반영되어 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_chori/post/88ca3567-7db9-49ad-93e2-a2571a6724dc/image.png" alt=""></p>
<ul>
<li>즉, 네트워크 파일 시스템이 두 개의 EC2 인스턴스에 동시에 연결되어 있다는 것을 확인할 수 있음</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>