<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>기록용 블로그</title>
        <link>https://velog.io/</link>
        <description>다 아는 건 아니어도 바라는 대로</description>
        <lastBuildDate>Wed, 28 May 2025 00:54:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>기록용 블로그</title>
            <url>https://velog.velcdn.com/images/hsh_124/profile/890e8125-531d-4ef5-bead-71d31814610c/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 기록용 블로그. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hsh_124" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[DB] Tibero to Tibero DB Link]]></title>
            <link>https://velog.io/@hsh_124/DB-Tibero-to-Tibero-DB-Link</link>
            <guid>https://velog.io/@hsh_124/DB-Tibero-to-Tibero-DB-Link</guid>
            <pubDate>Wed, 28 May 2025 00:54:47 GMT</pubDate>
            <description><![CDATA[<h3 id="db-link">DB Link</h3>
<p>DB Link 는 데이터베이스에서 네트워크상의 다른 데이터베이스에 접속하기 위하여 접속 설정을 정의하는 객체이다.
분산된 데이터를 다루기 편리하지만 실시간 트랜잭션이 많은 시스템에서는 장애가 발생할 수 있다. (Link를 통해 접속된 데이터베이스의 세션과 Lock 이 증가할 수 있으므로)
Long Type, CLOB, BLOB 타입을 Link를 통해 DML 작업할 경우 오류가 발생할 가능성이 있다.</p>
<h3 id="tibero-to-tibero">Tibero to Tibero</h3>
<p>Tibero to Tibero는 접속하려고 하는 Tibero의 IP 주소, 포트 번호, DB Name 내용을 확인하고 DB Link를 설정할 Tibero의 네트워크 설정 파일 (tbdsn.tbr) 에 설정한다.</p>
<pre><code class="language-json"># DB Link를 생성할 Tibero 의 정보

tibero=(
    (INSTANCE=(HOST=localhost)
              (PORT=6829)
              (DB_NAME=tibero)
    )
)

# 접속할 Tibero 의 정보

tibero_a=(
    (INSTANCE=(HOST=Node IP)
              (PORT=설정할 PORT)
              (DB_NAME=tibero)
    )
)</code></pre>
<h4 id="대상-서버-연결-확인">대상 서버 연결 확인</h4>
<p>접속하려는 Tibero(tibero_a)로 접속한 것을 확인한다.</p>
<pre><code class="language-bash">tbsql sys/tibero@tibero_a</code></pre>
<h4 id="db-link-object-생성">DB Link Object 생성</h4>
<p>DB Link 를 생성할 계정의 sql 에 접속하여 DB Link Object를 생성한다.</p>
<pre><code class="language-SQL">create database link &lt;DB LINK 명&gt; connect to &lt;접속 사용자 ID&gt; identified by &lt;접속 패스워드&gt; using &lt;접속에 사용할 alias&gt;;</code></pre>
<ul>
<li>DB LINK 명 : 생성할 DB Link Object 이름</li>
<li>접속 사용자 ID : 대상 DB 서버에 접속할 사용자 이름</li>
<li>접속 패스워드 : 대상 DB 서버에 접속할 패스워드</li>
<li>접속에 사용할 alias : Source DB의 tbdsn.tbr에 설정된 alias 이름</li>
</ul>
<p>DB Link 생성 예시는 다음과 같다.</p>
<pre><code class="language-SQL">create database link T7Link connect to TEST_DB identified by &#39;TEST_DB&#39; using &#39;tibero_a&#39;;</code></pre>
<p><em>DB Link를 생성하기 위해서는 CREATE DATABASE LINK 또는 CREATE PUBLIC DATABASE LINK 권한이 필요하다.</em></p>
<h4 id="sql-실행">SQL 실행</h4>
<p>생성이 완료되면 &lt; @Link명 &gt; 을 붙여 사용한다.</p>
<pre><code class="language-SQL">select * from dual@T7Link</code></pre>
<p>사용중인 Link 의 이름으로 알 수 없는 경우 다음과 같이 View 를 조회한다.</p>
<pre><code class="language-SQL">select * from user_db_links;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[cgroup 을 통해 컨테이너의 리소스 확인하기]]></title>
            <link>https://velog.io/@hsh_124/cgroup-%EC%9D%84-%ED%86%B5%ED%95%B4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%EB%A6%AC%EC%86%8C%EC%8A%A4-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hsh_124/cgroup-%EC%9D%84-%ED%86%B5%ED%95%B4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%EB%A6%AC%EC%86%8C%EC%8A%A4-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 25 Sep 2024 02:40:03 GMT</pubDate>
            <description><![CDATA[<p>kubectl top 명령어를 통해 pod의 메모리와 CPU를 확인할 수 있으면 좋겠지만, 모종의 이유로 kubectl top 을 설치하지 못해 kubectl top 명령어 없이도 memory와 CPU 등 리소스를 확인할 수 있는 방법을 알아보게 되었다. 다행히 cgroup 디렉터리를 이용하여 확인할 수 있는 방법이 있었다. <em>루트 권한으로의 접근이 가능하다면,</em> 해당 방법으로 확인하는 것이 좋겠다.</p>
<h3 id="확인할-수-있는-이유">확인할 수 있는 이유</h3>
<p>컨테이너는 linux의 cgroup 과 namespace 기능을 통해 격리된 환경이기 때문이다. cgroup은 프로세스 그룹별로 시스템 리소스를 제한하고 모니터링할 수 있게 한다.
cgroup은 컨테이너별로 그룹의 리소스 사용량과 제한 정보를 파일 시스템 형태로 노출한다. 일반적으로 <code>sys/fs/cgroup</code> 디렉토리에서 확인할 수 있다 (컨테이너 안에서도).</p>
<p>cgroup 은 cgroup v1 과 cgroup v2 가 사용되고 있다. 이에 따라 경로와 파일 명칭이 다를 수 있다.</p>
<ul>
<li><strong>cgroup v1</strong> : /sys/fs/cgroup/memory/memory.usage_in_bytes</li>
<li><strong>cgroup v2</strong> : /sys/fs/cgroup/memory.current</li>
</ul>
<blockquote>
<p><strong>cgroup v1과 cgroup v2란?</strong>
cgroup 은 버전에 따라 시스템 자원을 관리하는 방식과 구조에 차이가 있다. 
cgroup v1 은 각 자원 컨트롤러 (메모리, CPU) 등이 별도의 계층 구조를 가져 독립적으로 운영된다. 유연성을 제공하지만, 여러 자원을 동시에 관리할 때는 복잡성이 증가할 수 있다.
cgroup v2는 모든 자원 컨트롤러가 하나의 계층 구조를 공유한다. 컨트롤러 간의 일관성이 보장되고 관리가 용이하지만, 호환성의 이슈가 있을 수 있다.
어떤 버전의 cgroup 을 사용하는지는, <code>mount | grep cgroup2</code> 을 통해 확인할 수 있다. 출력에 cgroup2가 있으면 cgroup v2를 사용하는 것이다.</p>
</blockquote>
<p>컨테이너 런타임은 이 cgroup을 활용하여 컨테이너의 리소스 사용을 관리하므로, 해당 방법을 통해 컨테이너의 리소스를 정확하게 추적할 수 있다.</p>
<h4 id="확인-방법">확인 방법</h4>
<p><strong>1. 컨테이너 안에서 확인</strong>
컨테이너에 안에 접속하여 <code>/sys/fs/cgroup/memory</code> 폴더의 memory.usage_in_bytes 파일을 확인한다.</p>
<pre><code class="language-shell"># cgroup v1
cat /sys/fs/cgroup/memory/memory.usage_in_bytes

# cgroup v2
cat /sys/fs/cgroup/memory.current</code></pre>
<p><strong>2. 접속한 node에서 확인</strong></p>
<ol>
<li>kubernetes pod 혹은 container 가 위치한 node에 접속한다.</li>
<li>컨테이너의 PID (Process ID) 를 확인한다. (docker 의 경우 <code>docker inspect --format &#39;{{.State.Pid}}&#39; &lt;컨테이너 ID&gt;</code> , containerd를 사용하는 경우 <code>crictl inspect --output go-template --template &#39;{{.info.pid}}&#39; &lt;컨테이너 ID&gt;</code>) <ul>
<li>kubernetes의 경우 <code>kubectl describe &lt;pod&gt; -n &lt;namespace&gt;</code> 명령어를 사용하여 사용하는 컨테이너의 ID를 확인할 수 있다.</li>
</ul>
</li>
<li>컨테이너의 PID가 확인되었으면 해당 process 의 경로에서 PID의 cgroup을 찾는다. <code>cat /proc/&lt;PID&gt;/cgroup</code><ul>
<li>cgroup v1 : 각 컨트롤러 별로 경로가 표시된다.</li>
<li>cgroup v2 : 단일 계층 구조이므로 <code>0::/</code> 형태로 경로가 표시된다.<pre><code class="language-shell">cat /proc/&lt;PID&gt;/cgroup # cgroup v1의 경우
11:memory:/user.slice
10:cpu,cpuacct:/user.slice
9:cpuset:/
8:blkio:/user.slice
7:net_cls,net_prio:/
6:freezer:/
5:devices:/user.slice
4:hugetlb:/
3:perf_event:/
2:pids:/user.slice
1:name=systemd:/user.slice/user-1000.slice/session-2.scope
</code></pre>
</li>
</ul>
</li>
</ol>
<p>cat /proc/87357/cgroup # cgroup v2의 경우
0::/user.slice/user-1000.slice/session-2.scope</p>
<p>```
4. <code>/sys/fs/cgroup</code> 에서 위에서 확인한 경로로 이동한다. 경로를 그대로 <code>/sys/fs/cgroup</code> 아래에 결합하여 사용한다. ( e.g. <code>/sys/fs/cgroup/user.slice/user-1000.slice/session-2.scope</code> 
5. <code>memory.usage</code> 등의 파일을 사용하여 사용중인 메모리를 확인한다.</p>
<ul>
<li><strong>cgroup v1</strong> 의 경우 : <code>/sys/fs/cgroup/memory/&lt;cgroup 경로&gt;/memory.usage_in_bytes</code></li>
<li><strong>cgroup v2</strong> 의 경우 : <code>/sys/fs/cgroup/&lt;cgroup 경로&gt;/memory.current</code></li>
</ul>
<p>메모리 뿐만 아니라 CPU 사용량 (<code>cpuacct.usage</code> 혹은 <code>cpu.stat</code>) 역시 확인할 수 있다. 각 메모리 사용량 파일의 값은 바이트(Byte) 단위이다.</p>
<p>cgroup v1에서는 <code>cpuacct.usage</code> 파일에서 나노초 단위로 확인한다. cgroup v2 에서는 <code>cpu.stat</code> 파일에서 <code>usage_usec</code> 값을 통해 마이크로초 단위로 확인한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 4일차 TIL + Python의 명시적 형변환]]></title>
            <link>https://velog.io/@hsh_124/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%9D%BC%EC%B0%A8-TIL-Python%EC%9D%98-%EB%AA%85%EC%8B%9C%EC%A0%81-%ED%98%95%EB%B3%80%ED%99%98</link>
            <guid>https://velog.io/@hsh_124/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%9D%BC%EC%B0%A8-TIL-Python%EC%9D%98-%EB%AA%85%EC%8B%9C%EC%A0%81-%ED%98%95%EB%B3%80%ED%99%98</guid>
            <pubDate>Thu, 25 Jul 2024 08:40:16 GMT</pubDate>
            <description><![CDATA[<h3 id="내용-정리">내용 정리</h3>
<h4 id="개요">개요</h4>
<p>Python에서 형 변환은 다양한 데이터 유형 간에 값을 변환하는 과정이다. 암시적 형변환과 명시적 형변환 두 가지 방식이 있다. </p>
<h4 id="암시적-형-변환">암시적 형 변환</h4>
<p>Python 이 자동으로 데이터 유형을 변환한다. 이는 보통 데이터 손실이 없을 때 사용한다.</p>
<pre><code class="language-python">num_int = 123
num_float = 1.23

# num_int는 자동으로 float로 변환됨
result = num_int + num_float
print(&quot;Result:&quot;, result)
print(&quot;Type of result:&quot;, type(result))</code></pre>
<h4 id="명시적-형-변환">명시적 형 변환</h4>
<p>프로그래머가 직접 데이터 유형을 변환한다. Python은 다양한 형 변환 함수를 제공한다.</p>
<p><strong>주요 형 변환 함수</strong> </p>
<ul>
<li>int(): 다른 데이터 타입을 정수형으로 변환</li>
<li>float(): 다른 데이터 타입을 실수형으로 변환</li>
<li>str(): 다른 데이터 타입을 문자열로 변환</li>
<li>list(): 다른 데이터 타입을 리스트로 변환</li>
<li>tuple(): 다른 데이터 타입을 튜플로 변환</li>
<li>set(): 다른 데이터 타입을 집합으로 변환</li>
<li>dict(): 키-값 쌍의 순차열을 사전으로 변환</li>
</ul>
<pre><code class="language-python">num_str = &quot;456&quot;
num_int = int(num_str)
print(&quot;Converted to int:&quot;, num_int)
print(&quot;Type of num_int:&quot;, type(num_int))

float_str = &quot;3.14&quot;
num_float = float(float_str)
print(&quot;Converted to float:&quot;, num_float)
print(&quot;Type of num_float:&quot;, type(num_float))

# List to tuple
list_example = [1, 2, 3]
tuple_example = tuple(list_example)
print(&quot;Converted to tuple:&quot;, tuple_example)
print(&quot;Type of tuple_example:&quot;, type(tuple_example))

# Tuple to list
tuple_example = (1, 2, 3)
list_example = list(tuple_example)
print(&quot;Converted to list:&quot;, list_example)
print(&quot;Type of list_example:&quot;, type(list_example))</code></pre>
<h3 id="회고">회고</h3>
<p>python 재활 중이라는 마음가짐으로 하면 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 3일차 TIL + Shell 기초]]></title>
            <link>https://velog.io/@hsh_124/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-Shell-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@hsh_124/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-Shell-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Wed, 24 Jul 2024 06:15:20 GMT</pubDate>
            <description><![CDATA[<h3 id="내용-정리">내용 정리</h3>
<h4 id="개요">개요</h4>
<p>#! /bin/bash 를 가장 맨 처음에 넣어 /bin/bash 쉘을 사용한다는 것을 알려준다.</p>
<p>변수를 선언할 때는 <code>=</code> 기호를 사용하며, 변수 이름과 <code>=</code> 사이에 공백이 없어야 한다.</p>
<p>변수를 사용할 때는 <code>$</code> 기호를 앞에 붙여야 한다.</p>
<p>환경 변수를 설정하면 시스템 전체에서 변수를 사용할 수 있다. <code>export</code> 명령어를 사용해서 설정한다. <code>env</code> 명령어를 사용하면 환경 변수의 목록을 볼 수 있다.</p>
<p>문자열은 작은따옴표 혹은 큰따옴표를 이용해서 표현한다.</p>
<p><code>expr</code> 명령어를 사용하거나 <code>(( ))</code> 구문을 사용하여 간단한 산술 연산을 수행할 수 있다.</p>
<p>배열은 이와 같이 설정한다. <code>my_array=(array1, array2, array3</code>  특정 요소로 접근하기 위해서는 <code>${array[index]}</code> 구문을 사용한다.</p>
<h3 id="비교-표현식">비교 표현식</h3>
<ul>
<li>-gt : greater. 크다</li>
<li>-ge: greater or equal. 크거나 같다</li>
<li>-eq: equal. 같다</li>
<li>-ne: not equal. 같지 않다</li>
<li>-le: little or equal. 작거나 같다</li>
<li>-lt: little. 같다</li>
</ul>
<h3 id="조건문">조건문</h3>
<p>if문</p>
<pre><code class="language-bash">if [ $a -gt $b ]; then
    echo &quot;$a is greater than $b&quot;
elif [ $a -eq $b ]; then
    echo &quot;$a is equal to $b&quot;
else
    echo &quot;$a is less than $b&quot;
fi</code></pre>
<p>case문</p>
<pre><code class="language-bash">case $input in
    start)
        echo &quot;Starting&quot;
        ;;
    stop)
        echo &quot;Stopping&quot;
        ;;
    *)
        echo &quot;Unknown command&quot;
        ;;
esac</code></pre>
<h3 id="반복문">반복문</h3>
<p>for문</p>
<pre><code class="language-bash">for i in {1..5}; do
    echo &quot;Welcome $i times&quot;
done</code></pre>
<p>while문 - executes the commands inside it as long as the condition is true</p>
<pre><code class="language-bash">while [ $i -le 5 ]; do
    echo &quot;Welcome $i times&quot;
    i=$((i + 1))
done</code></pre>
<p>until문 - executing the commands inside it until the condition becomes true</p>
<pre><code class="language-bash">until [ $i -gt 5 ]; do
    echo &quot;Welcome $i times&quot;
    i=$((i + 1))
done</code></pre>
<h3 id="함수">함수</h3>
<p>쉘스크립트에서 반복되는 작업을 모듈화하고 코드의 재사용성을 높임</p>
<p>함수 정의</p>
<pre><code class="language-bash">function_name() {
    # 함수의 내용
}

or

function function_name {
    # 함수의 내용
}</code></pre>
<p>함수 호출 - 함수를 호출하려면 함수 이름을 사용하고, 필요한 경우 인자를 전달</p>
<pre><code class="language-bash">function_name arg1 arg2</code></pre>
<h3 id="위치-매개변수와-특수-변수">위치 매개변수와 특수 변수</h3>
<p>함수 내에서, 인자는 위치 매개변수 (<strong><code>$1</code></strong>, <strong><code>$2</code></strong>, ...)를 통해 접근할 수 있다. <strong><code>$0</code></strong>은 스크립트의 이름을 나타내며, <strong><code>$#</code></strong>는 전달된 인자의 수를 나타낸다.</p>
<pre><code class="language-bash">greet() {
    echo &quot;Hello, $1!&quot;
}

greet &quot;World&quot;

[root@localhost ~]# ./greet.sh
Hello, World!</code></pre>
<p> <strong><code>greet</code></strong> 함수는 하나의 인자를 받아 인사말을 출력한다. <strong><code>$1</code></strong>은 함수에 전달된 첫 번째 인자를 나타낸다.</p>
<pre><code class="language-bash">greet() {
        echo &quot;Hello, $1! Nice to meet you, $3, $2!&quot;
}

greet &quot;World&quot; &quot;Min&quot; &quot;Sin&quot;

[root@localhost ~]# ./greet.sh
Hello, World! Nice to meet you, Sin, Min!</code></pre>
<h3 id="쓸만한-명령어들-정리">쓸만한 명령어들 정리</h3>
<ul>
<li>삭제할 때<ul>
<li><code>ls *.파일명</code> 으로 실행하고 <code>rm !$</code>로  ls로 불러온 다른 파일들 삭제하기</li>
</ul>
</li>
<li><code>Crtl+R</code>로 증분검색 해서 편하게 불러오기</li>
<li>명령어에서 오타난 부분 바꿀때는 그냥 <code>^오타^수정함</code>으로 하면됨</li>
</ul>
<pre><code class="language-bash">[root@localhost ~]# lsa
-bash: lsa: command not found
[root@localhost ~]# ^lsa^ls
ls
BookNetwork_GKE  go                                            infra          minikube-linux-amd64
anaconda-ks.cfg  google-cloud-cli-461.0.0-linux-x86_64.tar.gz  kubectl        practice
cri-dockerd      google-cloud-sdk                              linux-amd64    shell_study
db.pem           helm-v3.11.1-linux-amd64.tar.gz               liveness.yaml
</code></pre>
<ul>
<li>두 디렉터리를 오며가며 반복할 때는 <code>cd -</code> 를 사용</li>
</ul>
<h3 id="회고">회고</h3>
<p>피곤하당</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 2일차 TIL + python의 lambda 사용법]]></title>
            <link>https://velog.io/@hsh_124/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%9D%BC%EC%B0%A8-TIL-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%AA%A8%EB%8D%B8-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@hsh_124/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%9D%BC%EC%B0%A8-TIL-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%AA%A8%EB%8D%B8-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Tue, 23 Jul 2024 08:24:15 GMT</pubDate>
            <description><![CDATA[<h3 id="내용-정리">내용 정리</h3>
<h4 id="개요">개요</h4>
<p>Python의 lambda 함수는 간단한 익명 함수(anonymous function)를 작성할 때 사용된다. lambda 함수는 단일 표현식을 가진 함수로, 일반적인 함수 정의보다 더 간결하게 작성할 수 있다.</p>
<pre><code class="language-python">lambda 인자1, 인자2, ... : 표현식</code></pre>
<ul>
<li>lambda 키워드로 시작한다.</li>
<li>인자 리스트가 뒤따른다.</li>
<li>콜론 : 뒤에 표현식이 온다. 이 표현식의 결과가 함수의 반환값이 된다.</li>
</ul>
<ol>
<li><p><strong>기본 사용법</strong></p>
<pre><code class="language-python">add = lambda x, y: x + y
print(add(2, 3))  # 출력: 5</code></pre>
</li>
<li><p><strong>리스트에서 사용</strong></p>
<pre><code class="language-python">add = lambda x, y: x + y
print(add(2, 3))  # 출력: 5</code></pre>
</li>
<li><p><strong>고차 함수에서 사용</strong></p>
<pre><code class="language-python">def apply_function(f, value):
 return f(value)
</code></pre>
</li>
</ol>
<p>result = apply_function(lambda x: x * 2, 5)
print(result)  # 출력: 10</p>
<pre><code>
4. **맵(map)과 필터(filter) 함수에서 사용**
```python
# map 사용 예제
numbers = [1, 2, 3, 4]
squared = map(lambda x: x ** 2, numbers)
print(list(squared))  # 출력: [1, 4, 9, 16]

# filter 사용 예제
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers))  # 출력: [2, 4]</code></pre><ol start="5">
<li><p><strong>리스트 컴프리헨션에서 조건으로 사용</strong></p>
<pre><code class="language-python">numbers = [1, 2, 3, 4, 5, 6]
even_squares = [x ** 2 for x in numbers if (lambda x: x % 2 == 0)(x)]
print(even_squares)  # 출력: [4, 16, 36]</code></pre>
</li>
<li><p><strong>정렬의 키 함수로 사용</strong></p>
<pre><code class="language-python">points = [(1, 2), (3, 3), (1, -1)]
points_sorted_by_y = sorted(points, key=lambda point: point[1])
print(points_sorted_by_y)  # 출력: [(1, -1), (1, 2), (3, 3)]</code></pre>
</li>
<li><p><strong>데이터 처리 파이프라인</strong></p>
<pre><code class="language-python">data = [1, 2, 3, 4, 5]
processed_data = map(lambda x: x * 2, filter(lambda x: x % 2 == 0, data))
print(list(processed_data))  # 출력: [4, 8]</code></pre>
</li>
</ol>
<h3 id="회고">회고</h3>
<p>평범한 날이 계속됐으면 좋겠습니다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크 기초 정리]]></title>
            <link>https://velog.io/@hsh_124/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B8%B0%EC%B4%88-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@hsh_124/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B8%B0%EC%B4%88-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 07 Jun 2024 02:08:09 GMT</pubDate>
            <description><![CDATA[<h2 id="네트워크">네트워크</h2>
<p>그물을 뜻하는 Net와 Work의 합성어
분산되어 있는 컴퓨터를 통신망으로 연결한 것</p>
<h3 id="네트워크-기본-개념">네트워크 기본 개념</h3>
<p>컴퓨터는 하드웨어 / 소프트웨어로 나뉨</p>
<p>소프트웨어는 OS 나 응용 프로그램 등으로 구성되어 있음
이런 네트워크를 이해하기 위해선 프로세스를 이해해야 함</p>
<p>운영체제 역할을 크게 두 가지로 구분하면</p>
<ol>
<li>하드웨어 자원의 할당</li>
<li>프로세스의 관리</li>
</ol>
<p>프로세스: 운영체제로부터 자원을 할당 받는 작업의 단위
스레드: 할당 받은 자원을 실제로 이용하는 실행의 단위</p>
<p>프로세스 내에는 여러 스레드가 존재할 수 있다. 프로세스는 완전히 독립된 실행 객체임. 독립되어 있는 만큼 다른 프로세스의 영향을 받지 않지만, 별도의 방법 없이는 서로 통신이 불가능하다.</p>
<p>프로세스간 통신: IPC, 뮤텍스, 세마포어 등 메모리 공유 방법엔 여러가지가 있다.</p>
<h3 id="네트워크에-기반을-둔-소켓">네트워크에 기반을 둔 소켓</h3>
<p>소켓: 두 개의 프로그램 간 양방향 통신의 하나의 엔드 포인트</p>
<p>소켓은 프로세스와 시스템의 가장 기초적인 부분. 프로세스 간의 통신을 가능하게 한다.</p>
<p>데이터 교환을 위해 양쪽 프로세스에서 임의의 포트를 정하고, 해당 포트간의 연결을 통해 데이터를 주고받는다.</p>
<p>이러한 소켓 통신은 네트워크의 기본이 된다.</p>
<ul>
<li>서버와 클라이언트
서버: 서비스를 제공하는 컴퓨터
클라이언트: 서비스를 사용하는 컴퓨터</li>
</ul>
<p>서버는 특정 포트가 바인딩된 소켓을 하나 가지고 있다. 이를 통해 listen() 을 하며 클라이언트의 연결을 기다린다.</p>
<p>클라이언트는 서버의 호스트이름과 리스닝하고 있는 포트를 통해 서버와의 연결을 시도한다. 또한 서버에 연결되어 있는 동안 서버에게 자신을 식별시키기 위해 로컬 포트에 바인딩이 된다. 이러한 포트 바인딩 작업은 OS에 의해 이루어진다.</p>
<p>클라이언트가 연결 요청을 하면 서버가 accept()을 한다. 서버는 동일한 로컬 포트에 바인딩된 새로운 소켓을 연다. 예를 들어 80번으로 웹 서버가 listen()을 하고 있으면, 새로운 클라이언트가 연결요청할 때 그 클라이언트와 통신하기 위해 새로운 포트로 소켓을 연다.</p>
<p>웹서버는 80번으로 계속 리스닝을 하고 있어야 하기 때문에 클라이언트와의 연결을 별도의 포트를 통해 바인딩 하는 것이다. 동시에 80번 포트는 지속적으로 연결 요청을 받는데 사용한다.</p>
<h3 id="tcp--ip">TCP / IP</h3>
<p>초창기, 네트워크 장비는 서로 통일이 안 되어 호환이 되지 않았다. 이러한 장비들을 통일시키기 위해 네트워크를 7계층으로 나누어 통일시켰다 (OSI 7계층).</p>
<p> 이러한 7계층의 흐름 (어떤 헤더가 붙고 떨어지는지) 등을 이해하는 것이 외우는 것보다 더 중요하다.</p>
<p>HTTP 프로토콜은 HTML과 같은 리소스를 가져올 수 있게 하는 프로토콜이다. 현대 웹에서 이루어지는 모든 데이터 교환의 기초이기도 하고 클라이언트-서버 프로토콜이다.</p>
<p>인터넷에서 클라이언트 서버 간의 연결을 가능하게 하는 통신 규약이 인터넷 프로토콜 스위트 (Internet Protocol Suite) 이다. 이는 인터넷(공용망) 상에서 컴퓨터들이 정보를 주고받는 데 쓰이는 프로토콜의 모음이다.</p>
<p>인터넷 프로토콜 스위트 중에서 TCP/IP가 가장 많이 쓰이므로 TCP/IP 프로토콜 스위트라고 부르기도 한다. </p>
<p>TCP/IP는 패킷 통신 방식의 인터넷 프로토콜인 IP와 전송 조절 프로토콜인 TCP로 이루어져 있다.</p>
<p>IP는 패킷 전달 여부를 보장하지 않는다. 패킷을 보낸 순서와 받는 순서가 다를 수도 있다. 그래서 TCP는 IP 위에서 동작하며 데이터 전달을 보장하고 보낸 순서대로 패킷을 받을 수 있게 하는 시퀀스를 보장하는 역할을 한다.</p>
<p>HTTP/FTP/SMTP 등 TCP를 기반으로 한 많은 어플리케이션 프로토콜이 IP 위에서 동작하므로 TCP/IP라고 부른다.</p>
<p>네트워크는 네트워크 호스트들로 구성이 되어 있다. 이러한 호스트들은 서로 통신이 가능하다. 이러한 통신에 사용하는 프로토콜이 IP이다. IP를 이용한 통신을 하기 위해서, 즉 한 호스트에서 다른 호스트로 패킷을 전달하기 위해서는 호스트의 주소 정보가 필요하다. 주소가 있어야지 패킷을 보낼 목적지를 설정할 수 있기 때문이다. 이런 주소값이 바로 IP Address 이다.</p>
<p>IP Address는 IP 프로토콜을 사용하는 호스트의 주소 정보이다. IP Address가 있어야 패킷이 어느 주소로 갈지 알 수 있다. 예를 들어, 노드들이 직접적으로 연결되어 있지 않은 경우에는 (가장 왼쪽의 노드와 가장 오른쪽의 노드) 다른 노드들을 통해 연결되어 있는 노드들의 정보를 전달해 주어야 한다. 이 경우, 목적지 값이 제대로 설정되어 있어야 해당 호스트에 전달이 가능하다.</p>
<h4 id="tcp">TCP</h4>
<p>IP는 패킷들의 관계 (순서, 전달 보장) 등을 이해하지는 못한다. IP는 목적지를 제대로 찾아가는 것에 중점을 둔 프로토콜이다. 이에 반해 TCP는 통신하고자 하는 양쪽 단말이 준비가 되었는지를 체크하고, 데이터가 제대로 전송되었는지, 변질되지는 않았는지, 수신자가 얼마나 데이터를 받았고 빠진 부분은 없는지를 점검하는 프로토콜이다.</p>
<p>이런 정보는 TCP 헤더에 담겨있고, TCP 헤더는 SYN, ACK, FIN, RESET 등 신뢰성 보장과 흐름 제어, 혼잡 제어에 관여할 수 있는 요소들로 구성되어 있다.</p>
<p>IP 헤더가 TCP 헤더를 제외한, TCP가 실을 수 있는 데이터 크기를 세그먼트라고 부른다.</p>
<p>TCP는 네트워크 장비 간의 논리적 연결을 성립시키기 위해 3 Way Handshake 라는 방법을 사용한다. </p>
<p>3 Way Handshake는 TCP/IP 프로토콜을 사용해서 통신을 할 때 응용프로그램이 데이터를 전송하기 전 정확한 전송을 보장하기 위해 상대방 컴퓨터가 커넥션을 맺을 수 있는 상태인지 체크하고 연결을 수립하는 과정이다.</p>
<ol>
<li><p>클라이언트가 서버에 접속을 요청하는 SYN 패킷을 보낸다. 클라이언트는 SYN 패킷을 보내고 SYN, ACK 응답을 기다린다. SYN sent 상태가 된다.</p>
</li>
<li><p>서버가 클라이언트에게 요청을 수락한다는 뜻의 ACK 패킷을 보낸다. 이 때 SYN Flag가 설정된 패킷을 전송하고 다시 ACK 패킷을 받기를 기다린다. 이 때 SYN Received 상태가 된다.</p>
</li>
<li><p>마지막으로 클라이언트가 서버에 ACK을 보내고 이 이후로 연결이 수립되고 데이터를 보낼 수 있는 상태가 된다. 연결 수립 상태가 된다.</p>
</li>
</ol>
<p>3 Way Handshake 방식은 최초로 TCP 연결을 수립할 때 쓰인다. 4 Way Handshake 방식은 연결을 종료할 때 수행된다.</p>
<h3 id="사설-네트워크와-네트워크-확장">사설 네트워크와 네트워크 확장</h3>
<p>네트워크를 다양한 관점에서 분류할 수 있다. 네트워크를 분류하는 기준은 이용자, 크기 등 다양하다.</p>
<p>서버가 클라이언트와 통신하기 위해서는 IP Address가 필요하다. 인터넷이 보편화되며 IP 주소가 빠르게 고갈되기 시작했다. 지금은 새로운 IPv4 주소의 할당은 중지된 상태이다.</p>
<p>ISP(Internet Service Provider) 회사에서는 일정량의 IP를 보유하고 있고 사용자에게 이를 제공해 준다.</p>
<p>ISP에 인터넷 사용 신청을 하고 공유기를 통해 하나의 공용 IP를 받았다고 가정해 보자. 공유기를 통해 인터넷을 사용하는 다양한 기기들이 있을 것이다. 이러한 공유기가 만들어주는 네트워크를 사설 네트워크라고 한다. 가정 뿐만 아니라 접속할 수 있는 사람이 제한되어 있는 네트워크를 사설 네트워크라고 한다.</p>
<p>사설 네트워크 내에서는 사설 IP가 이용된다. 사설 IP란 인터넷 상에서 사용되는 공용 IP가 아닌 독립된 네트워크 안에서 사용할 수 있는 IP이다.</p>
<p>주소를 가지고 있는 계층은 데이터 링크 계층과 네트워크 계층이다. 2계층은 MAC주소, 3계층은 IP 주소를 사용한다. IP 주소는 32비트 (IPv4), 64비트(IPv6) 이다.</p>
<p>IP 주소는 네트워크 주소와 호스트 주소로 나뉜다. 네트워크 주소는 호스트의 집합이다. 호스트 주소는 하나의 네트워크 내에 존재할 수 있는 호스트를 구분하기 위한 고유의 주소이다.</p>
<p>초기의 IP 주소 체계는 호스트 IP의 갯수에 따라 IP 네트워크의 크기를 다르게 할당할 수 있는 클래스 개념을 도입했었다. (A, B, C, D, E 그거...) 예를 들어 C클래스는 192.0.0.0~223.255.255.0 의 주소를 가지고 있다. 네트워크 주소는 3개의 옥텟, 호스트 주소는 한개의 옥텟이다. 따라서 네트워크 당 256개의 호스트를 가지고 있다. 여기서 네트워크와 브로드캐스트 주소를 빼고 254개의 주소를 사용할 수 있다.</p>
<p>이러한 클래스 기반의 IP 주소 체계는 IP 초창기에는 최적의 선택이었지만 인터넷이 상용화되고 호스트 수가 폭발적으로 증가하며 클래스 기반의 주소 체계는 증가하는 수요를 감당하기에는 부족했다. 또한 IPv4의 가장 큰 문제점은 IP 주소를 낭비하고 있다는 점이다. 예를 들어 A클래스의 경우 1677만 개의 주소를 가지게 되는데 일반적인 기업들이 모두 소비하기에는 너무 큰 숫자이다.</p>
<h4 id="cidr">CIDR</h4>
<p>이러한 상황을 해결하기 위해 나온 것이 CIDR(Classless Inter-Domain Routing) 이다. CIDR는 기존의 클래스 체계보다 더 유연하다.</p>
<p>CIDR는 슬래시 뒤의 숫자가 네트워크 주소를 의미한다. 즉 /16 이라면 32비트중 16비트를 네트워크 주소로 사용하겠다는 의미이다. 그 뒤의 16비트를 호스트 주소로 사용한다. 그리고 네트워크에서 첫번째 주소와 마지막 주 (192.168.x.x 라면 192.168.0.0 / 192.168.255.255) 는 네트워크 주소와 브로드캐스트 주소이므로 65534개의 IP를 사용할 수 있다. 클라우드라면 추가적으로 DNS나 관리의 용도로 일반적으로 3개의 IP가 더 예약 되어있다.</p>
<h4 id="네트워크-확장">네트워크 확장</h4>
<p>현대는 단일 네트워크 대역이 아닌 인터넷을 통한 광범위 대역의 네트워크를 기반으로 리소스를 사용하고 있다. 이 때 나오는 개념이 LAN, WAN 등의 개념이다.</p>
<ul>
<li>LAN(Local Network Area): 근거리 통신망. 라우터나 스위치같은 커넥터를 사용하여 물리적으로 서로 가까운 디바이스들을 연결해준다. 이를 통해 디바이스는 서로 데이터를 교환하고 소규모로 안전하게 통신할 수 있다. 하나의 건물 혹은 캠퍼스, 특정 지역이나 전세계까지 분산된 여러 위치도 포함하므로 이를 확장한 것이 광역 네트워크 (WAN) 이다.</li>
</ul>
<p>허브로 묶여있는 PC들, 스위치로 묶여있는 PC들의 집합을 일반적으로 세그먼트라고 부른다. 세그먼트 범위 내의 컴퓨터들은 패킷을 교환할 필요 없이 직접 데이터를 송수신할 수 있다. 이 케이블 분배기의 역할을 하는 것이 허브와 스위치다. 허브와 스위치는 LAN 수준에서 사용한다. 외부 네트워크와 통신을 하게 되려면 IP가 필요하다. 허브와 스위치는 MAC 주소를 읽을 수 있을 뿐, IP 주소를 읽지를 못한다. </p>
<p>따라서 외부 네트워크, 즉 인터넷이 형성되려면 라우터가 필요하다. 라우터는 IP주소를 바탕으로 한 네트워크에서 다른 네트워크로 데이터를 라우팅하거나 전달할 때 사용하는 장치다. 라우터로 데이터 패킷이 수신되면 첫 번째로 자신이 갖고 있는 네트워크 용인지, 외부 네트워크 용인지를 판단하여 IP 주소를 검사한다.</p>
<p>전자의 경우에는 받고 후자의 경우에는 다른 라우터로 트래픽을 보낸다. 이처럼 라우터 간의 패킷 교환이 이루어지며 네트워크 대역이 점점 확장된다.</p>
<h3 id="nat-network-address-translation">NAT (Network Address Translation)</h3>
<p>인터넷 상의 클라이언트와 서버의 통신을 위해서는 각자의 고유한 IP 주소가 있어야 한다. 사설 네트워크는 인터넷과 독립적인 네트워크이다. 내부 장비는 사설 IP를 가지게 된다. 이러한 사설 IP로는 통신이 불가능하다.</p>
<p>이런 상황에서 사용되는 기술이 NAT 이다. 네트워크 주소인 IP를 변환한다는 뜻이다. NAT는 IPv4의 주소부족 문제를 해결하기 위한 방법으로 디자인되었다. 사설 네트워크 주소를 사용하는 망에서 외부 공인망과 통신하기 위해 네트워크 주소를 변환해 준다.</p>
<p>사설 IP의 사용자는 공용 인터넷 망을 통해 서버와 통신하기 위해서 공용 IP가 필요하다. 사용자의 트래픽이 NAT 디바이스를 거치게 될 때 NAT 디바이스의 공용 IP로 변환된다. 이런 NAT 디바이스의 공용 IP를 가지고 인터넷에 있는 서버와 통신한다.</p>
<p>클라우드 네트워크는 백본이 연결되어 있는데, 그 내에서 VPC 역할을 만들게 되면 VPC는 사용자 네트워크 안에서 격리된 네트워크 공간이다. 즉, 사설 네트워크이다. 이런 VPC에서 공용망 통신을 하려면 보통 NAT 게이트웨이를 통해 변환되어 나간다.</p>
<p>NAT는 Source를 바꾸는 SNAT와 Destination을 바꾸는 DNAT 두 종류가 있다. 일반적으로는 연결이 stateful 하므로 (방화벽이나 라우터가 연결 상태를 추적하여, 이로 인해 이슈가 발생하지 않도록 한다.) egress traffic이 변환이 되면 ingress traffic은 자동으로 처리되므로 DNAT는 어색할수도 있다.</p>
<h3 id="dns">DNS</h3>
<p>호스트의 도메인 이름을 호스트의 네트워크 주소로 바꾸거나 그 반대의 변환을 수행한다. 특정 컴퓨터의 주소를 찾거나 임의의 장치의 주소를 찾기 위해 사람이 이해하기 쉬운 도메인 이름을 IP 주소로 변환한다.
주 컴퓨터의 도메인 이름을 IP 주소로 변환하고 라우팅 정보를 제공하는 <strong>분산형 데이터베이스 시스템</strong>이다.</p>
<p>인터넷은 두 개의 주요 이름 공간을 관리한다. 하나는 도메인 네임 계층, 하나는 IP 주소 공간이다. 도메인 네임 시스템은 도메인 네임 계층을 관리하고 해당 네임 계층과 주소 공간간의 변환 서비스를 제공한다. </p>
<p>인터넷 네임 서버와 통신 프로토콜은 DNS의 네임 시스템을 구현하고 DNS 네임 서버는 도메인을 위한 DNS 레코드를 저장하는 서버이다. DNS는 데이터베이스에 대한 쿼리 응답 정보를 리턴해준다.</p>
<ol>
<li>DNS recursor는 웹 브라우저와 같은 응용 프로그램에서 쿼리를 수신할 수 있도록 설계된 서버이다. 일반적으로 클라이언트의 DNS 쿼리 (ex. <a href="http://www.naver.com">www.naver.com</a>) 를 충족하기 위해 요청을 수행한다.</li>
<li>Root Name Server는 사람이 읽을 수 있는 호스트 이름을 IP 주소로 변환해주는 첫 번째 단계이다. 인덱스처럼 특정 위치에 대한 참조 역할을 한다.</li>
<li>TLD DNS Server는 루트 네임 서버 다음으로, 홉(.com, .net) 등을 해 해주는 역할을 한다.</li>
<li>AWS의 Route 53같은 인증된 DNS 서버들은 실제 엑세스해서 IP를 return해준다. 호스트 이름의 IP 주소를 초기 요청을 한 DNS recursor에게 반환해주는 역할을 한다.</li>
</ol>
<p>사용자가 웹 브라우저에 주소를 입력하고 전송하면, 요청은 일반적으로는 가장 첫 번째로 케이블 인터넷 공급 업체, 광대역 공급 업체 혹은 기업 네트워크와 같은 인터넷 서비스 공급 업체가 관리하는 DNS 해석기로 라우팅된다. </p>
<p>보통 DNS 서버는 OS에 등록되어 있는 곳을 가리키는데 이 DNS 서버를 가기 전에 ISP에서 제공하는 서버를 탄다. 이 ISP에 등록되어 있는 도메인 해석기가 요청을 DNS Root name server로 보낸다. <code>www.example.com</code> 의 경우 .com으로 끝나므로 .com TLD 서버로 리턴한다.</p>
<p>.com TLD Name Server에서 네임 서버를 알려주고, 해당 네임 서버에서 실제 서버를 호스팅하고 있는 네임 서버를 찾아 IP주소를 리턴한다. 이 리턴된 IP 주소는 DNS resolver를 통해 전달되고 TCP/IP 연결을 수행한다.</p>
<p>이러한 과정을 매 연결마다 수행하지 않는다. 일반적으로는 브라우저 레벨에서 관리되는 DNS 캐시가 있다.</p>
<h3 id="https와-ssltls">HTTPS와 SSL/TLS</h3>
<p>HTTPS는 HTTP의 보안이 강화된 버전이다. HTTPS는 넷스케이프 웹 프로토콜이다. 거의 모든 웹 서비스에서 사용되고 있다. HTTPS는 소켓 통신에서 일반 텍스트를 사용하는 대신 SSL/TLS 등의 프로토콜을 사용해 세션 데이터를 암호화한다.</p>
<ul>
<li>SSL: 컴퓨터 네트워크를 통해 통신 보안을 제공하는 C 언어로 작성된 암호화 프로토콜. 암호화를 사용하여 데이터의 무결성과 기밀성을 보호</li>
<li>TLS: 인터넷을 통한 보안 통신을 위한 표준. 클라이언트/서버 애플리케이션이 도청 및 정보 변조를 방지하도록 설계된 방식으로 네트워크를 통해 통신할 수 있도록 한다.</li>
</ul>
<p>SSL이 초기 버전이고, SSL이 지원을 중단하자 나온 후속 버전이 TLS이다. 두 가지 모두 인터넷을 통한 안전한 인증과 데이터 전송을 제공한다. SSL와 TLS는 메세지 인증에 차이점이 있다. SSL은 메시지 인증을 위해 MAC을 사용한다. 전송 중에 메시지가 변조되지 않도록 한다. TLS는 보호를 위해 MAC을 사용하지 않고 암호화 알고리즘을 사용해 데이터의 변조를 방지한다.</p>
<p>TLS는 SSL의 직접적인 후속 버전이다. 현재는 SSL은 더 이상 사용되지 않고 TLS를 사용한다. 따라서 SSL 인증서는 지금은 TLS 동작 원리이다.</p>
<h4 id="tls-동작-원리">TLS 동작 원리</h4>
<p>TLS는 대칭키 암호화 방식과 공개키 암호화 방식을 두 가지 모두 사용한다.</p>
<ul>
<li><p>대칭키: 암복화에 사용하는 키가 동일하다. 해당 키가 아는 사람은 모두 문서를 복호화할 수 있다. 대표적인 알고리즘은 DES, 3DES, AES, SEED, ARIA 등이 있다. 공개키 암호화 방식에 비해 연산 속도가 빠르지만 키를 교환해야 하는 문제와 키를 교환하는 방법이 필요하다. 사용자마다 각각의 키가 필요하므로 관리해야 할 키가 많다.</p>
</li>
<li><p>공개키: 대칭키 암호화 방식보다 복잡한 연산을 거친다. 대표적인 알고리즘은 RSA, DSA, ECC가 있다. 대칭키 방식의 문제점을 해결하기 위해 만들어졌다. 공개키 방식의 키는 공개되어 있다. 따라서 키를 교환할 필요가 없어진다. 공개키는 모든 사람이 접근 가능한 키이고, 개인키는 각 사용자만이 가지고 있는 키가 된다.</p>
</li>
</ul>
<p>수신자가 공개키와 개인키 페어를 만든다. 송신자가 접근 가능한 환경에 수신자의 공개키를 공개한다. 수신자의 개인키는 수신자만이 가지고 있다. 송신자는 수신자의 공개키를 받아 보낼 데이터를 수신자의 공개키로 암호화한다. 암호화된 데이터를 수신자에게 보낸다. 수신자는 암호화된 데이터를 자신이 가지고 있던 개인키로 복호화한다.</p>
<p>공개키 방식은 키가 공개되어 있으므로 따로 키교환이나 분배를 할 필요가 없다. 중간공격자가 개인키를 가지고 있다 해도 수신자만이 암호화한 데이터를 복호화할 수 있으므로 인증을 할 수 있는 환경을 제공한다.</p>
<p>인증서는 이러한 공개키 기반이다.</p>
<p>HTTPS에서 공개키로 사용되는 것이 인증서이다. 인증서에는 키나 소유자에 대한 정보, 인증사 발급자의 디지털 사인 등이 포함되어 있다. 루트 인증서는 최상위 기관인 루트 인증 기관에서 발급된 인증서를 뜻한다. TLS 루트 인증서는 기관에서 직접 발급한 인증서이다. 다른 인증서와는 달리 루트 인증서는 발급 기관에서 자체 서명을 하게 된다. 루트 인증서의 개인키는 TLS 인증서 계층 구조의 다른 인증서의 서명을 하는데 사용된다.</p>
<p>이러한 루트 인증서는 매우 엄격하게 발급되므로 보통 OS나 브라우저에 이미 설치되어 있다. 일반적으로 공개가 되어 있고 다른 루트 인증서를 사용하고자 하는 경우에는 브라우저나 OS에 등록하면 된다.</p>
<p>루트 인증서를 보호하기 위해 중간 인증서를 두는 경우가 많다. 실제 서비스를 제공하는 웹서버들은 루트 인증서가 아닌 중간 기관을 통해 인증서를 발급하는 경우가 많다.</p>
<p>TLS 인증서는 인증서를 발급한 기관의 정보, 서비스를 제공하는 도메인 등의 정보, 즉 서버의 공개키가 포함되어 있다. 이 내용은 인증 기관에 의해 암호화된다. 이 때 공개키 암호화 기법이 사용된다. 이 때는 특이하게 인증 기관의 개인키로 암호화가 진행된다.</p>
<p>브라우저에는 인증기관의 공개키가 깔려있으므로 공개키에 의해 복호화가 가능해진다. 이 서버를 보증하는 것을 인증 회사가 갖고 있는 개인키로 암호화하면 브라우저에 깔려 있는 공개키를 통해 해당 인증 기관에서 만들어진 인증서가 맞다는 것을 인정한다. 이것이 HTTPS 인증서의 핵심이다.</p>
<p>즉, 브라우저가 보유하고 있는 인증 기관의 공개키로 복호화가 가능하다는 것은 해당 공개키와 쌍을 이루는 개인키로 암호화되었다는 것을 보증하는 것이다. 해당 데이터가 인증 기관으로 왔음이 증명되는 것이다.</p>
<p>클라이언트가 공개키를 가지고 보안성 검증이 완료되면 사이트를 신뢰할 수 있으므로 해당 공개키를 활용해 서버와 소통하며 대칭키인 세션키를 가지고 소통할 수 있게 된다.</p>
<h4 id="tls-handshake">TLS Handshake</h4>
<p>인증 기관의 비공개키로 암호화된 인증서가 브라우저가 가지고 있는 공개키로 복호화를 진행하면 공개키를 활용하여 세션키를 만든다. 이 세션키를 통해 통신을 시작한다. 이러한 HTTPS 연결을 하는 과정을 TLS Handshake 라고 한다.</p>
<ol>
<li><p>클라이언트가 서버에게 hello를 보내며 handshake를 시작한다. 여기서 사용하고자 하는 TLS의 버전, 암호 제품군 등의 내용을 실어 보낸다.</p>
</li>
<li><p>hello 메시지를 받은 서버는 서버가 가지고 있는 서버의 TLS 인증서, 서버에서 사용하는 암호 제품군 (알고리즘) 을 보낸다.</p>
</li>
<li><p>클라이언트는 인증기관을 통해 서버 인증서를 검증한다. 확인이 완료되면 랜덤한 시퀀스를 서버의 공개키로 암호화하고 보낸다.</p>
</li>
<li><p>여기서 서버가 클라이언트에게 인증서를 요구한다면 서버 인증서와 같은 방식으로 암호화를 해 전송한다. 서버가 클라이언트로부터 서버의 공개키로 받은 문자열을 자신의 개인키로 해독한다.</p>
</li>
<li><p>클라이언트가 아까 보낸 암호화된 시퀀스를 대칭키로 활용할 세션키로 만든다. 그 후 finish를 서버에 보낸다.</p>
</li>
<li><p>서버가 클라이언트가 보낸 랜덤 시퀀스를 개인키로 복호화하고 그 동일한 랜덤 문자열을 세션키로 만든다. 그러면 대칭키가 되므로 똑같이 finish를 보내 연결을 수립하고 HTTPS 통신을 수행한다.</p>
</li>
</ol>
<p>이러한 HTTPS 통신은 엔드포인트 단에서 수행하고, 내부에서의 통신은 offloading 시키고 HTTP로 수행하는 경우가 많다 (지만 사실은 다 HTTPS 를 써야 하는 것이 맞긴하다)...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[번역] 쿠버네티스의 PVC]]></title>
            <link>https://velog.io/@hsh_124/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4%EC%9D%98-PVC</link>
            <guid>https://velog.io/@hsh_124/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4%EC%9D%98-PVC</guid>
            <pubDate>Fri, 23 Feb 2024 02:22:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.linkedin.com/pulse/complete-guide-understanding-persistent-volume-claim-pvc-arya-soni">해당 글</a> 을 번역한 포스트입니다.</p>
<p><img src="https://velog.velcdn.com/images/hsh_124/post/166a30ae-d8f2-4821-a12d-f0977f14b214/image.png" alt=""></p>
<p>데이터 지속성(어플리케이션 데이터가 영구적으로 저장되어 프로세스나 서버의 재시작 후에도 유지되는 성질) 은 모든 애플리케이션의 핵심 구성 요소 중 하나이며, Kubernetes로 조정되는 컨테이너화된 어플리케이션의 경우, Persistent Volumes (PV) 와 Persistent Volume Claims (PVC) 를 이해하는 것은 필수적입니다. 이 글은 PVCs가 무엇인지 쉽게 이해할 수 있도록 하고, 어떻게 PVs와 다른지, 어째서 그들이 Kubernetes 환경에서 필수적인지에 대해 초점을 맞추고 있습니다. 당신이 DevOps Engineer인지, Kubernetes 초보자인지, 아니면 숙련된 개발자인지 여부에 관계없이 PVC에 대해 알아야 할 모든 정보를 제공하는 종합 가이드입니다.</p>
<h2 id="kubernetes에서-persistent-volume-claims-pvc-란-무엇일까요">Kubernetes에서 Persistent Volume Claims (PVC) 란 무엇일까요?</h2>
<p>Kubernetes에서, Persistent Volume Claims (이하 PVC) 는 Persistent Volume (이하 PV) 이 처리할 수 있는 스토리지에 대한 사용자의 요청입니다. 특정한 스토리지 리소스에 액세스 하기 위한 “티켓” 이라고 생각하시면 됩니다. 사용자가 PVC를 생성할 때, Kubernetes는 이를 바인딩할 적절한 PV를 찾아서, PVC에 지정된 스토리지 요구사항이 충족되는지 확인합니다. 일치하는 PV를 찾는다면, PVC는 해당 PV에 바인딩되어, 애플리케이션이 스토리지에 액세스 할 수 있게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/hsh_124/post/e253cc0c-a62a-4251-8cfc-3d86cb247de3/image.png" alt=""></p>
<h2 id="persistent-volume-pv-vs-persistent-volume-claims-pvc">Persistent Volume (PV) vs Persistent Volume Claims (PVC)</h2>
<h3 id="persistent-volume-pv">Persistent Volume (PV)</h3>
<ul>
<li><strong>시스템 관리자에 의해 프로비저닝됨:</strong> 일반적으로 시스템 관리자가 프로비저닝합니다.</li>
<li><strong>클러스터 리소스:</strong> 클러스터 레벨 리소스이므로, 클러스터의 어느 노드에서나 액세스할 수 있습니다.</li>
<li><strong>Storage Backend:</strong> NFS, iSCSI, local disk, 그 외가 될 수 있습니다.</li>
<li><strong>네임스페이스가 지정되지 않음:</strong> PVs 는 특정한 네임스페이스에 묶여 있지 않습니다.</li>
</ul>
<h3 id="persistent-volume-claims-pvc">Persistent Volume Claims (PVC)</h3>
<ul>
<li><strong>유저에 의해 요청됨:</strong> 유저에 의해 생성되거나, 경우에 따라, 쿠버네티스가 자동으로 생성합니다.</li>
<li><strong>네임스페이스 범위:</strong> PVCs 는 네임스페이스에 한정되므로, 같은 네임스페이스에 있는 파드에서만 액세스할 수 있습니다.</li>
<li><strong>요구 사항 정의:</strong> 스토리지 크기, 액세스 모드 등을 지정합니다.</li>
<li><strong>라이프사이클:</strong> 일반적으로 이 PVC를 사용하는 파드와 동일한 생명 주기를 갖습니다.</li>
</ul>
<p>간단하게 비유하면: PV가 주차장이라면, PVC는 그 주차장에 한 자리를 차지할 수 있는 주차권입니다.</p>
<h2 id="코드-예시-pv와-pvc-생성하기">코드 예시: PV와 PVC 생성하기</h2>
<p>PV를 생성한 다음 PVC를 생성하여 해당 볼륨에서 스토리지를 요청하는 예를 살펴 보겠습니다.</p>
<h3 id="pv-만들기">PV 만들기</h3>
<p>다음은 로컬 스토리지를 사용하여 간단한 PV를 정의하는 YAML 파일입니다:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: local-storage
  local:
    path: /mnt/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - my-node</code></pre>
<p>PV를 만들기 위해, 해당 YAML 파일을 실행합니다:</p>
<pre><code class="language-bash">kubectl apply -f my-pv.yaml</code></pre>
<h3 id="pvc-만들기">PVC 만들기</h3>
<p>PVC를 정의하는 YAML 파일입니다:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  storageClassName: local-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi</code></pre>
<p>PVC를 만들기 위해, 해당 YAML 파일을 실행합니다:</p>
<pre><code class="language-bash">kubectl apply -f my-pvc.yaml</code></pre>
<p>PVC를 생성한 후, 쿠버네티스는 요구 사항을 충족하는 사용 가능한 PV에 자동으로 바인딩합니다. 스테이터스를 다음 명령어로 확인할 수 있습니다:</p>
<pre><code class="language-bash">kubectl get pvc my-pvc</code></pre>
<p>그리고 당신은 우리가 이미 만들어 놓은 PV에 PVC가 바인딩되었다는 것을 확인할 수 있을 것입니다.</p>
<h2 id="왜-pvc가-중요한가요">왜 PVC가 중요한가요?</h2>
<ul>
<li><strong>추상화:</strong> PVC는 기본 스토리지 인프라를 추상화하여 개발자들이 스토리지 백엔드의 세부 사항을 알 필요 없이 스토리지를 요청할 수 있는 방법을 제공합니다.</li>
<li><strong>이식성:</strong> PVC는 네임스페이스 범위를 가지고 스토리지 요구 사항을 정의하기 때문에, 다른 쿠버네티스 클러스터 간에 애플리케이션을 이동하기가 더 쉬워집니다.</li>
<li><strong>확장성:</strong> PVC는 필요에 따라 쿠버네티스가 새로운 볼륨을 자동으로 프로비저닝할 수 있게 하여, 애플리케이션을 확장하기가 더 쉬워집니다.</li>
<li><strong>데이터 지속성:</strong> PVC를 사용하면 파드가 종료되어도 데이터가 손실되지 않으며, 장기 스토리지 솔루션을 제공합니다.</li>
</ul>
<p>쿠버네티스를 사용할 때, 특히 데이터 지속성과 스토리지 효율성을 보장하기 위해서는 PV 와 PVC 를 이해하는 것이 중요합니다. PV는 실제 스토리지 인프라를 제공하는 반면, PVC는 사용자 애플리케이션과 이러한 스토리지 리소스 간의 다리 역할을 하여, 더욱 유연하고 효율적이며 확장 가능한 스토리지 솔루션을 가능하게 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프라이빗 레지스트리에서 이미지 받아오기]]></title>
            <link>https://velog.io/@hsh_124/%ED%94%84%EB%9D%BC%EC%9D%B4%EB%B9%97-%EB%A0%88%EC%A7%80%EC%8A%A4%ED%8A%B8%EB%A6%AC%EC%97%90%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hsh_124/%ED%94%84%EB%9D%BC%EC%9D%B4%EB%B9%97-%EB%A0%88%EC%A7%80%EC%8A%A4%ED%8A%B8%EB%A6%AC%EC%97%90%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Fri, 16 Feb 2024 00:18:41 GMT</pubDate>
            <description><![CDATA[<p><a href="https://kubernetes.io/ko/docs/tasks/configure-pod-container/pull-image-private-registry/">참고</a></p>
<p>프라이빗 컨테이너 레지스트리를 사용하기 위해서는 시크릿을 사용하는 파드를 사용한다. 실습에서는, Docker Hub를 사용해서 구축한다. </p>
<ul>
<li>Docker Hub 로그인<ul>
<li><code>docker login</code> 명령어를 사용하여 Docker Hub에 로그인한다. 다른 프라이빗 레지스트리를 사용한다면, 해당 레지스트리에 대한 명령줄 정보가 필요하다.</li>
<li><code>cat ~/.docker/config.json</code> 명령어를 사용하여 자격 증명을 확인한다. 하단과 비슷한 정보가 나오는지 확인한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">[developer@localhost root]$ cat ~/.docker/config.json
{
        &quot;auths&quot;: {
                &quot;https://index.docker.io/v1/&quot;: {
                        &quot;auth&quot;: &quot;어쩌고&quot;
                }
        }</code></pre>
<ul>
<li>쿠버네티스 클러스터는 프라이빗 레지스트리에 인증할 때 <code>kubernetes.io/dockerconfigjson</code> 타입의 시크릿을 사용한다. docker login이 되어 있는 상태라면 자격 증명을 클러스터에 복사할 수 있다. 해당 명령어를 사용한다.</li>
</ul>
<pre><code class="language-bash">k create secret generic regcred \
--from-file=.dockerconfigjson=/home/developer/.docker/config.json \
--type=kubernetes.io/dockerconfigjson 

secret/regcred created

---

k get secret
NAME      TYPE                             DATA   AGE
regcred   kubernetes.io/dockerconfigjson   1      78s</code></pre>
<ul>
<li>로그인 하지 않은 경우에는 CLI에서 자격 증명을 통하여 시크릿을 생성할 수 있다.</li>
</ul>
<pre><code class="language-bash">kubectl create secret docker-registry regcred \
--docker-server=&lt;your-registry-server&gt; \
--docker-username=&lt;your-name&gt; --docker-password=&lt;your-pword&gt; \
--docker-email=&lt;your-email&gt;</code></pre>
<ul>
<li>방금 생성한 <code>regcred</code> 시크릿의 정보를 확인해 보자.</li>
</ul>
<pre><code class="language-bash">k get secret regcred -o yaml

apiVersion: v1
data:
  .dockerconfigjson: ~/.docker/config.json을 base64로 인코딩한 내용
kind: Secret
metadata:
  creationTimestamp: &quot;2024-02-02T07:23:36Z&quot;
  name: regcred
  namespace: default
  resourceVersion: &quot;77283&quot;
  uid: 8e7edf7e-0499-43f7-aa85-e53e93f4949d
type: kubernetes.io/dockerconfigjson</code></pre>
<ul>
<li>.dockerconfigjson 필드 값을 확인하기 위해, 시크릿 데이터를 읽을 수 있는 형식으로 변형해 본다.</li>
</ul>
<pre><code class="language-bash">kubectl get secret regcred --output=&quot;jsonpath={.data.\.dockerconfigjson}&quot; | base64 --decode

{
        &quot;auths&quot;: {
                &quot;https://index.docker.io/v1/&quot;: {
                        &quot;auth&quot;: &quot;아까 결과와 같음&quot;
                }
        }
}</code></pre>
<ul>
<li>이를 통해 <code>regcred</code> 라는 시크릿으로 클러스터 내에서 도커 자격 증명을 생성한 것을 확인할 수 있다.</li>
<li>생성한 시크릿을 사용하는 파드를 만든다.<ul>
<li>도커에 이미지를 넣기 전에, tag를 꼭 변경해 주자…</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">    docker tag busybox:latest &lt;myusername&gt;/busybox:latest

    ---

    docker tag busybox:latest your_id/busybox:latest
    docker push your_id/busybox:latest
    The push refers to repository [docker.io/your_id/busybox]
    2e112031b4b9: Mounted from library/busybox
    latest: digest: sha256:어쩌고~... size: 527
    ---
    vi pod.yaml 

    apiVersion: v1
    kind: Pod
    metadata:
      name: private-reg
    spec:
      containers:
      - name: private-reg-container
        image: your_id/busybox:latest
      imagePullSecrets:
      - name: regcred</code></pre>
<ul>
<li>프라이빗 저장소에서 이미지를 받아오기 위하여, 쿠버네티스에서 자격 증명이 필요하다. 구성 파일의 <code>imagePullSecrets</code> 필드를 통해 쿠버네티스가 <code>regcred</code> 라는 시크릿으로부터 자격 증명을 가져올 수 있다.</li>
<li>파드를 생성했을 때 파드가 생성되면 ok</li>
</ul>
<pre><code class="language-bash">k get po
NAME                                READY   STATUS    RESTARTS      AGE
nginx-deployment-6d6565499c-fdzhn   1/1     Running   0             22m
private-reg                         1/1     Running   9 (75s ago)   7m32s

containerStatuses:
    - containerID: docker://1ae39d854c8cb35cbe974208587ebf5de7cec69f1cbb8c1149734ab60b407de8
      image: your_id/nginx:latest
      imageID: docker-pullable://your_id/nginx@sha256:6a9af2366105c104e353d16998458d6a15aa5d6db0861ad9ce98538890391950</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTPS]]></title>
            <link>https://velog.io/@hsh_124/HTTPS</link>
            <guid>https://velog.io/@hsh_124/HTTPS</guid>
            <pubDate>Fri, 16 Feb 2024 00:13:25 GMT</pubDate>
            <description><![CDATA[<h3 id="https">HTTPS</h3>
<p>HTTPS는 HTTP의 요청과 응답을 암호화하여 전달하기 위해 사용한다. HTTP는 암호화 되어 있지 않으므로, 데이터가 중간에 가로채지거나 변조될 수 있는 취약점을 해결하기 위해 도입되었다. 또한 사용자간의 인증을 위해서도 사용한다. HTTPS는 <strong>공개 키 기반 구조(PKI)를 사용하여 신원을 확인하고 데이터를 암호화한다.</strong></p>
<p>웹 사이트에 접속했을 때, 웹 사이트가 개인 키를 가지고 있으며, 브라우저는 해당 웹 사이트의 공개 키가 포함된 인증서를 검증한다. 인증서는 신뢰할 수 있는 인증 기관(CA)에 의해 발급된다. 브라우저는 인증서를 사용하여 웹 서버의 신원을 확인한다.</p>
<p>브라우저에 &quot;등록&quot;된 것은 실제로 인증서의 유효성을 확인하기 위한 루트 인증서 목록이다. 웹 서버의 인증서가 이 루트 인증서들 중 하나에 의해 서명되었다면, 브라우저는 그 인증서를 신뢰한다.</p>
<p>HTTPS는 HTTP 프로토콜 상위에 TLS 암호화를 구현한 것이라고 할 수 있다. TLS는 응용 계층과 전송 계층 사이에서 작동하여 안전한 보안 채널을 형성해 주는 역할을 한다.</p>
<p>HTTPS는 다음과 같이 동작한다:</p>
<ol>
<li><strong>핸드셰이크</strong>: 클라이언트가 서버에 접속하면, 서버는 자신의 공개 키를 포함한 인증서를 클라이언트에게 제공한다.</li>
<li><strong>인증서 검증</strong>: 클라이언트는 인증서가 신뢰할 수 있는 CA(Certificate Authority)에 의해 발급되었는지 확인한다.</li>
<li><strong>대칭 키 생성</strong>: 클라이언트는 대칭 키를 생성하고, 서버의 공개 키를 사용하여 이를 암호화하여 서버에게 보낸다.</li>
<li><strong>암호화된 세션 시작</strong>: 서버는 개인 키를 사용해 대칭 키를 복호화하고, 이후의 통신은 이 대칭 키를 사용하여 암호화된다.</li>
</ol>
<p>HTTPS의 장점은 다음과 같다.</p>
<ul>
<li><strong>데이터 보안</strong>: 중간자 공격으로부터 사용자 데이터를 보호한다.</li>
<li><strong>데이터 무결성</strong>: 전송 중인 데이터가 변경되거나 손상되지 않았음을 보장한다.</li>
<li><strong>인증</strong>: 사용자가 실제로 의도한 서버와 통신하고 있음을 보증한다.</li>
</ul>
<p>HTTP의 성능 향상을 목적으로 설계된 새로운 프로토콜인 HTTP/2는 대부분의 상황에서 HTTPS 연결을 전제로 한다. 이를 통해 웹 페이지 속도를 빠르게 하고, 다수의 리소스를 효율적으로 전송할 수 있도록 해 준다.</p>
<h3 id="ssl--tls">SSL / TLS</h3>
<p>TLS는 넷스케이프의 SSL을 기반으로 한 보안 프로토콜이다. SSL과 TLS는 사실상 같은 것을 의미한다고 보면 된다. 주로 HTTP를 암호화할 때 사용한다. 인증서를 통해 상호가 적당한 사용자인지 검증하고, 암호화 알고리즘을 통해 암호화된 키를 교환한 후 암호화 통신을 진행한다.</p>
<p>TLS는 세 가지 역할을 한다.</p>
<ul>
<li><strong>암호화</strong>: 제 3자로부터 전송되는 데이터를 숨긴다.</li>
<li><strong>인증</strong>: 정보를 교환하는 당사자가 요청된 당사자임을 보장한다.</li>
<li><strong>무결성</strong>: 데이터가 위조되거나 변조되지 않았는지 확인한다.</li>
</ul>
<p>TLS는 공개키를 사용하여 암호화를 진행한다. (공개키 암호화) 공개 키는 서버의 인증서를 통해 클라이언트 장치와 공유된다. 인증서는 CA(인증 기관) 에 의해 암호화되어 서명되며, 브라우저에는 신뢰하는 CA의 목록이 있다. 이 CA의 목록에 저장되지 않은 곳에서 인증서가 발급되었다면, https가 신뢰되지 않는다고 뜬다. (그 빨간색 그거)</p>
<p>서버와 클라이언트는 교환된 공개 키와 개인 키를 통해 세션 키라는 새로운 키에 동의하여 통신을 암호화한다. 세션 키는 이 하나의 통신 세션만을 위해 존재하는 일회용 대칭 키이다.</p>
<p>TLS 프로토콜은 여러 버전(예: TLS 1.0, 1.1, 1.2, 1.3 등)이 있으며, 각각의 버전은 보안 강도와 호환성 측면에서 차이가 있다.</p>
<ul>
<li>TLS 1.0<ul>
<li>SSL의 직접적인 후속 버전이다. SSL의 보안 취약점을 많이 해결했지만, 현대의 기준으로는 여전히 취약점이 많다고 여겨진다.</li>
</ul>
</li>
<li>TLS 1.1<ul>
<li>TLS 1.0에 비해 몇 가지 보안 개선 사항을 도입했지만, 초기 버전의 취약점을 완전히 해결하지는 못했다.</li>
</ul>
</li>
<li>TLS 1.2<ul>
<li>SHA-256과 같은 강력한 해시 알고리즘을 지원하며, 안전하지 않은 암호화 알고리즘(예: RC4)의 사용을 피함. 현재까지도 널리 사용되고 있는 버전이다.</li>
</ul>
</li>
<li>TLS 1.3<ul>
<li>TLS 프로토콜의 최신 버전. TLS 1.2보다 더 빠르고, 더 안전하다. TLS 핸드셰이크의 작동 방식을 업데이트하여, 두 번이 아니라 한 번의 왕복만 하면 되도록 했다. 또한, 클라이언트가 이전의 웹 사이트에 접속한 적이 있는 경우 왕복 횟수를 0으로 해 HTTPS의 연결 속도가 빨라지도록 했다. 이를 통해 대기 시간이 대폭 단축되고 전반적인 사용자 경험이 개선되도록 했다.</li>
</ul>
</li>
</ul>
<h3 id="tls-handshake">TLS Handshake</h3>
<p>TLS를 사용하는 통신 프로세스를 TLS Handshake 라고 한다. 서버와 클라이언트가 서로를 인식하고, 검증하고, 안전하게 통신할 수 있도록 하는 과정이다. </p>
<ol>
<li><p><strong>ClientHello</strong></p>
<p> Handshake 프로세스는 클라이언트가 서버에게 Hello 메시지를 보내면서 시작된다. 이 때 메시지에는 클라이언트가 지원하는 TLS 버전, 클라이언트가 지원하는 암호화 방식, 세션을 재개하기 위한 ID(이전 연결에서 사용됨), 그리고 무작위 난수가 포함되어 있다.</p>
</li>
<li><p><strong>ServerHello</strong></p>
<p> 서버는 ClientHello에 대해 ServerHello 메시지로 응답한다. 이 때 메시지에는 선택된 TLS 버전, 서버에서 선택한 암호화 방식, 세션 ID, 그리고 서버에서 생성한 무작위 난수가 포함되어 있다.</p>
</li>
<li><p><strong>Certificate</strong></p>
<p> 서버는 자신의 인증서를 클라이언트에게 보내 클라이언트가 서버의 신뢰성을 검증할 수 있도록 한다. 인증서에는 서버의 공개 키와 인증서 발급 기관(CA)의 서명이 포함되어 있다. 클라이언트는 자신의 웹 브라우저에 등록된 “신뢰할 수 있는 인증서” 목록에 있는 공개 키로 인증서 해시 값을 비교하여 서버를 신뢰할 수 있는지 검증한다.</p>
</li>
<li><p><strong>ServerHelloDone</strong></p>
<p> 서버가 인증서와 필요한 모든 메시지를 보낸 후, 서버는 ServerHelloDone 메시지를 클라이언트에게 전송하여 서버가 클라이언트에게 보낼 메시지를 모두 보냈다고 한다.</p>
</li>
<li><p><strong>ClientKeyExchange</strong></p>
<p> 인증서의 무결성이 검증되었다면, 클라이언트는 클라이언트의 난수와 서버의 난수를 조합하여 대칭 키(세션 키)를 생성한다. 그리고 대칭 키를 서버의 공개 키로 암호화하고, 암호화된 키를 서버에 전송한다. 이를 통해 양측은 통신에 사용할 공유된 비밀을 가지게 된다.</p>
<p> 자세히 설명하자면, 클라이언트는 키 교환에 필요한 정보를 서버에 제공한다. 이 정보를 사전 마스터 시크릿(pre-master secret) 이라고 하며, 절대로 노출이 되어서는 안 된다. 이 값을 클라이언트가 서버의 공개 키로 암호화 하여 전송한다. 그러면 서버는 개인 키로 복호화하고, 이렇게 되면 서로가 사전 마스터 시크릿을 공유한다. 이 값을 사용해서 세션에 사용할 키를 만드는 것이다. </p>
</li>
<li><p><strong>ChangeCipherSpec</strong></p>
<p> 양측은 이후의 메시지가 협상된 암호화 설정을 사용하여 전송될 것임을 알린다.</p>
</li>
<li><p><strong>Finished</strong></p>
<p> 핸드셰이크 과정 동안 교환된 모든 메시지의 무결성을 검증하며, 이 시점부터 양측은 안전한 통신을 시작한다.</p>
</li>
</ol>
<h3 id="tls-termination">TLS Termination</h3>
<p>클라이언트-인터넷망-서비스-파드로 연결된 구조에서, 클라이언트부터 파드까지 end-to-end로 https 암호화가 걸린다. 문제는 서버 입장에서 end-to-end 암호화는 전혀 안전하지 않다. https는 클라이언트가 서버를 인증하는 과정인데, 서버 입장에서는 클라이언트를 확인하지 못한다(인증을 할 수 없다). 따라서 서버 입장에서는 클라이언트의 요청을 수행할 수 밖에 없는데, 악성 요청도 그대로 수행하게 된다.</p>
<p>이를 방지하기 위해서 쓰이는 것이 WAF지만, 결국 암호화 된 데이터를 중간에서 풀어볼 수 없어 공격인지 아닌지 확인할 수 없다. 이를 위해서 사용하는 것이 TLS Termination Proxy이다. 클라이언트와 프록시 서버 간에 HTTPS 연결을 종료하고, 프록시 서버와 내부 서비스 간에는 HTTP 또는 새로운 HTTPS 연결을 사용하는 방식이다. 프록시 서버에서 암호화를 해제하여 요청을 검사할 수 있으며, 내부 네트워크에서는 HTTPS가 필요하지 않거나, 필요한 경우 새로운 HTTPS 세션을 시작할 수 있다.</p>
<p>클라이언트와 서버 사이에 프록시 서버가 있다. 프라이빗 네트워크 안에서는 사실 접근 제어만 잘 된다는 가정 하에 https로 통신할 필요가 없다. 프록시 서버에서 tls 종료 프록시를 통해 https를 종료시키고, 내부에서는 http를 종료한 상태에서 통신한다. 내부적으로 https 통신을 한다고 해도, tls 통신 프록시를 통해 한 번 종료시키고 다시 https로 통신한다.</p>
<p>쿠버네티스 환경에서는 ingress를 사용하여 쉽게 TLS Termination Proxy를 구현할 수 있다.</p>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ing-tls-term
spec:
  tls:
  # - hosts:
    # - myapp.example.com #실습할 때는 없애는 것이 편함
    secretName: myapp-tls-secret #이곳의 인증서와 키를 가져온다.
  rules:
  # - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend: 
          service:
            name: myapp-svc-np
            port: 
              number: 80</code></pre>
<p>예시는 클라이언트와 Ingress 간의 HTTPS 연결을 종료하고, Ingress와 내부 서비스 간에는 HTTP를 사용하도록 설정하는 방법을 보여준다. 이 설정을 통해 Ingress에서 TLS 인증서를 관리하며, 내부 파드는 HTTPS 관련 설정이나 키를 관리할 필요가 없게 된다. 암호화 통신은 클라이언트와 인그레스 사이에서만 하기 때문에 파드의 nginx는 암호를 위한 설정과 인증키가 필요 없기 때문이다. 따라서 사용자가 훨씬 유연하게 사용할 수 있다.</p>
<h3 id="pki">PKI</h3>
<p>PKI는 공개 키 기반 구조(Public Key Infrastructure)를 의미한다. PKI는 디지털 인증서와 공개 키 암호화를 활용하여 인터넷 상에서 사용자, 서버 및 조직의 신원을 안전하게 인증하고, 데이터의 무결성 및 기밀성을 보장한다.</p>
<p>기존의 암호화는 대칭 키(암호화 키와 복호화 키가 같음) 알고리즘을 사용하여 정보를 교환했다. 대칭 키 암호화는 구현이 빠르고 속도가 빠르다는 장점이 있지만, 키를 스니핑(패킷을 엿보는 행위)나 스푸핑(속여서 공격) 등의 해킹 기법에 당하기 쉽다는 단점이 있었다. 이를 해결하기 위해 암호화 키와 복호화 키를 따로 가져가는 기술이 등장했고, 이에 사용하는 것이 PKI 이다.</p>
<p>PKI를 이용한 정보 교환은 두 가지 상황을 가정할 수 있다:</p>
<ul>
<li>공개 키를 사용해 암호화<ul>
<li>공개 키를 사용하여 암호화하면 공개 키에 매칭되는 개인 키를 가지고 있는 상대방만 개인 키를 사용해 복호화 할 수 있다.</li>
</ul>
</li>
<li>개인 키를 사용해 암호화<ul>
<li>개인 키를 사용해 암호화하면 공개 키를 이용해 누구나 복호화할 수 있다. 이는 자신의 자격을 증명하는 데에 주로 사용된다.</li>
</ul>
</li>
</ul>
<p>공개 키의 경우에는 공개되어 있기 때문에, 해당 키가 신뢰할 수 있는 키인지 확인하기 어렵다. 이 때 사용되는 것이 바로 인증 기관(CA) 이다. CA는 인증서를 발행하고 관리하는 공개 키에 대한 공신력 있는 기관이다. 인증 정책을 수립하고 사용자의 공개 키와 신원 정보를 결합하여 인증서를 발급하며, 이를 통해 사용자의 신원을 보증한다. </p>
<ul>
<li>RA(등록 기관) 이라는 것도 있다. 등록 기관은 CA의 일 중 공개 키의 등록과 본인에 대한 인증을 대행한다.</li>
<li>인증서를 보존해 두고, PKI의 이용자가 인증서를 입수할 수 있도록 한 데이터베이스를 저장소라고 한다. 전화번호부와 같은 역할을 한다고 볼 수 있다.</li>
</ul>
<p>CA는 개인 키와 공개 키 쌍을 만들고, 개인 키에 대한 인증서를 발급한다. 이러한 인증서들의 표준 규칙이 X.509 형식이다. 이 때 인증서의 최상위에 존재하는, 일반적으로 웹 브라우저에 내장되어 있는 ‘누구나 신용할 만한’ 인증서 발급 기관에서 발급한 인증서들을 Root CA라고 한다.</p>
<h3 id="chain-of-trust">Chain of Trust</h3>
<p>인증서는 트리 구조를 가진다. 예를 들어, 네이버의 인증서 구조를 보자.</p>
<p>네이버는 Root CA인 DigiCert로 부터 인증받은 하위 인증서인 DigiCert TLS<del>CA1 으로부터 인증을 받았다. 보통은 이처럼 3계층 구조로 이루어진다. DigiCert가 Root CA이고, DigiCert TLS</del>CA1 역시 인증기관이다. 이것을 중간 인증 기관(Intermidiate CA) 라고 부른다. 이 ICA에 대한 신뢰를 입증하기 위해 다음과 같은 구성을 한다.</p>
<ol>
<li>회사가 Root CA에 인증서 발급 요청을 하면, 인증서의 해시 값을 Root CA의 비밀 키로 암호화한다.</li>
<li>이러면 회사의 인증서 값은 Root CA의 공개 키로 복호화 할 수 있다. 복호화한 값이 Root CA 인증서의 값과 다르다면 인증서의 내용이 변조되었음을 의미한다.</li>
<li>클라이언트는 제공된 최종 인증서부터 시작하여, 각 중간 인증서를 거슬러 올라가면서 루트 인증서에 도달할 때까지 각 인증서의 유효성을 검증한다. 각 인증서는 바로 상위 인증서에 의해 서명되어 있어야 하며, 상위 인증서의 공개 키로 하위 인증서의 서명을 검증할 수 있다.</li>
<li>마지막 단계에서는 루트 인증서가 클라이언트 소프트웨어에 사전 설치된 신뢰할 수 있는 루트 인증서 목록 중 하나와 일치하는지 확인한다.</li>
<li>상위기관의 공개키로 하위기관의 인증서 해시값을 복호화함으로써 쉽게 변조 유무를 확인할 수 있으므로, 하위 기관을 신뢰할 수 있다고 간주한다.</li>
</ol>
<p>이처럼, X.509 인증서는 신뢰 체인을 구성할 수 있다. 최상위의 루트 CA가 중간 CA의 인증서에 서명하고, 이 중간 CA는 다시 사용자 또는 서버의 인증서에 서명한다. 이 체인을 통해 최종 사용자의 인증서가 루트 CA에 의해 간접적으로 신뢰된다. 이것을 Chain of Trust라고 부른다.</p>
<h3 id="x509">X.509</h3>
<p>X.509는 ITU-T에서 책정한 표준 형식이다. X.509는 매우 엄격한 수직 구조를 띄고 있어, 하나의 인증 기관(Root CA)를 정점으로 하는 트리 구조를 띄고 있다. </p>
<p>X.509 규격의 인증서는 공개되어 있고, OpenSSL 등을 통해 누구나 만들 수 있다. 이러면 인증서에 대한 신뢰의 문제가 발생한다. 이래서 등장한 것이 바로 CA라고 볼 수 있다.</p>
<p>X.509 인증서는 다음과 같은 주요 구성 요소를 포함한다:</p>
<ul>
<li><strong>Version</strong> 인증서의 버전. v3 가 가장 널리 사용되는 버전이다</li>
<li><strong>Serial Number</strong> 인증서를 식별하기 위한 고유 번호</li>
<li><strong>Algorithm Identifier</strong> 알고리즘 식별자</li>
<li><strong>Signature</strong> 서명</li>
<li><strong>Issuer</strong> 인증서를 발급한 CA의 이름</li>
<li><strong>Validity</strong> 인증서의 유효 기간<ul>
<li><strong>Not Before</strong> 유효 기간이 시작하는 날짜</li>
<li><strong>Not After</strong> 유효 기간이 끝나는 날짜</li>
</ul>
</li>
<li><strong>Subject</strong> 인증서를 소유하고 있는 개인, 조직, 장치 등의 이름</li>
<li><strong>Subject Public Key Info</strong> 소유자 공개 키 정보. 공개 키 알고리즘과 공개 키를 포함한다</li>
<li><strong>Issuer Unique Identifier (Optional)</strong> 증서의 발급자를 고유하게 식별하는 데 사용된다</li>
<li><strong>Subject Unique Identifier (Optional)</strong> 인증서의 주체를 고유하게 식별하는 데 사용된다</li>
<li><strong>Extensions</strong> <strong>(Optional)</strong> 키 사용법, 인증서 정책, 대체 이름 등 추가적인 정보를 제공한다.</li>
</ul>
<h3 id="openssl로-인증서-설정해-보기">OpenSSL로 인증서 설정해 보기</h3>
<p><strong>HAProxy가 설치되어 있다는 것을 전제로 한다.</strong></p>
<p>인증서의 경로는 /etc/haproxy/tls 에 있다.</p>
<pre><code class="language-bash">openssl genrsa -out test.com.key 2048
openssl req -new -key test.com.key -out test.com.csr
openssl x509 -req -days 365 -in test.com.csr -signkey test.com.key -out test.com.crt</code></pre>
<p>를 통해 crt 파일을 생성한다.</p>
<pre><code class="language-bash">openssl rsa -in test.com.key -text &gt; key.pem
openssl x509 -inform PEM -in test.com.crt &gt; crt.pem
openssl pkcs12 -export -in test.com.crt -inkey test.com.key -out cert.p12
openssl pkcs12 -in cert.p12 -nodes -out cert.pem</code></pre>
<p>.pem 파일로 만든다.</p>
<pre><code class="language-bash">vi /etc/haproxy/haporxy.cfg

global
ssl-default-bind-options no-sslv3
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
ssl-default-server-options no-sslv3
ssl-default-server-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
tune.ssl.cachesize 100000
tune.ssl.lifetime 600
tune.ssl.default-dh-param 2048

frontend https
bind *:443 ssl crt /etc/haproxy/tls/cert.pem
default_backend https_test
option forwardfor

backend https_test
balance roundrobin
server app1 192.168.0.26:80 check
server app2 192.168.0.36:80 check</code></pre>
<p>정상적으로 설정되었는지 확인한다.</p>
<pre><code class="language-bash">haproxy -f /etc/haproxy/haproxy.cfg -c

configure valid ok가 나오면 완료.</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform을 사용하여 GKE 프로비저닝 하기]]></title>
            <link>https://velog.io/@hsh_124/Terraform%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-GKE-%ED%94%84%EB%A1%9C%EB%B9%84%EC%A0%80%EB%8B%9D-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hsh_124/Terraform%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-GKE-%ED%94%84%EB%A1%9C%EB%B9%84%EC%A0%80%EB%8B%9D-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 19 Dec 2023 08:43:25 GMT</pubDate>
            <description><![CDATA[<p>GCP는 90일간 무료 300달러 크레딧을 제공한다. 하지만 보통 매니지드 쿠버네티스를 제공하는 시스템 (GCP의 경우에는 GKE)은 비용이 많이 들고, 이를 피하기 위해서 매번 클러스터를 새로 생성하는 것은 효율적이지 않다. 더욱 편한 실습 환경을 위해 Terraform을 통해 자동으로 클러스터를 생성할 수 있도록 했다.</p>
<h3 id="1-시작하기-전">1. 시작하기 전</h3>
<p>시작하기 전에, 해당 사항들을 충족하는지 확인한다.</p>
<ol>
<li>GCP Account</li>
<li>Google Cloud SDK (gcloud) 설치: GKE CLI를 제공한다.</li>
<li>GKE Cluster API 활성화: 프로젝트에 GKE Cluster API를 활성화한다. 콘솔이나 해당 명령어를 통해 gcloud에서 활성화할 수 있다. <code>gcloud services enable container.googleapis.com --project=PROJECT_ID</code></li>
<li>인증 및 자격 증명: Terraform이 GCP 계정에 액세스할 수 있도록 적절한 인증을 구성한다. Application Default Credentials (ADC) 또는 Service Account 키 파일을 사용할 수 있다. <a href="https://cloud.google.com/docs/authentication/provide-credentials-adc?hl=ko">참고</a></li>
<li>Terraform과 kubectl을 로컬 머신에 설치해야 한다.</li>
</ol>
<p><code>gcloud config list</code> 명령어를 통해 현재 구성 정보를 확인할 수 있다.</p>
<h3 id="2-gcp-계정에서-새로운-프로젝트-생성하기">2. GCP 계정에서 새로운 프로젝트 생성하기</h3>
<p>GCP 콘솔에서 새 프로젝트 버튼을 눌러 새로운 프로젝트를 생성한다.</p>
<h3 id="3-api-활성화">3. API 활성화</h3>
<p>Compute Engine API와 Kubernetes Engine API를 활성화한다. 콘솔에서 활성화하거나, 해당 명령어를 통해 gcloud에서 활성화할 수 있다.</p>
<pre><code class="language-bash">gcloud services enable container.googleapis.com # Kubernetes Engine
gcloud services enable compute.googleapis.com # Compute Engine</code></pre>
<h3 id="4-서비스-계정-생성">4. 서비스 계정 생성</h3>
<p>서비스마다 별도의 서비스 계정을 만드는 것이 좋다. Terraform 전용 서비스 계정을 생성하면, 적절하게 격리 및 권한 제어를 할 수 있다.</p>
<ul>
<li>GCP 콘솔에서 서비스 계정 페이지로 이동한다. (IAM 및 관리자 -&gt; 서비스 계정)</li>
<li>서비스 계정 생성 버튼을 클릭하고 새로운 서비스 어카운트를 생성한다.</li>
<li>서비스 계정에 액세스 권한을 부여하고 생성 버튼을 누른다.</li>
<li>서비스 계정이 JSON 형태로 컴퓨터에 저장된다. 이 파일에는 서비스 계정의 자격 증명이 포함되어 있다. (서비스 계정 -&gt; 작업 -&gt; 키 관리 -&gt; 키 추가 -&gt; 새 키 만들기 -&gt; JSON)</li>
</ul>
<h3 id="5-gcloud-sdk-설치-및-초기화-kubectl-terraform-설치">5. gcloud SDK 설치 및 초기화, kubectl, Terraform 설치</h3>
<p>GCP에서 Terraform 작업을 실행하고 GKE 클러스터를 생성하려면 Google Cloud SDK (gcloud) 를 설치해야 한다. <a href="https://cloud.google.com/sdk/docs/install">설치</a></p>
<p>설치한 후에는, <code>gcloud init</code> 명령어를 사용하여 초기화한다.</p>
<p><code>gcloud auth login</code>을 통해 당신의 구글 계정을 gcloud SDK와 연동할 수 있다.</p>
<pre><code class="language-shell">gcloud auth list
gcloud auth application-default login
gcloud config set account &#39;ACCOUNT&#39;
gcloud config set project &#39;PROJECT_ID&#39;</code></pre>
<p>쿠버네티스를 활용하기 위해서는 kubectl을 설치해야 한다. <a href="https://kubernetes.io/docs/tasks/tools/">설치</a> </p>
<p>Terraform도 설치해 주자. <a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli">설치</a></p>
<h3 id="6-terraform-파일-생성">6. Terraform 파일 생성</h3>
<p>생성한 폴더 내에 Terraform 파일을 생성해 준다. <a href="https://registry.terraform.io/modules/terraform-google-modules/kubernetes-engine/google/latest">kubernetes-engine</a> 과 <a href="https://registry.terraform.io/modules/terraform-google-modules/network/google/latest">network</a> 모듈을 사용할 것이다.</p>
<pre><code>terraform {
  # provider 파일을 통해 Terraform은 클라우드 공급자, SaaS 공급자 및 기타 API와 상호작용할 수 있다. 
  # Terraform이 프로젝트에서 사용할 특정 프로바이더와 그들의 버전을 정의하여, 구성의 일관성과 호환성을 보장한다.

  required_version = &quot;&gt;= 1.6&quot; # 최소 Terraform 버전을 1.6.0 이상으로 지정
  required_providers {
    google = {
      source  = &quot;hashicorp/google&quot; # Google 프로바이더의 출처를 HashiCorp 레지스트리에서 &quot;hashicorp/google&quot;으로 지정
      version = &quot;~&gt; 5.7.0&quot;         # Google 프로바이더의 버전을 5.7.0으로 지정하며, 호환 가능한 마이너 업데이트는 허용
    }
    local = {
      source  = &quot;hashicorp/local&quot;
      version = &quot;2.4.0&quot;
    }
    kubernetes = {
      source  = &quot;hashicorp/kubernetes&quot;
      version = &quot;2.24.0&quot;
    }
  }
}</code></pre><pre><code class="language-tf"># backend.tf를 통해 Terraform의 state를 원격 저장소에 저장하도록 설정할 수 있다.
# GCP를 사용할 것이므로, GCS(Google Cloud Storage) 버킷으로 설정해 준다. 혹은 Terraform Cloud를 사용할 수도 있다.

terraform {
  backend &quot;gcs&quot; {
    bucket = &quot;k8s-standard-bucket-tfstate&quot;
    prefix = &quot;terraform/state&quot;
  }
}</code></pre>
<p>backend.tf에 사용할 GCS 생성을 위하여 bash 스크립트를 작성하였다.</p>
<pre><code class="language-bash">#!/bin/bash

BUCKET_NAME=&quot;BUCKET_NAME&quot;
PROJECT_ID=&quot;PROJECT_ID&quot;
LOCATION=&quot;ASIA-NORTHEAST3&quot;

# GCS 버킷 생성

if gsutil ls -p $PROJECT_ID gs://$BUCKET_NAME 2&gt;&amp;1 | grep -q &#39;NotFound&#39;; then
    echo &quot;Creating bucket $BUCKET_NAME&quot;
    gsutil mb -p $PROJECT_ID -l $LOCATION gs://$BUCKET_NAME
    gsutil versioning set on gs://$BUCKET_NAME
else
    echo &quot;Bucket $BUCKET_NAME already exists.&quot;
fi</code></pre>
<p><img src="https://velog.velcdn.com/images/hsh_124/post/50709919-bf76-43f8-89b1-dd94907e609a/image.png" alt=""></p>
<pre><code class="language-tf"># vpc-network.tf
# VPC와 서브넷을 프로비저닝한다.
module &quot;vpc&quot; {
  source       = &quot;terraform-google-modules/network/google&quot;
  version      = &quot;~&gt; 8.0&quot;
  project_id   = var.project_id
  network_name = &quot;${var.network}-${var.env_name}&quot;
  routing_mode = &quot;GLOBAL&quot;
  subnets = [
    {
      subnet_name           = &quot;subnet-01&quot;
      subnet_ip             = &quot;10.10.20.0/24&quot;
      subnet_region         = var.region
      subnet_private_access = &quot;true&quot;
      subnet_flow_logs      = true
      description           = &quot;For private subnet&quot;
    }
  ]
  # 클러스터 내부의 Pods와 Services에 IP 주소를 할당하기 위한 추가적인 IP 범위
  secondary_ranges = {
    subnet-01 = [
      {
        range_name    = var.ip_range_pods_name
        ip_cidr_range = &quot;10.20.0.0/16&quot;
      },
      {
        range_name    = var.ip_range_services_name
        ip_cidr_range = &quot;10.21.0.0/16&quot;
      }
    ]
  }
}</code></pre>
<pre><code># main.tf
# GKE 클러스터와, 별도로 관리되는 노드 풀을 생성한다.
data &quot;google_client_config&quot; &quot;default&quot; {}

provider &quot;kubernetes&quot; {
  host                   = &quot;https://${module.gke.endpoint}&quot;
  token                  = data.google_client_config.default.access_token
  cluster_ca_certificate = base64decode(module.gke.ca_certificate)
}

resource &quot;google_service_account&quot; &quot;default&quot; {
  # google_service_account 리소스의 account_id에는 서비스 계정의
  # 전체 이메일 주소가 아니라 고유 ID 부분만 포함해야 한다.
  account_id   = &quot;k8s-standard-architecture&quot;
  project      = var.project_id
  display_name = &quot;K8s Standard Architecture Service Account&quot;
  description  = &quot;Service account for K8s standard architecture&quot;
}

module &quot;gke&quot; {
  source                     = &quot;terraform-google-modules/kubernetes-engine/google//modules/private-cluster&quot;
  project_id                 = var.project_id
  name                       = var.cluster_name
  region                     = var.region
  zones                      = var.zones
  network                    = module.vpc.network_name
  subnetwork                 = module.vpc.subnets_names[0]
  ip_range_pods              = var.ip_range_pods_name
  ip_range_services          = var.ip_range_services_name
  http_load_balancing        = true
  network_policy             = true
  private_cluster_config     = true
  horizontal_pod_autoscaling = true
  filestore_csi_driver       = false
  enable_private_endpoint    = false
  enable_private_nodes       = true
  master_ipv4_cidr_block     = &quot;10.0.0.0/28&quot;
  deletion_protection        = false 
  # Terraform 등 다른 방법으로 클러스터가 삭제되는 것을 허용하지 않는다면 true로 설정

  node_pools = [
    {
      name            = &quot;default-node-pool&quot;
      machine_type    = &quot;e2-standard-4&quot;
      node_locations  = &quot;asia-northeast3-b,asia-northeast3-c&quot;
      min_count       = 2
      max_count       = 5
      disk_size_gb    = 30
      spot            = false
      image_type      = &quot;COS_CONTAINERD&quot;
      disk_type       = &quot;pd-standard&quot;
      logging_variant = &quot;DEFAULT&quot;
      auto_repair     = true
      auto_upgrade    = true
      service_account = &quot;k8s-standard-architecture@${var.project_id}.iam.gserviceaccount.com&quot;
    }
  ]

  node_pools_oauth_scopes = {
    all = [
      &quot;https://www.googleapis.com/auth/logging.write&quot;,
      &quot;https://www.googleapis.com/auth/monitoring&quot;,
      &quot;https://www.googleapis.com/auth/trace.append&quot;,
      &quot;https://www.googleapis.com/auth/service.management.readonly&quot;,
      &quot;https://www.googleapis.com/auth/devstorage.read_only&quot;,
      &quot;https://www.googleapis.com/auth/servicecontrol&quot;
    ]
  }

  node_pools_labels = {
    all = {}
    default-node-pool = {
      default-node-pool = true
    }
  }

  node_pools_metadata = {
    all = {}
    default-node-pool = {
      #노드가 종료될 때, kubectl drain 명령어를 사용하여 
      # 파드를 안전하게 제거하고 필요하다면 다른 노드로 재스케줄링 되도록 하는 스크립트를 실행한다.
      shutdown-script                 = &quot;kubectl --kubeconfig=/var/lib/kubelet/kubeconfig drain --force=true --ignore-daemonsets=true --delete-local-data \&quot;$HOSTNAME\&quot;&quot;
      node-pool-metadata-custom-value = &quot;default-node-pool&quot;
    }
  }

  node_pools_taints = {
    all = []

    default-node-pool = [
      {
        key    = &quot;default-node-pool&quot;
        value  = true
        effect = &quot;PREFER_NO_SCHEDULE&quot;
      }
    ]
  }

  node_pools_tags = {
    all = []

    default-node-pool = [
      &quot;default-node-pool&quot;,
    ]
  }

  depends_on = [module.vpc, google_service_account.default]
}</code></pre><pre><code># auth.tf
# GKE 클러스터에 대한 인증을 구성한다.

module &quot;gke_auth&quot; {
  source               = &quot;terraform-google-modules/kubernetes-engine/google//modules/auth&quot;
  version              = &quot;29.0.0&quot;
  project_id           = var.project_id
  location             = module.gke.location
  cluster_name         = module.gke.name
  use_private_endpoint = true
  depends_on           = [module.gke] 
  # module.gke에 의존한다. 즉 gke에 설정된 클러스터가 생성된 후 인증 정보가 설정된다.
}

# kubeconfig를 로컬 시스템에 저장하여, 쿠버네티스 클러스터에 접근할 수 있도록 한다.

resource &quot;local_file&quot; &quot;kubeconfig&quot; {
  content  = module.gke_auth.kubeconfig_raw
  filename = &quot;kubeconfig-${var.env_name}&quot;
}</code></pre><pre><code># variables.tf
# 변수를 정의한다.

# Common variables

variable &quot;project_id&quot; {
  type        = string
  description = &quot;project id&quot;
  default     = &quot;&quot;

  validation {
    condition     = length(var.project_id) &gt; 0
    error_message = &quot;The project_id must not be empty.&quot;
  }
}

variable &quot;region&quot; {
  type        = string
  description = &quot;cluster region&quot;
  default     = &quot;&quot;

  validation {
    condition     = can(regex(&quot;^asia-northeast3&quot;, var.region))
    error_message = &quot;The region must start with &#39;asia-northeast3&#39;.&quot;
  }
}

variable &quot;env_name&quot; {
  type        = string
  description = &quot;The environment for the GKE cluster&quot;
  default     = &quot;dev&quot;
}

# Cluster variables


variable &quot;cluster_name&quot; {
  type        = string
  description = &quot;name of cluster&quot;
  default     = &quot;&quot;
}

variable &quot;network&quot; {
  type        = string
  description = &quot;The VPC network&quot;
  default     = &quot;gke-network&quot;
}

variable &quot;zones&quot; {
  type        = list(string)
  description = &quot;to host cluster in&quot;
  default     = [&quot;asia-northeast3-a&quot;, &quot;asia-northeast3-b&quot;, &quot;asia-northeast3-c&quot;]

  validation {
    condition     = length(var.zones) &gt; 0
    error_message = &quot;At least one zone must be specified.&quot;
  }
}

variable &quot;ip_range_pods_name&quot; {
  type        = string
  description = &quot;The secondary ip ranges for pods&quot;
  default     = &quot;subnet-01-pods&quot;
}

variable &quot;ip_range_services_name&quot; {
  type        = string
  description = &quot;The secondary ip ranges for services&quot;
  default     = &quot;subnet-01-services&quot;
}

# 변수를 정의한다. 정의한 변수에 값을 주입하기 위해서는 terraform.tfvars 파일을 이용한다.</code></pre><pre><code># terraform.tfvars
project_id   = &quot;spry-framework-406704&quot;
cluster_name = &quot;k8s-standard-architecture&quot;
region       = &quot;asia-northeast3&quot;

# variables.tf 파일에 정의된 변수에 값을 주입한다. 보통 Variables = Value 형태로 정의한다.</code></pre><pre><code># output.tf
# 리소스에서 생성된 데이터를 출력하는데 사용된다. output을 사용하면 원하는 정보를 개발환경에서도 바로 확인할 수 있다.

output &quot;cluster_id&quot; {
  description = &quot;cluster id&quot;
  value       = module.gke.cluster_id
}

output &quot;cluster_name&quot; {
  description = &quot;cluster name&quot;
  value       = module.gke.name
}

output &quot;ca_certificate&quot; {
  sensitive   = true
  description = &quot;Cluster ca certificate (base64 encoded)&quot;
  value       = module.gke.ca_certificate
}

output &quot;cluster_type&quot; {
  description = &quot;cluster type (regional / zonal)&quot;
  value       = module.gke.type
}

output &quot;cluster_location&quot; {
  description = &quot;Cluster location (region if regional cluster, zone if zonal cluster)&quot;
  value       = module.gke.location
}

output &quot;horizontal_pod_autoscaling_enabled&quot; {
  description = &quot;Whether horizontal pod autoscaling enabled&quot;
  value       = module.gke.horizontal_pod_autoscaling_enabled
}

output &quot;http_load_balancing_enabled&quot; {
  description = &quot;Whether http load balancing enabled&quot;
  value       = module.gke.http_load_balancing_enabled
}

output &quot;master_authorized_networks_config&quot; {
  description = &quot;Networks from which access to master is permitted&quot;
  value       = module.gke.master_authorized_networks_config
}

output &quot;cluster_region&quot; {
  description = &quot;Cluster region&quot;
  value       = module.gke.region
}

output &quot;cluster_zones&quot; {
  description = &quot;List of zones in which the cluster resides&quot;
  value       = module.gke.zones
}

output &quot;cluster_endpoint&quot; {
  sensitive   = true
  description = &quot;Cluster endpoint&quot;
  value       = module.gke.endpoint
}

output &quot;min_master_version&quot; {
  description = &quot;Minimum master kubernetes version&quot;
  value       = module.gke.min_master_version
}

output &quot;logging_service&quot; {
  description = &quot;Logging service used&quot;
  value       = module.gke.logging_service
}

output &quot;monitoring_service&quot; {
  description = &quot;Monitoring service used&quot;
  value       = module.gke.monitoring_service
}

output &quot;master_version&quot; {
  description = &quot;Current master kubernetes version&quot;
  value       = module.gke.master_version
}

output &quot;network_policy_enabled&quot; {
  description = &quot;Whether network policy enabled&quot;
  value       = module.gke.network_policy_enabled
}

output &quot;node_pools_names&quot; {
  description = &quot;List of node pools names&quot;
  value       = module.gke.node_pools_names
}

output &quot;node_pools_versions&quot; {
  description = &quot;Node pool versions by node pool name&quot;
  value       = module.gke.node_pools_versions
}

output &quot;service_account&quot; {
  description = &quot;The service account to default running nodes as if not overridden in node_pools.&quot;
  value       = module.gke.service_account
}</code></pre><h3 id="7-gke-cluster-프로비전">7. GKE Cluster 프로비전</h3>
<p>파일을 모두 저장한 후, Terraform workspace를 초기화한다. <code>terraform init</code> 명령어를 이용하여 필요한 provider와 plugins를 다운로드한다.</p>
<pre><code class="language-shell">terraform init</code></pre>
<p><img src="https://velog.velcdn.com/images/hsh_124/post/72830800-99c2-4e2f-b23f-dac7b82e8f5f/image.png" alt=""></p>
<p><code>terraform plan</code> 명령어를 실행하여 실행 계획을 생성한다.</p>
<pre><code class="language-shell">terraform plan</code></pre>
<p>만들어진 plan이 적절한 것 같아 보이면 계획을 적용한다</p>
<pre><code class="language-shell">terraform apply -auto-approve</code></pre>
<p><img src="https://velog.velcdn.com/images/hsh_124/post/9287592d-d8b5-4aeb-bda4-719d7b7132fc/image.png" alt=""></p>
<p>생성이 완료되었다! GCP 콘솔을 통해 만들어진 클러스터를 확인한다.</p>
<p><img src="https://velog.velcdn.com/images/hsh_124/post/f4cae922-fb1c-47f8-bcef-6205fb3c5478/image.png" alt=""></p>
<h3 id="8-kubectl-명령어를-사용하여-클러스터에-접속하기">8. kubectl 명령어를 사용하여 클러스터에 접속하기</h3>
<p>gcloud 명령어를 사용하여 kubectl이 GKE 클러스터에 접속할 수 있도록 kubectl을 구성한다.</p>
<pre><code class="language-bash">gcloud container clusters get-credentials 클러스터 이름 --region 리전 </code></pre>
<p><img src="https://velog.velcdn.com/images/hsh_124/post/820f186c-7f3d-4eea-8bbb-b43511d79884/image.png" alt=""></p>
<p>오류가 발생하는 경우, Google Kubernetes Engine(GKE) 클러스터에 kubectl을 사용하여 인증하는 데 필요한 gke-gcloud-auth-plugin이 설치되지 않았다는 것이다. 해당 플러그인을 설치하기 위해서는 cmd를 관리자 권한으로 실행한 후 커맨드를 입력한다.</p>
<pre><code class="language-bash">gcloud components install gke-gcloud-auth-plugin
gcloud components list # 설치 확인</code></pre>
<p><img src="https://velog.velcdn.com/images/hsh_124/post/be54464f-4be3-4318-a440-06d1e10853dc/image.png" alt=""></p>
<p>kubectl 커맨드를 사용하여 설정이 잘 되었는지 확인한다.</p>
<pre><code class="language-bash">❯ k get node
NAME                                                  STATUS   ROLES    AGE   VERSION
gke-k8s-standard-arc-default-node-poo-c689d9fc-1bcq   Ready    &lt;none&gt;   31m   v1.27.3-gke.100
gke-k8s-standard-arc-default-node-poo-c689d9fc-l8vr   Ready    &lt;none&gt;   31m   v1.27.3-gke.100
gke-k8s-standard-arc-default-node-poo-f4dbcfa0-79g2   Ready    &lt;none&gt;   31m   v1.27.3-gke.100
gke-k8s-standard-arc-default-node-poo-f4dbcfa0-qrwh   Ready    &lt;none&gt;   31m   v1.27.3-gke.100</code></pre>
<h3 id="9-클러스터-삭제">9. 클러스터 삭제</h3>
<p>더 이상 클러스터가 필요하지 않다면, 크레딧을 줄이기 위해 클러스터를 삭제한다.</p>
<pre><code>terraform destroy -auto-approve</code></pre><h3 id="10-오류-정리">10. 오류 정리</h3>
<p>클러스터를 생성 및 삭제하면서 경험한 오류 및 참고사항을 정리한다.</p>
<h4 id="1-서비스-어카운트-미생성-오류">1. 서비스 어카운트 미생성 오류</h4>
<p>서비스 어카운트를 생성하지 않아서 생긴 오류다....</p>
<pre><code>resource &quot;google_service_account&quot; &quot;default&quot; {
  # google_service_account 리소스의 account_id에는 서비스 계정의 전체 이메일 주소가 아니라 고유 ID 부분만 포함해야 한다.
  account_id   = &quot;k8s-standard-architecture&quot;
  project      = var.project_id
  display_name = &quot;K8s Standard Architecture Service Account&quot;
  description  = &quot;Service account for K8s standard architecture&quot;
}</code></pre><p>main.tf에 해당 블럭을 추가하고</p>
<pre><code>  depends_on = [module.vpc, google_service_account.default]</code></pre><p>service account에 대한 종속성을 추가하면 해결된다.</p>
<h4 id="2-enable_private_endpoint를-true로-설정했을-때의-오류">2. enable_private_endpoint를 true로 설정했을 때의 오류</h4>
<p><code>enable_private_endpoint = true</code>는 private endpoint를 설정하는 것이 아니라 private_endpoint &quot;만&quot; 접속할 수 있도록 만드는 변수이다. 퍼블릭 엔드포인트도 생성하기 위해서는 <code>enable_private_endpoint</code>를 <code>true</code>가 아니라 <code>false</code>로 설정해야 한다. <code>true</code>로 설정하면 퍼블릭 IP로 접속 자체가 되지 않는다.</p>
<blockquote>
<p><code>enable_private_endpoint</code> (Optional) - When <code>true</code>, the cluster&#39;s private endpoint is used as the cluster endpoint and access through the public endpoint is disabled. When <code>false</code>, either endpoint can be used. This field only applies to private clusters, when <code>enable_private_nodes</code> is <code>true</code>.</p>
</blockquote>
<p>프라이빗 엔드포인트의 활성화 자체는 <code>private_cluster_config</code>에 의해 관리된다. 이 설정은 클러스터가 프라이빗 모드로 작동할 것인지를 결정한다. <code>private_cluster_config</code>를 <code>true</code>로 설정하면, 클러스터는 프라이빗 네트워크 내에서만 접근 가능한 상태가 된다. 이 설정은 프라이빗 엔드포인트의 사용을 가능하게 하지만, <code>enable_private_endpoint</code> 설정에 따라 퍼블릭 엔드포인트의 사용 여부가 결정된다. <code>enable_private_endpoint</code>는 클러스터가 퍼블릭 엔드포인트를 통한 접근을 허용할지를 결정한다.</p>
<ol>
<li><p><code>enable_private_endpoint = true</code>: 이 설정은 클러스터가 프라이빗 엔드포인트를 사용하도록 하며, 퍼블릭 엔드포인트를 통한 접근을 비활성화한다. 즉, 외부 인터넷에서의 직접 접근이 차단된다.</p>
</li>
<li><p><code>enable_private_endpoint = false</code>: 이 설정은 클러스터가 프라이빗 엔드포인트와 퍼블릭 엔드포인트 모두를 사용할 수 있도록 한다. 이 경우, 클러스터는 내부 네트워크와 외부 인터넷 양쪽에서 접근 가능하다.</p>
</li>
</ol>
<p>프라이빗 엔드포인트만으로 접속하게 하고 싶다면, 이와 같이 설정하면 된다.</p>
<pre><code>  # 프라이빗 네트워크를 설정하기 위해서는 마스터 인증 네트워크도 설정해야 한다
  master_authorized_networks = [
    {
      # 쿠버네티스 마스터 엔드포인트에 액세스할 수 있도록 허용하는 cidr block. 
      # 보안을 위해 필요한 곳만 허용하도록 설정하는 것이 좋다.(일반적으로 조직의 네트워크 또는 클러스터를 관리하는 특정 머신의 IP 주소).
      # 예를 들어, 조직 네트워크의 IP 범위가 192.168.0.0/16인 경우, cidr_block = &quot;192.168.0.0/16&quot; 로 변경할 수 있다.
      # 이렇게 하면 192.168.0.0~192.168.255.255 범위의 IP 주소를 가진 머신만 쿠버네티스 마스터 엔드포인트에 액세스할 수 있다. 
      cidr_block   = &quot;0.0.0.0/0&quot;
      display_name = &quot;Allow from everywhere&quot;
    },
  ]</code></pre><h3 id="참고-링크">참고 링크</h3>
<p><a href="https://medium.com/cloud-native-daily/provision-gke-cluster-with-terraform-ee787457f3e">medium</a>
<a href="https://cloud.google.com/docs/terraform?hl=ko">GCP 공식 문서</a>
<a href="https://registry.terraform.io/">Terraform Registry</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[다른 리전과 네트워크 연결하기]]></title>
            <link>https://velog.io/@hsh_124/%EB%8B%A4%EB%A5%B8-%EB%A6%AC%EC%A0%84%EA%B3%BC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hsh_124/%EB%8B%A4%EB%A5%B8-%EB%A6%AC%EC%A0%84%EA%B3%BC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 16 Nov 2023 09:49:29 GMT</pubDate>
            <description><![CDATA[<ol>
<li>다른 리전으로 이동한다. (여기서는 런던)</li>
<li>런던 리전에서 VPC를 생성한다. (단일 퍼블릭 서브넷으로)</li>
<li>다시 서울 리전으로 이동해, 피어링 연결 생성한다.</li>
<li>서울-런던으로 피어링을 생성한다.</li>
<li>피어링 연결 승인을 위해 런던으로 이동하여, 피어링 연결 탭에서 요청을 수락한다.</li>
<li>이를 위해선 라우팅 테이블 수정이 필요하므로, 지금 내 라우팅 테이블 수정을 클릭</li>
<li>런던 리전의 라우팅 편집 페이지에서 서울 리전의 VPC 대역(10.0.0.0/16)을 입력하고 Peering Connection을 선택한다. VPC Peering 연결을 선택 후 라우팅 저장 버튼을 클릭한다.</li>
<li>서울 리전의 라우팅 편집 페이지에서 런던 리전의 VPC 대역(20.0.0.0/16)을 입력하고 반복한다.</li>
<li>런던 리전의 EC2 생성 페이지에서 생선한 VPC를 선택하고 퍼블릭 IP 자동 할당을 활성화한 후 EC2 인스턴스를 생성한다.</li>
<li>인스턴스의 세부 항목 페이지에서 보안 그룹 링크를 클릭한다.</li>
<li>인바운드 규칙에서 규칙 추가를 한다. 유형: 모든 트래픽, 소스: 10.0.0.0/16 후 저장.</li>
<li>서울 리전에서도 같은 방식으로 20.0.0.0/16을 저장한다.</li>
<li>서울 리전으로 접속해 런던 리전의 private ip로 ping을 보내 통신이 가능한지 확인한다.</li>
<li>런던 리전에서도 반복.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] GitHub Actions를 통한 CI]]></title>
            <link>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-GitHub-Actions%EB%A5%BC-%ED%86%B5%ED%95%9C-CI</link>
            <guid>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-GitHub-Actions%EB%A5%BC-%ED%86%B5%ED%95%9C-CI</guid>
            <pubDate>Tue, 25 Apr 2023 07:58:55 GMT</pubDate>
            <description><![CDATA[<p>프로젝트에서 CI 도구로는 GitHub Actions, CD 도구로는 ArgoCD를 이용했다. GitHub Actions를 이용한 이유는 촉박한 시간에서 쉽게 설정할 수 있었기 때문이다. 또한 Jenkins는 사용해 본 적이 있기 때문에 GitHub Actions를 통해서 CI를 설정하였다. 푸시하면 연동된 ECR로 자동으로 업로드되도록 코드를 설정하였다.</p>
<h2 id="코드">코드</h2>
<p>backend</p>
<pre><code class="language-yaml">name: CI

on:
  push:
    branches:
      - main
env:
  AWS_REGION: ap-northeast-2
  REGISTRY: &lt;id&gt;.dkr.ecr.ap-northeast-2.amazonaws.com
  REPOSITORY: &lt;repo_name&gt;
  IMAGE_TAG: back
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-region: ${{ env.AWS_REGION }}
          aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}

      - name: login to ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag and push docker image to Amazon
        id: build-image
        env:
          ECR_REGISTY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          docker build -t ${{ env.REGISTRY }}/${{env.REPOSITORY}}:${{env.IMAGE_TAG}} .
          docker push ${{ env.REGISTRY}}/${{env.REPOSITORY}}:${{env.IMAGE_TAG}}</code></pre>
<p>frontend</p>
<pre><code class="language-yaml">name: CI

on:
  push:
    branches:
      - master
env:
  AWS_REGION: ap-northeast-2
  REGISTRY: &lt;id&gt;.dkr.ecr.ap-northeast-2.amazonaws.com
  REPOSITORY: &lt;repo_name&gt;
  IMAGE_TAG: front
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-region: ${{ env.AWS_REGION }}
          aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}

      - name: login to ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag and push docker image to Amazon
        id: build-image
        env:
          ECR_REGISTY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          docker build -t ${{ env.REGISTRY }}/${{env.REPOSITORY}}:${{env.IMAGE_TAG}} .
          docker push ${{ env.REGISTRY}}/${{env.REPOSITORY}}:${{env.IMAGE_TAG}}</code></pre>
<h2 id="회고">회고</h2>
<p>이후 push가 되었는데도 웹페이지에 제대로 반영이 되어있지 않아 살펴보았다. Deployment.yaml의 imagePullPolicy가 제대로 작동하지 않는 문제였다. 공식문서를 살펴보면, tag가 latest일때만 imagePullPolicy가 default로 설정되고, 다른 태그일 경우에는 따로 imagePullPolicy를 always로 설정해 줘야 한다. 설정한 후 웹 페이지에 push된 최근 이미지가 잘 반영되는 것을 확인하였다.</p>
<p>또한, aws의 secret key를 그대로 github의 secret에 저장하면 보안 문제가 발생할 수 있다는 것을 알게 되었는데, 이는 RBAC로 해결 가능한 부분이라고 한다. 나중에 시간이 되면 RBAC를 적용해 볼 예정이다.</p>
<p>그리고 ECR에서 자꾸 untagged가 늘어나 좀 더러워지는 것 같은 면이 있는데, 이것은 버전을 자동으로 바꾸게 하는 것으로 해결 할 수 있는 것으로 보인다. 하지만 태그를 유지하면서 자동으로 이전 버전을 삭제하게는 못 하는 건가? 이 쪽은 더 찾아봐야 할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[로컬 환경에서 쿠버네티스 실행하기]]></title>
            <link>https://velog.io/@hsh_124/%EB%A1%9C%EC%BB%AC-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hsh_124/%EB%A1%9C%EC%BB%AC-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 24 Apr 2023 10:05:49 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 위하여 EKS를 이용하기 전, 쿠버네티스 실습은 대부분 로컬 환경에서 진행되었다.
Vagrant와 Ansible, VirtualBox를 사용하였고, 실행하기까지의 과정을 정리해서 올려 본다.</p>
<h2 id="1-vagrant를-이용한-가상머신-배포">1. Vagrant를 이용한 가상머신 배포</h2>
<p>먼저, Vagrant를 이용하여 가상머신을 생성한다.</p>
<pre><code class="language-ruby">Vagrant.configure(&quot;2&quot;) do |config|
  config.vm.define &quot;control-plane&quot; do |config|
    config.vm.box = &quot;ubuntu/focal64&quot;
      config.vm.provider &quot;virtualbox&quot; do |vb|
        vb.name = &quot;control-plane&quot;
        vb.memory = &quot;2048&quot;
        vb.cpus = 2
      end
      config.vm.hostname = &quot;control-plane&quot;
      config.vm.network &quot;private_network&quot;, ip: &quot;192.168.56.11&quot;
  end

  config.vm.define &quot;worker1&quot; do |config|
    config.vm.box = &quot;ubuntu/focal64&quot;
      config.vm.provider &quot;virtualbox&quot; do |vb|
        vb.name = &quot;worker1&quot;
        vb.memory = &quot;1536&quot;
        vb.cpus = 1
      end
      config.vm.hostname = &quot;worker1&quot;
      config.vm.network &quot;private_network&quot;, ip: &quot;192.168.56.21&quot;
  end

  config.hostmanager.enabled = true
  config.hostmanager.manage_guest = true

  config.vm.provision &quot;shell&quot;, inline: &lt;&lt;-SHELL
    sed -i &#39;s/archive.ubuntu.com/mirror.kakao.com/g&#39; /etc/apt/sources.list
    sed -i &#39;s/security.ubuntu.com/mirror.kakao.com/g&#39; /etc/apt/sources.list
    sed -i &#39;s/PasswordAuthentication no/PasswordAuthentication yes/g&#39; /etc/ssh/sshd_config
    systemctl restart ssh
  SHELL

end</code></pre>
<p>컴퓨터의 사양 문제로 worker node를 한 개만 생성하였으므로, worker node의 갯수를 추가하고 싶으면 worker1 아래에 추가하면 된다. 작성한 Vagrantfile을</p>
<pre><code class="language-bash">vagrant up</code></pre>
<p>명령어를 통해 배포하면, VirtualBox에 Master와 Worker Node가 배포되어 있는 것을 확인할 수 있다.</p>
<h2 id="2-control-plane-설정과-kubespray-설치">2. control-plane 설정과 kubespray 설치</h2>
<p>control-plane에 ssh를 통해 접속하여, key 기반의 인증을 control-plane과 모든 worker node에 설정해줘야 한다.</p>
<p>그 전에, /etc/hosts에서 각각의 ip를 등록해 주자.</p>
<pre><code class="language-bash">vi /etc/hosts

127.0.0.1 localhost
127.0.1.1 worker2
192.168.56.11 control-plane
192.168.56.21 worker1</code></pre>
<pre><code class="language-bash">ssh-keygen
ssh-copy-id worker1 #worker node는 worker node에서 실행해야 한다.</code></pre>
<p>이후 git과 python을 설치해 준다.</p>
<pre><code class="language-bash">sudo apt-get update
sudo apt install python3 python3-pip
sudo apt install git</code></pre>
<p>kubespray를 git clone으로 설치하고, pip로 패키지를 설치한다.</p>
<pre><code class="language-bash">git clone --single-branch --branch=release-2.19 https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
sudo pip3 install -r requirements.txt</code></pre>
<h2 id="3-ansible-설정">3. Ansible 설정</h2>
<p>먼저, mycluster 폴더를 수정해서 사용한다.</p>
<pre><code class="language-bash">cp -rfp inventory/sample/ inventory/mycluster
vi inventory/mycluster/inventory.ini</code></pre>
<pre><code class="language-bash">[all]
worker1 ansible_host=192.168.56.21  ip=192.168.56.21 ansible_sudo_pass=&#39;vagrant&#39;
control-plane ansible_host=192.168.56.11  ip=192.168.56.11

[all:vars]
ansible_python_interpreter=/usr/bin/python3

[kube_control_plane]
control-plane

[etcd]
control-plane

[kube_node]
worker1

[calico_rr]

[k8s_cluster:children]
kube_control_plane
kube_node
calico_rr</code></pre>
<p>이후, addons.yaml 파일을 수정해 준다.</p>
<pre><code class="language-bash">root@control-plane:~/kubespray/inventory/mycluster/group_vars/k8s_cluster# ls
addons.yml          k8s-net-canal.yml    k8s-net-kube-ovn.yml     k8s-net-weave.yml
k8s-cluster.yml     k8s-net-cilium.yml   k8s-net-kube-router.yml
k8s-net-calico.yml  k8s-net-flannel.yml  k8s-net-macvlan.yml</code></pre>
<pre><code class="language-bash">ingress_nginx_enabled: true
metrics_server_enabled: true
metallb_enabled: true
metallb_ip_range:
  - &quot;192.168.56.50-192.168.56.99&quot;
metallb_protocol: &quot;layer2&quot;</code></pre>
<p>k8s-cluster.yml 에서 해당 줄을 수정한다.</p>
<pre><code class="language-bash">kube_proxy_strict_arp: true</code></pre>
<p>마지막으로 쿠버네티스 클러스터를 배포(오랜 시간이 걸린다) 하고, vagrant 사용자에 kubectl 권한을 부여한다.</p>
<pre><code class="language-bash">ansible-playbook -i inventory/mycluster/inventory.ini -b cluster.yml -v
mkdir ~/.kube
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $USER:$USER ~/.kube/config</code></pre>
<h2 id="오류">오류</h2>
<p>Vagrant 설치 과정에서 문제가 있었는지, IP가 잡히지 않아 쿠버네티스 배포 과정에서 문제가 있었다. 이는 수동으로 IP를 설정해서 해결하였다. (ubuntu 20.04 기준)</p>
<p>/etc/netplan/의 yaml 파일을 dhcp에서 고정 IP로 변경해 준다.</p>
<pre><code class="language-yaml">network:
  ethernets:
    enp0s8:
      addresses:
        - 192.168.56.21/24
      gateway4: 192.168.0.1
      nameservers:
        addresses:
            - 8.8.8.8
  version: 2</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] ECR로 이미지 푸시하기]]></title>
            <link>https://velog.io/@hsh_124/AWS-ECR%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%91%B8%EC%8B%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hsh_124/AWS-ECR%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%91%B8%EC%8B%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 29 Mar 2023 10:20:53 GMT</pubDate>
            <description><![CDATA[<p>프로젝트 중에 docker hub를 사용할까 하다가, 아무래도 AWS를 사용하는 만큼 ECR로 업로드를 해서 사용하면 좋을 것 같아서 ECR로 이미지 푸시하는 법을 찾아보게 되었다.</p>
<ol>
<li>docker build</li>
</ol>
<pre><code>docker build . -t &lt;id&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com/&lt;ecr repository name&gt;:&lt;tag&gt;</code></pre><p>ID에는 aws 12자짜리 account id(aws sts get-caller-identity의 account)
region에는 region(한국이니까 보통 ap-northeast-2)
ecr repository name에는 콘솔에서 생성한 repository name
tag에는 달고싶은 tag를 달면 된다.</p>
<ol start="2">
<li>ecr 로그인</li>
</ol>
<pre><code class="language-bash">aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin &lt;id&gt;.dkr.ecr.ap-northeast-2.amazonaws.com</code></pre>
<p>aws configure로 로그인 되어 있으면 안 해도 되는줄 알고 하지 않았었는데 오류가 떴다.
그래서 이걸 적용해보고 다시 해봤더니 오류가 발생하지 않았다 ㅎ..
암튼?? 로그인에 성공하면 login succeed라고 나온다.</p>
<ol start="3">
<li>docker push</li>
</ol>
<pre><code class="language-bash">docker push &lt;id&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com/&lt;ecr repository name&gt;:&lt;tag&gt;</code></pre>
<p>성공적으로 실행되면 </p>
<pre><code class="language-yaml">0987654321ba: Pushed
1234567890ab: Pushed
...</code></pre>
<p>이런식으로 뜬다. 떨어질 때 까지 기다리면 ECR 콘솔에서 확인할 수 있다.</p>
<pre><code class="language-yaml">containers:
      - name: name
        image: &lt;id&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com/&lt;ecr repository name&gt;:&lt;tag&gt;</code></pre>
<p>deployment.yaml에는 이렇게 작성하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] 6일차 - 테스트 웹 (일단) 작성 완료]]></title>
            <link>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-6%EC%9D%BC%EC%B0%A8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9B%B9-%EC%9D%BC%EB%8B%A8-%EC%9E%91%EC%84%B1-%EC%99%84%EB%A3%8C</link>
            <guid>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-6%EC%9D%BC%EC%B0%A8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9B%B9-%EC%9D%BC%EB%8B%A8-%EC%9E%91%EC%84%B1-%EC%99%84%EB%A3%8C</guid>
            <pubDate>Sat, 25 Mar 2023 15:12:22 GMT</pubDate>
            <description><![CDATA[<p>Fast API와 React를 이용한 테스트 웹 사이트의 코드를 일단 완성하였다. 또한, DB와의 연결을 위해 준비하고 Deployment만 하면 되도록 쿠버네티스의 구성요소들을 세팅하였다.</p>
<p>Front</p>
<pre><code class="language-javascript">import React, { useState, useEffect } from &quot;react&quot;;

function App() {
  const [users, setUsers] = useState([]);

  useEffect(() =&gt; {
    fetch(&quot;/users&quot;)
      .then((response) =&gt; response.json())
      .then((data) =&gt; setUsers(data));
  }, []);

  return (
    &lt;div&gt;
      &lt;h1&gt;Users&lt;/h1&gt;
      &lt;ul&gt;
        {users.map((user) =&gt; (
          &lt;li key={user.id}&gt;{user.name}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p>Back</p>
<pre><code class="language-python">from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import mysql.connector
import os

app = FastAPI()

origins = [
    &quot;http://localhost&quot;
    &quot;http://localhost:3000&quot;
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=[&quot;*&quot;],
    allow_headers=[&quot;*&quot;],
)

db_address = os.environ[&#39;DB_ADDRESS&#39;]
db_port = os.environ[&#39;DB_PORT&#39;]
db_name = os.environ[&#39;DB_NAME&#39;]
db_password = os.environ[&#39;DB_PASSWORD&#39;]
db_dbname = os.environ[&#39;DB_DBNAME&#39;]


@app.get(&quot;/&quot;)
def read_root():
    return {&quot;Hello&quot;: &quot;World&quot;}


@app.get(&quot;/users&quot;)
def read_users():
    mydb = mysql.connector.connect(
        host=db_address,
        port=db_port,
        user=db_name,
        password=db_password,
        database=db_dbname
    )

    cursor = mydb.cursor()
    cursor.excute(&quot;SELECT * FROM users&quot;)
    result = cursor.fetchall()
    return result</code></pre>
<pre><code class="language-javascript">FROM python:3.9-slim-buster

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD uvicorn --host=0.0.0.0 --port 8080 main:app</code></pre>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: book-deploy
spec:
  selector:
    matchLabels:
      tier: back
  replicas: 2
  template:
    metadata:
      labels:
        tier: back
    spec:
      containers:
      - name: book-back
        image: &lt;Image&gt;
        resources:
          requests:
            memory: &quot;128Mi&quot;
            cpu: &quot;500m&quot;            
          limits:
            memory: &quot;128Mi&quot;
            cpu: &quot;500m&quot;
        ports:
        - containerPort: 8080
        env:
          - name: DB_ADDRESS
            valueFrom:
              secretKeyRef:
                name: web-rds-secret
                key: address
          - name: DB_PORT
            valueFrom:
              secretKeyRef:
                name: web-rds-secret
                key: port
          - name: DB_NAME
            valueFrom:
              secretKeyRef:
                name: web-rds-secret
                key: name
          - name: DB_PASSWORD
            valueFrom:
              secretKeyRef:
                name: web-rds-secret
                key: password
          - name: DB_DBNAME
            valueFrom:
              secretKeyRef:
                name: web-rds-secret
                key: dbname
        volumeMounts:
          - mountPath: /book_db
            name: efs-volume
            resources:
              requests:
                cpu: &quot;250m&quot;
                memory: &quot;64Mi&quot;
              limits:
                cpu: &quot;250m&quot;
                memory: &quot;64Mi&quot;
        volumes:
          - name: efs-volume
            persistentVolumeClaim:
              claimName: efs-pvc</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] 5일차 - RDS 수정과 EFS 설정]]></title>
            <link>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-5%EC%9D%BC%EC%B0%A8-RDS-%EC%88%98%EC%A0%95%EA%B3%BC-EFS-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-5%EC%9D%BC%EC%B0%A8-RDS-%EC%88%98%EC%A0%95%EA%B3%BC-EFS-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 23 Mar 2023 17:45:09 GMT</pubDate>
            <description><![CDATA[<p>RDS를 프라이빗 서브넷에서만 두었더니 외부에서 액세스하기 어려운 문제가 발생하였다. 원래는 배스천 호스트를 사용해서 접속하게 하려고 하였지만, 개발하시는 분들이 EC2 이용해서 RDS를 한번 더 접속하기 귀찮으실까봐... 그냥 프라이빗 서브넷 중 하나를 퍼블릭 서브넷으로 바꾸는 것으로 해결하였다.</p>
<p>그리고 EFS 를 설정하였다. EFS가 Multi AZ를 지원하기 때문이다. 처음에 EBS로 하지 말고 클러스터를 만들 때 부터 EFS로 하는게 맞았겠지만, 그 때는 이 차이점을 몰랐어서.. 따로 세팅해 주었다.</p>
<p><a href="https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/efs-csi.html">공식 문서</a> 를 참고해서 진행하였다.</p>
<pre><code class="language-bash">curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json

aws iam create-policy \
    --policy-name AmazonEKS_EFS_CSI_Driver_Policy \
    --policy-document file://iam-policy-example.json

eksctl create iamserviceaccount \
    --cluster my-cluster \
    --namespace kube-system \
    --name efs-csi-controller-sa \
    --attach-policy-arn arn:aws:iam::111122223333:policy/AmazonEKS_EFS_CSI_Driver_Policy \
    --approve \
    --region region-code


helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
helm repo update

helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
    --namespace kube-system \
    --set image.repository=602401143452.dkr.ecr.region-code.amazonaws.com/eks/aws-efs-csi-driver \
    --set controller.serviceAccount.create=false \
    --set controller.serviceAccount.name=efs-csi-controller-sa</code></pre>
<p>image.repository의 컨테이너 주소는 링크되어있는 <a href="https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/add-ons-images.html">문서</a>를 통해 바꿨다. 나는 ap-northeast-2를 사용하고 있으니 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com 로 사용하면 된다.</p>
<pre><code class="language-bash">vpc_id=$(aws eks describe-cluster \
    --name my-cluster \
    --query &quot;cluster.resourcesVpcConfig.vpcId&quot; \
    --output text)

cidr_range=$(aws ec2 describe-vpcs \
    --vpc-ids $vpc_id \
    --query &quot;Vpcs[].CidrBlock&quot; \
    --output text \
    --region region-code)

security_group_id=$(aws ec2 create-security-group \
    --group-name MyEfsSecurityGroup \
    --description &quot;My EFS security group&quot; \
    --vpc-id $vpc_id \
    --output text)

aws ec2 authorize-security-group-ingress \
    --group-id $security_group_id \
    --protocol tcp \
    --port 2049 \
    --cidr $cidr_range

file_system_id=$(aws efs create-file-system \
    --region region-code \
    --performance-mode generalPurpose \
    --query &#39;FileSystemId&#39; \
    --output text)

kubectl get nodes</code></pre>
<p>k get nodes를 통해 노드의 주소를 얻었다면, </p>
<pre><code class="language-bash">aws ec2 describe-subnets \
    --filters &quot;Name=vpc-id,Values=$vpc_id&quot; \
    --query &#39;Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}&#39; \
    --output table</code></pre>
<p>해당 명령어를 사용하여 노드의 IP주소가 속해 있는 서브넷 아이디를 </p>
<pre><code class="language-bash">aws efs create-mount-target \
    --file-system-id $file_system_id \
    --subnet-id subnet-EXAMPLEe2ba886490 \
    --security-groups $security_group_id</code></pre>
<p>의 subnet-id 부분에 넣으면 된다. 나는 노드가 두 개이므로 두 번 진행하였다.</p>
<p>그리고 스토리지클래스와 PVC도 작성을 완료하였다.</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-pvc
spec:
  resources:
    requests:
      storage: 1Gi
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc</code></pre>
<pre><code class="language-yaml">apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
parameters:
  provisioningMode: efs-ap
  fileSystemId: fs-046fc26e91f93b2c1
  directoryPerms: &quot;700&quot;</code></pre>
<p>내일은 오전에 테스트 웹 코드 작성을 완료하고, github actions를 이용한 CI까지 해볼 예정이다. 물론 웹과 RDS의 연동이 잘.. 된다면의 말이지만...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] 4일차 - RDS 끝!]]></title>
            <link>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-4%EC%9D%BC%EC%B0%A8-RDS-%EB%81%9D</link>
            <guid>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-4%EC%9D%BC%EC%B0%A8-RDS-%EB%81%9D</guid>
            <pubDate>Tue, 21 Mar 2023 16:28:17 GMT</pubDate>
            <description><![CDATA[<p>마침내 RDS Cloudformation 스택 배포를 완료하였다. 시간 날 때 VPC를 직접 입력하지 말고 자동으로 가져오게 하는 방법이 있을지 확인해 봐야 할 것 같다.</p>
<p>이 것 외에도 테스트용 프론트엔드와 백엔드 파일의 제작을 시작하였다. 간단하게 DB를 사용할 수 있는 정도로만 만들어서, DB와 연결이 잘 되어 있는지, 접속은 잘 할 수 있는지를 확인해 볼 것이다. 또한 ArgoCD를 이용한 배포와 Github Workflows를 이용한 CI 작업 역시 시작해야 한다.</p>
<p>웹 페이지가 완성되기 전에 인프라를 완료해서 완성되면 배포만 하면 할 수 있도록 하고 싶은데, 경험상 이 배포할 때 오류가 정말 많이 터지기 때문에... 그것을 수정할 시간을 벌기 위해서라도 최대한 빨리 인프라 작업을 끝내야 할 것 같다. 그리고 logging쪽에도 따로 하고 싶은게 있으니 그것을 위해서라도 내가 맡은 인프라 구축 작업을 최대한 빠르게 끝내는 것이 중요하다!</p>
<pre><code class="language-yaml">AWSTemplateFormatVersion: 2010-09-09

Resources:
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: vpc-049fd765256442cd7
      CidrBlock: 192.168.192.0/19
      AvailabilityZone: ap-northeast-2a

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: vpc-049fd765256442cd7
      CidrBlock: 192.168.230.0/19
      AvailabilityZone: ap-northeast-2b

  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: subnet group for rds
      DBSubnetGroupName: web-db-subnet-group
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2

  DBInstance:
    Type: AWS::RDS::DBInstance
    DependsOn: DBSubnetGroup
    Properties:
      DBName: webDB
      DBInstanceIdentifier: web-db
      AllocatedStorage: 20
      DBInstanceClass: db.t3.medium
      Engine: mysql
      MasterUsername: root
      MasterUserPassword: rootpassword1
      VPCSecurityGroups:
        - sg-0910431509fba56a0
        - sg-0c8c916ce0943aa26
        - sg-0bc951ad09afb9df3
      DBSubnetGroupName: !Ref DBSubnetGroup
      MultiAZ: true
      PubliclyAccessible: true
      StorageType: gp3</code></pre>
<p>이 간단한 걸 구성하는데 오류가 얼마나 터진 건지..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] 3일차 - create stack만 몇십 번]]></title>
            <link>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%9D%BC%EC%B0%A8-create-stack%EB%A7%8C-%EB%AA%87%EC%8B%AD-%EB%B2%88</link>
            <guid>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%9D%BC%EC%B0%A8-create-stack%EB%A7%8C-%EB%AA%87%EC%8B%AD-%EB%B2%88</guid>
            <pubDate>Mon, 20 Mar 2023 14:03:59 GMT</pubDate>
            <description><![CDATA[<p>말 그대로다.. 하루종일 rds cloudformation만 붙잡고 있었다. 분명 이렇게 오래걸릴 일이 아니었는데 어째서? 그냥 cli로 만들걸 그랬나 싶은 생각이 들지 않는 것도 아니지만, 이렇게 한 번 만들어 두면 나중에 훨씬 편할 것 같아서 계속 오류를 수정하기로 했다.</p>
<p>오류 목록은 다음과 같았다.</p>
<ol>
<li><pre><code class="language-bash">Error parsing parameter &#39;--template-body&#39;:
Unable to load paramfile (CreateRDS.yaml), text contents could not be decoded.
If this is a binary file, please use the fileb:// prefix instead of the file:// prefix.</code></pre>
</li>
</ol>
<p>이건.. 잘 모르겠다...? parsing할 수 없어서 생긴 에러라고 하는데, 좀 있다가 다시 하니까 에러가 뜨지 않았다. 저걸 생성했을 때 팀원분이 잠깐 eks 클러스터를 지우셨어서 오류가 뜬 것 같기도 하다.</p>
<pre><code class="language-bash">aws cloudformation validate-template --template-body file://test.yml</code></pre>
<p>찾아보니까 이렇게 template의 오류를 체크할 수 있는 것 같다.</p>
<ol start="2">
<li><pre><code class="language-bash">Properties validation failed for resource DBInstance with message: #
/DBName: failed validation constraint for keyword [pattern]</code></pre>
</li>
</ol>
<p>DBName이 patterns에 부합하지 않아서 생긴 문제이다. 찾아보니 DBName은</p>
<pre><code class="language-yaml">        &quot;DBName&quot;: {
            &quot;Default&quot;: &quot;mydb&quot;,
            &quot;Description&quot;: &quot;My database&quot;,
            &quot;Type&quot;: &quot;String&quot;,
            &quot;MinLength&quot;: &quot;1&quot;,
            &quot;MaxLength&quot;: &quot;64&quot;,
            &quot;AllowedPattern&quot;: &quot;[a-zA-Z][a-zA-Z0-9]*&quot;,
            &quot;ConstraintDescription&quot;: &quot;Must begin with a letter and contain onlyalphanumeric characters.&quot;
        },</code></pre>
<p>으로 정의되어 있어서 AllowedPattern대로 다시 이름을 바꾸어 오류를 해결했다.</p>
<p>3.</p>
<pre><code class="language-bash">Resource handler returned message: &quot;DBSubnetGroup &#39;web-db-subnet-group&#39; not found.
(Service: Rds, Status Code: 404, Request ID: 75f008d5-014c-4d8d-a858-fb42fe181c67)&quot;
(RequestToken: a50db435-d8c4-5b33-74eb-383e386f1c5d, HandlerErrorCode: NotFound)</code></pre>
<p>DBInstance가 생성될 때 DBSubnetGroup이 생성되지 않아서 생긴 문제이다. yaml파일에 입력되어 있는 순서대로 생성되는 줄 알았는데, 역시 완벽하지는 않은 것 같다. 이미 subnetgroup은 subnet에 종속되어 있어서 마지막에 생기기도 하고.. 그래서 DBInstance를 DBSubnetGroup에 종속시키려고 시도해보았다.</p>
<p>4.</p>
<pre><code class="language-bash">Properties validation failed for resource DBInstance with message:#
: extraneous key [DependsOn] is not permitted</code></pre>
<p>시도의 결과로 오류가 났는데, properties 안에 들어가는게 아니라 dbinstance 바로 아래에 들어가야 하는 것이었다. properties 밖으로 빼 줘서 오류를 해결했다. + 종속도 확인했다.</p>
<p>5.</p>
<pre><code class="language-bash">Resource handler returned message: &quot;Invalid storage size for
engine name mysql and storage type gp2: 1
(Service: Rds, Status Code: 400, Request ID: 05ac5cc6-6854-4321-8f9b-f9bc16beda25)&quot;
(RequestToken: df385daa-3347-c849-f7d3-4cccb5c536d8, HandlerErrorCode: InvalidRequest)</code></pre>
<p>gp2타입의 MySQL 용량은 최소 5GiB여야 한다고 한다. 근데 생각해보니 gp3가 더 나을 것 같아서 gp3로 변경했다. 그랬더니 다시 오류가 떴고, 찾아보니 gp3의 경우 최소 용량이 GiB라고 한다. 그걸로 수정해서 오류를 해결하였다.</p>
<p>6.</p>
<pre><code class="language-bash">Resource handler returned message: &quot;The DB subnet group doesn&#39;t
meet Availability Zone (AZ) coverage requirement.
Current AZ coverage: ap-northeast-2b. Add subnets to cover at least 2 AZs. 
(Service: Rds, Status Code: 400, Request ID: 3b3903e0-0617-4e57-984a-aedbf7d656e6)&quot; 
(RequestToken: 71b86e8e-bd79-5159-3d96-08053892da99, HandlerErrorCode: InvalidRequest)</code></pre>
<p>MultiAZ를 true 로 설정했을 때, 서브넷이 AZ의 범위 요구 사항을 충족하지 않는다는 에러이다. 여기서 오늘 시간이 끝나서 클러스터가 내려가 멈춰 있는 상태인데, 내일 서브넷의 IP를 바꿔가면서 확인해 볼 예정이다.</p>
<pre><code class="language-bash">aws ec2 describe-subnets --filters &quot;Name=vpc-id,Values=&lt;VPC-ID&gt;&quot;</code></pre>
<p>명령어를 사용하면 확인해 볼 수 있다고 하므로, 해당 명령어를 사용해볼 것이다.</p>
<p>오늘 만난 오류의 목록은 다음과 같았다. 솔직히 다음엔 무슨 오류가 뜰 지 모르겠다.. 하지만 열심히 찾아보다 보면 다 해결책이 있어서 다행인 것 같다. 내일 RDS와 EKS 연동까지 마무리되면 좋겠다.</p>
<p>내일은 RDS 스택을 일단 마무리하고, Fast API와 React를 이용해 테스트 웹 코드를 만들고 Deploy해 봐야겠다. 또, 여럿이서 작업을 하다 보니 폴더가 충돌하는 경우가 생겨서 폴더 트리를 만들어 봐야할 것 같다. 예전에는 이 트리를 굳이 만들어야 하나 생각했는데, 안 만들고 작업하니까 꼭 있어야 할 것 같다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] 2일차]]></title>
            <link>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Fri, 17 Mar 2023 12:32:30 GMT</pubDate>
            <description><![CDATA[<p>시작하기 전에, 팀원분 컴퓨터에서 eks 클러스터를 만들었기 때문에 정작 내 컴퓨터에서 어떻게 접속할지 모르겠어서... 잠시 헤맸었는데 이렇게 접속하면 되는 것 같다.</p>
<pre><code class="language-bash">aws eks update-kubeconfig --region &lt;region&gt; --name &lt;cluster name&gt;</code></pre>
<p>rds cloudformation stack을 만들었다. clusterconfig가 아니라 cloudformation이라서 많이 고민 했었는데, 여러가지를 보고 생각해본 결과로는 이렇게 만들면 될 것 같았다.</p>
<pre><code class="language-yaml">AWSTemplateFormatVersion: 2010-09-09

Resources:
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: &lt;VPC ID&gt;
      CidrBlock: &lt;Private Subnet 1 CIDR&gt;

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: &lt;VPC ID&gt;
      CidrBlock: &lt;Private Subnet 2 CIDR&gt;

  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: subnet group for rds
      DBSubnetGroupName: web-db-subnet-group
      SubnetIds:
        - !Ref PrivateSubnet1 #원래오류남
        - !Ref PrivateSubnet2

  DBInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBName: web-db
      DBInstanceIdentifier: web-db
      AvailabilityZone: ap-northeast-2
      AllocatedStorage: 1
      DBInstanceClass: db.t3.medium
      Engine: mysql
      MasterUsername: 
      MasterUserPassword:
      VPCSecurityGroups:
        - vpc-sg-id #eks 클러스터와 같은 값을 입력하면 EKS클러스터가 속해있는 보안그룹과 같은 곳에 속한다.
      DBSubnetGroupName: web-db-subnet-group
      MultiAZ: true</code></pre>
<p>VSCode의 yaml파일에서는 !Ref 부분이 오류가 있다고 나왔지만, 공식 문서를 살펴 보니 저렇게 쓰는게 맞고 yaml 파일 형식이라서 VSCode에서 오류가 나는 것 같다. 다음주에는 rds를 올리고 팀원 분들에게 액세스 가능한지를 물어봐야 할 것 같다. + 지금 생각해 보니 웹 개발 하시는 분들이 rds를 사용하셔야 하는데 아마 퍼블릭 액세스를 허용해두어야 할 듯 싶다..? 퍼블릭 서브넷을 하나 만들고 개발 완료하면 없애던가 배스천 호스트를 구현하던가 할 듯...</p>
<p>그것 말고도 전에 만들어두었던 HPA나 PVC같은 yaml 파일들을 미리 github에 push해 두었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] 1일차]]></title>
            <link>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@hsh_124/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Thu, 16 Mar 2023 18:14:09 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘-한-일">오늘 한 일</h2>
<p>별거 없음.. 착수보고 완성하고 eks yaml 파일 만들었다. 끝!!</p>
<pre><code class="language-yaml">apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: book-network
  region: ap-northeast-2
  version: &quot;1.24&quot; #쿠버네티스의 버전

# AZ(가용 영역), 설정하지 않으면 3개의 가용 영역에서 랜덤으로 생성된다
availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;,  &quot;ap-northeast-2c&quot;]

# IAM OIDC &amp; Service Account
iam: #AWS IAM
  withOIDC: true #Open ID Connection
  serviceAccounts: #미리 생성할 서비스 어카운트
    - metadata: #인그레스용
        name: aws-load-balancer-controller #계정
        namespace: kube-system #네임스페이스
      wellKnownPolicies: #AWS Role, SA에 역할을 붙인다
        awsLoadBalancerController: true
    - metadata: #볼륨용
        name: ebs-csi-controller-sa
        namespace: kube-system
      wellKnownPolicies: #AWS Role, SA에 역할을 붙인다
        ebsCSIController: true
    - metadata: #클러스터 오토스케일러용
        name: cluster-autoscaler
        namespace: kube-system
      wellKnownPolicies: #AWS , SA에 역할을 붙인다
        autoScaler: true

# Managed Node Groups, EKS는 관리형이므로 컨트롤플레인을 AWS에서 관리해 준다.
managedNodeGroups:
  # On-Demand Instance
  - name: mynodes-t3
    instanceType: t3.medium #인스턴스 타입
    minSize: 1 #최소 용량
    desiredCapacity: 2 #기본 용량
    maxSize: 3 #최대 용량
    privateNetworking: true #프라이빗 네트워크로 구성
    #ssh:
      #allow: true
      #publicKeyPath: ./keypair/myeks.pub #프라이빗 네트워크에 있으므로 굳이?
    availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;, &quot;ap-northeast-2c&quot;] #가용 영역
    iam: #역할들을 ec2 인스턴스에도 세팅해준다.
      withAddonPolicies:
        autoScaler: true
        albIngress: true
        cloudWatch: true
        ebs: true 
        imageBuilder: true
        externalDNS: true
        certManager: true

# CloudWatch Logging
cloudWatch: #컨트롤플레인 로깅
  clusterLogging:
    enableTypes: [&quot;*&quot;] #모든 로그를 cloudwatch에 담는다.</code></pre>
<p>생각해보니 SQL용 role도 부여해야 할 것 같다,, <a href="https://eksctl.io/usage/iam-policies/">참고</a> 아침에 일어나서 수정하기로.. 다시 보니까 수정할거 많은 듯</p>
<pre><code class="language-bash">aws eks describe-cluster --name book-network --region ap-northeast-2 --query cluster.resourcesVpcConfig</code></pre>
<h2 id="내일-할-것">내일 할 것</h2>
<p>생각해보니 aws sql을 인프라쪽에서 파일을 만들어서 웹 쪽에 넘겨줘야 하는 것 같아서... 그거 말하고 DB부터 만들 예정. 그러면 순서는 이렇게 되는 것 같다.</p>
<ol start="0">
<li>cluster 파일 수정하기</li>
<li>DB yaml 파일 만들기</li>
<li>CI / CD 파이프라인 생성</li>
<li>로깅 생성</li>
<li>파이프라인 생성</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>