<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>ssunw</title>
    <link>https://ssunw.tistory.com/</link>
    <description>Site Reliability Engineer</description>
    <language>ko</language>
    <pubDate>Thu, 21 May 2026 00:25:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ssunw</managingEditor>
    <image>
      <title>ssunw</title>
      <url>https://tistory1.daumcdn.net/tistory/5152794/attach/ee98a7fbcd4b48c38ce81860a926f7bf</url>
      <link>https://ssunw.tistory.com</link>
    </image>
    <item>
      <title>001. Loki Logging Stack 구성 요소</title>
      <link>https://ssunw.tistory.com/entry/001-Loki-Logging-Stack-%EA%B5%AC%EC%84%B1-%EC%9A%94%EC%86%8C</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Loki 개요 (Loki Overview)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki는 Prometheus에서 영감을 받아 만들어진 수평 확장형(horizontally scalable), 고가용성(highly available), 멀티테넌트(multi-tenant) 로그 집계 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Prometheus가 pull 기반 메트릭을 중심으로 설계된 것과 달리, Loki는 push 기반으로 로그를 수집하며 비용 효율성과 확장성을 핵심 목표로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki는 기존 로그 시스템처럼 로그 본문(log content)을 색인하지 않는다. 대신 로그 스트림별 메타데이터(label)만 색인하고, 실제 로그 데이터는 압축된 chunk 형태로 S3/GCS 같은 오브젝트 스토리지에 저장한다. 이 구조 덕분에 인덱스가 매우 작고 운영 비용이 낮아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 스트림(log stream)은 동일한 label 셋을 공유하는 로그 집합을 의미한다. 효율적인 쿼리를 위해서는 적절한 label 설계가 매우 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Loki Logging Stack 구성 요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Loki 기반 로깅 환경은 다음 세 가지 요소로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Agent (Promtail 또는 Grafana Alloy 등)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그를 수집(scrape)&lt;/li&gt;
&lt;li&gt;label을 붙여 스트림으로 구성&lt;/li&gt;
&lt;li&gt;HTTP API를 통해 Loki 서버로 push&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Loki 서버&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그 ingest 및 저장&lt;/li&gt;
&lt;li&gt;쿼리 수행&lt;/li&gt;
&lt;li&gt;단일 노드, Simple Scalable, Microservices 세 가지 모드 중 하나로 배포 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Grafana&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그 시각화&lt;/li&gt;
&lt;li&gt;쿼리 실행(LogQL)&lt;/li&gt;
&lt;li&gt;CLI 기반(LogCLI, Loki API) 조회도 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Loki 주요 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 확장성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Raspberry Pi 단일 노드부터 하루 페타바이트 규모까지 확장&lt;/li&gt;
&lt;li&gt;읽기/쓰기 경로를 분리하여 개별적으로 스케일 아웃&lt;/li&gt;
&lt;li&gt;필요 시 Kubernetes 마이크로서비스 형태로 각 컴포넌트 분리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 멀티테넌시&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 Loki 인스턴스를 여러 테넌트가 공유&lt;/li&gt;
&lt;li&gt;테넌트 간 로그/쿼리는 완전히 격리&lt;/li&gt;
&lt;li&gt;Agent 단계에서 tenant ID를 설정하여 활성화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 타사 에이전트 지원&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 외부 로그 수집기가 plugin 형태로 Loki에 로그 전송&lt;/li&gt;
&lt;li&gt;기존 로깅 파이프라인을 유지하면서도 Loki를 도입 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 효율적인 저장 구조&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;chunk 기반 고압축 저장&lt;/li&gt;
&lt;li&gt;로그 본문을 색인하지 않아 인덱스 크기가 작음&lt;/li&gt;
&lt;li&gt;오브젝트 스토리지를 메인 저장소로 사용하여 비용 효율성 확보&lt;/li&gt;
&lt;li&gt;SSD/HDD 중심의 로컬 저장 방식 대비 운영 복잡성 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. LogQL&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PromQL과 유사한 문법&lt;/li&gt;
&lt;li&gt;로그 조회뿐만 아니라 로그 기반 메트릭 생성 가능&lt;/li&gt;
&lt;li&gt;로그 기반 Alerting 구성에 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Alerting&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Loki Ruler가 쿼리를 지속적으로 평가&lt;/li&gt;
&lt;li&gt;결과에 따라 경보(Alert) 또는 액션 실행&lt;/li&gt;
&lt;li&gt;Prometheus Alertmanager 또는 Grafana Alerting과 연동 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. Grafana 생태계 통합&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Grafana, Mimir, Tempo와 자연스럽게 연동&lt;/li&gt;
&lt;li&gt;로그&amp;middot;메트릭&amp;middot;트레이스 상호 연관 분석이 용이해 e2e Observability 확보 가능&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>O11y</category>
      <category>lgtm</category>
      <category>LOKI</category>
      <category>o11y</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/289</guid>
      <comments>https://ssunw.tistory.com/entry/001-Loki-Logging-Stack-%EA%B5%AC%EC%84%B1-%EC%9A%94%EC%86%8C#entry289comment</comments>
      <pubDate>Thu, 4 Dec 2025 10:32:28 +0900</pubDate>
    </item>
    <item>
      <title>LinkedIn</title>
      <link>https://ssunw.tistory.com/notice/287</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.linkedin.com/in/seongwoo-choi&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.linkedin.com/in/seongwoo-choi&lt;/a&gt;&lt;/p&gt;</description>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/notice/287</guid>
      <pubDate>Tue, 23 Jan 2024 23:10:57 +0900</pubDate>
    </item>
    <item>
      <title>쿠버네티스에서 동시성 제어와 리소스의 일관성을 보장하는 방법</title>
      <link>https://ssunw.tistory.com/entry/Concurrency-Control-and-Consistency-in-kubernetes</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;resourceVersion 개념&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는&amp;nbsp;최적화&amp;nbsp;된&amp;nbsp;Concurrency&amp;nbsp;를&amp;nbsp;달성하기&amp;nbsp;위해&amp;nbsp;resourcVersion&amp;nbsp;개념을&amp;nbsp;사용하며,&amp;nbsp;모든&amp;nbsp;쿠버네티스&amp;nbsp;리소스는&amp;nbsp;메타데이터에&amp;nbsp;resourceVersion&amp;nbsp;이라는&amp;nbsp;필드를&amp;nbsp;갖는다.&amp;nbsp;&lt;br&gt;&lt;br&gt;다시&amp;nbsp;말해,&amp;nbsp;resourceVersion&amp;nbsp;은&amp;nbsp;클라이언트가&amp;nbsp;kubernetes&amp;nbsp;오브젝트가&amp;nbsp;변경된&amp;nbsp;시점을&amp;nbsp;확인하는&amp;nbsp;데&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;내부&amp;nbsp;버전을&amp;nbsp;식별하는&amp;nbsp;문자열이다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;etcd 의 record&amp;nbsp;가&amp;nbsp;업데이트&amp;nbsp;될&amp;nbsp;때,&amp;nbsp;이전에&amp;nbsp;저장된&amp;nbsp;value&amp;nbsp;와&amp;nbsp;비교하여&amp;nbsp;크로스&amp;nbsp;체크를&amp;nbsp;하는데,&amp;nbsp;만약&amp;nbsp;매치되지&amp;nbsp;않은&amp;nbsp;value&amp;nbsp;일&amp;nbsp;경우&amp;nbsp;409&amp;nbsp;status&amp;nbsp;code&amp;nbsp;를&amp;nbsp;반환하며,&amp;nbsp;object&amp;nbsp;가&amp;nbsp;수정될&amp;nbsp;경우&amp;nbsp;API&amp;nbsp;서버에&amp;nbsp;의해&amp;nbsp;resourceVersion&amp;nbsp;이&amp;nbsp;변경된다.&lt;br&gt;&lt;br&gt;만약&amp;nbsp;PUT&amp;nbsp;작업(리소스&amp;nbsp;변경&amp;nbsp;시)의&amp;nbsp;경우,&amp;nbsp;시스템은&amp;nbsp;resourceVersion&amp;nbsp;의&amp;nbsp;현재&amp;nbsp;값이&amp;nbsp;지정된&amp;nbsp;값과&amp;nbsp;일치하는지&amp;nbsp;확인하여&amp;nbsp;읽기/수정/쓰기&amp;nbsp;라는&amp;nbsp;일련의&amp;nbsp;Cycle&amp;nbsp;에서&amp;nbsp;해당&amp;nbsp;리소스에&amp;nbsp;다른&amp;nbsp;변경(실패한&amp;nbsp;이력&amp;nbsp;말고&amp;nbsp;성공한&amp;nbsp;이력)이&amp;nbsp;없었는지&amp;nbsp;확인한다.&amp;nbsp;&lt;br&gt;&lt;br&gt;resourceVersion 은 &lt;a href=&quot;https://etcd.io/docs/v3.5/learning/api/#key-value-pair&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;etcd 의 mod_revision&lt;/span&gt;&lt;/a&gt;&amp;nbsp;에&amp;nbsp;의해&amp;nbsp;지원된다.&amp;nbsp;&lt;br&gt;&lt;br&gt;resourceVersion&amp;nbsp;의&amp;nbsp;값을&amp;nbsp;예측하는&amp;nbsp;방법은&amp;nbsp;일반적으로&amp;nbsp;API&amp;nbsp;서버로&amp;nbsp;GET&amp;nbsp;요청을&amp;nbsp;보내는&amp;nbsp;방법밖에&amp;nbsp;없다.&amp;nbsp;&lt;br&gt;GET&amp;nbsp;요청으로&amp;nbsp;값을&amp;nbsp;예측하는&amp;nbsp;방법은&amp;nbsp;반드시&amp;nbsp;클라이언트에서&amp;nbsp;불투명하게&amp;nbsp;처리되어야&amp;nbsp;하고,&amp;nbsp;서버로&amp;nbsp;수정되지&amp;nbsp;않은채&amp;nbsp;전달되어야&amp;nbsp;한다.&amp;nbsp;&lt;br&gt;&lt;br&gt;즉,&amp;nbsp;resourceVersion&amp;nbsp;은&amp;nbsp;namespace&amp;nbsp;나&amp;nbsp;kubernetes&amp;nbsp;상의&amp;nbsp;다른&amp;nbsp;리소스와&amp;nbsp;관련된&amp;nbsp;값이&amp;nbsp;아니라는&amp;nbsp;것이다.&lt;br&gt;&lt;br&gt;resouceVersion&amp;nbsp;의&amp;nbsp;값은&amp;nbsp;etcd&amp;nbsp;의&amp;nbsp;시퀀서와&amp;nbsp;값이&amp;nbsp;일치하도록만&amp;nbsp;되어&amp;nbsp;있지만,&amp;nbsp;나중에는&amp;nbsp;kind&amp;nbsp;나&amp;nbsp;namespace&amp;nbsp;별로&amp;nbsp;상태를&amp;nbsp;샤딩하거나&amp;nbsp;다른&amp;nbsp;스토리지&amp;nbsp;시스템으로&amp;nbsp;포팅하는&amp;nbsp;것처럼&amp;nbsp;resourceVersion&amp;nbsp;의&amp;nbsp;구현이&amp;nbsp;변경될&amp;nbsp;것으로&amp;nbsp;예상된다.&amp;nbsp;&lt;br&gt;&lt;br&gt;만약&amp;nbsp;PUT&amp;nbsp;요청을&amp;nbsp;통해&amp;nbsp;리소스를&amp;nbsp;변경하는&amp;nbsp;도중에&amp;nbsp;confilct&amp;nbsp;이&amp;nbsp;발생할&amp;nbsp;경우,&amp;nbsp;client&amp;nbsp;는&amp;nbsp;API&amp;nbsp;서버로&amp;nbsp;GET&amp;nbsp;요청을&amp;nbsp;통해&amp;nbsp;리소스를&amp;nbsp;다시&amp;nbsp;가져온&amp;nbsp;뒤,&amp;nbsp;변경&amp;nbsp;사항을&amp;nbsp;새로&amp;nbsp;적용한다.&amp;nbsp;&lt;br&gt;&lt;br&gt;이&amp;nbsp;메카니즘을&amp;nbsp;사용하면&amp;nbsp;여러&amp;nbsp;요청이&amp;nbsp;동시에&amp;nbsp;들어와&amp;nbsp;경합이&amp;nbsp;발생하더라도&amp;nbsp;문제&amp;nbsp;없이&amp;nbsp;해결할&amp;nbsp;수&amp;nbsp;있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;동시에 리소스를 변경하는 케이스&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;아래와&amp;nbsp;같은&amp;nbsp;시퀀스가&amp;nbsp;병렬로&amp;nbsp;발생하면,&amp;nbsp;Foo.Bar&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;변경&amp;nbsp;사항이나&amp;nbsp;Foo.Baz&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;변경&amp;nbsp;사항이&amp;nbsp;손실될&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;이를&amp;nbsp;막기&amp;nbsp;위해&amp;nbsp;나온&amp;nbsp;개념이&amp;nbsp;위에서&amp;nbsp;설명한&amp;nbsp;resourceVersion&amp;nbsp;이다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfUwor/btsDJn39xFH/2zRkhX2gkdRmKnh3CXtCnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfUwor/btsDJn39xFH/2zRkhX2gkdRmKnh3CXtCnk/img.png&quot; data-alt=&quot;동시에 Foo 를 변경하는 케이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfUwor/btsDJn39xFH/2zRkhX2gkdRmKnh3CXtCnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfUwor%2FbtsDJn39xFH%2F2zRkhX2gkdRmKnh3CXtCnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1278&quot; height=&quot;242&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;동시에 Foo 를 변경하는 케이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resourceVersion&amp;nbsp;을&amp;nbsp;지정할&amp;nbsp;경우,&amp;nbsp;누가&amp;nbsp;리소스에&amp;nbsp;값을&amp;nbsp;Write&amp;nbsp;하든,&amp;nbsp;리소스를&amp;nbsp;성공적으로&amp;nbsp;변경하든&amp;nbsp;API&amp;nbsp;서버는&amp;nbsp;크게&amp;nbsp;신경쓰지&amp;nbsp;않는다.&amp;nbsp;&lt;br&gt;&lt;br&gt;단지,&amp;nbsp;두&amp;nbsp;client&amp;nbsp;중에&amp;nbsp;하나가&amp;nbsp;실패하고&amp;nbsp;특정&amp;nbsp;client&amp;nbsp;의&amp;nbsp;Foo&amp;nbsp;가&amp;nbsp;저장된다는&amp;nbsp;것만을&amp;nbsp;알면&amp;nbsp;된다.&amp;nbsp;&lt;br&gt;&lt;br&gt;이런&amp;nbsp;성질을&amp;nbsp;이용하여&amp;nbsp;resourceVersion&amp;nbsp;은&amp;nbsp;캐싱이&amp;nbsp;됐을&amp;nbsp;때&amp;nbsp;읽기&amp;nbsp;후&amp;nbsp;쓰기&amp;nbsp;일관성을&amp;nbsp;위해&amp;nbsp;향후&amp;nbsp;다른&amp;nbsp;작업(ex.&amp;nbsp;GET,&amp;nbsp;DELETE)의&amp;nbsp;전제&amp;nbsp;조건으로&amp;nbsp;사용될&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;&lt;br&gt;&lt;br&gt;예를&amp;nbsp;들어,&amp;nbsp;&quot;Watch&quot;&amp;nbsp;작업은&amp;nbsp;API&amp;nbsp;서버에&amp;nbsp;쿼리&amp;nbsp;매개변수를&amp;nbsp;사용해서&amp;nbsp;특정&amp;nbsp;resourceVersion&amp;nbsp;을&amp;nbsp;갖는&amp;nbsp;값을&amp;nbsp;가져오고,&amp;nbsp;해당&amp;nbsp;리소스가&amp;nbsp;변경되어&amp;nbsp;resourceVersion&amp;nbsp;이&amp;nbsp;변경될&amp;nbsp;경우를&amp;nbsp;감지한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;활용&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;처럼&amp;nbsp;resourceVersion&amp;nbsp;을&amp;nbsp;잘&amp;nbsp;이해하면,&amp;nbsp;go-client&amp;nbsp;라이브러리를&amp;nbsp;사용하여&amp;nbsp;resourceVersion&amp;nbsp;이&amp;nbsp;변경되는&amp;nbsp;것을&amp;nbsp;감지하여&amp;nbsp;사이드카&amp;nbsp;컨테이너를&amp;nbsp;강제로&amp;nbsp;injection&amp;nbsp;하거나,&amp;nbsp;특정&amp;nbsp;annotaion&amp;nbsp;이나&amp;nbsp;label&amp;nbsp;을&amp;nbsp;강제로&amp;nbsp;injection&amp;nbsp;하는&amp;nbsp;등의&amp;nbsp;작업을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것으로&amp;nbsp;생각한다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/286</guid>
      <comments>https://ssunw.tistory.com/entry/Concurrency-Control-and-Consistency-in-kubernetes#entry286comment</comments>
      <pubDate>Sat, 20 Jan 2024 21:28:11 +0900</pubDate>
    </item>
    <item>
      <title>golang http 패키지</title>
      <link>https://ssunw.tistory.com/entry/golang-http-%ED%8C%A8%ED%82%A4%EC%A7%80</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;http.Servemux &amp;amp; http.HandleFunc&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ServeMux는 HTTP 요청 멀티플렉서이다. 여기서 멀티플렉서는 라우터, 컨트롤러랑 동일한 뜻을 갖고 있다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수신되는 각 요청의 URL 을 등록된 패턴과 비교하고, URL과 가장 일치하는 패턴의 핸들러를 호출한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC 패턴의 Controller 역할이라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 ServeMux는 URL Request Path 와 Host Header 를 임의로 처리하는데, 포트 번호를 제거하거나 반복되는 슬래시가 포함된 요청을 동등하고 깔끔한 URL로 리 다이렉션한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 server.go 에 작성된 http 패키지 코드이다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &amp;amp;defaultServeMux

var defaultServeMux ServeMux

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HandleFunc 메소드는 DefaultServeMux 의 HandleFunc 를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;mux := http.NewServeMux()
mux.HandleFunc(&quot;/&quot;, handlerFunc)

http.HandleFunc(&quot;/&quot;, handlerFunc)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http.HandlFunc() 메소드는 ServeMux 구조체의 메소드이다. 그래서 mux 를 새로 생성하여 HandleFunc 를 구현할 수도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;http.HandlerFunc&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 server.go 에 작성된 http 패키지 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http 패키지를 보면 HandlerFunc 라는 함수 타입을 선언했는데 해당 타입은 ResponseWriter 와 *Request 를 매개변수로 갖는 함수 타입이다. 즉 구조체 타입을 생성하는 것 말고도 함수 타입을 생성할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;함수 타입은 다른 타입과 동일하게 취급된다. 중요한 점이자 신기한 점은 함수 타입이 구조체 타입처럼 메소드를 가질 수 있다는 점이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 http.HandleFunc 타입은 스스로를 호출하는 ServeHTTP 메소드를 구현할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;http.HandleFunc 은 http.HandlerFunc 를 매개변수로 받는다. http.HandlerFunc 는 함수 타입이며, ServeHTTP 라는 메소드를 갖는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ServeHTTP 메소드를 구현하기 때문에 HandlerFunc 는 Handler 인터페이스이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;http.Handler 는 ServeHTTP(ResponseWriter, *Request) 메소드를 갖는 인터페이스 타입이다.&lt;/p&gt;
&lt;pre id=&quot;code_1701062065985&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pathHandler&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;http.HandlerFunc&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;타입의 메서드 시그니처와 일치하기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;HandlerFunc&lt;/b&gt;로 변환되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;router&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수에 할당된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 메서드 시그니처는 함수 또는 메서드의 형태를 설명하는 것으로 메서드의 이름, 매개변수 유형, 반환 유형 등을 포함한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;pathHandler 함수가 http.HandlerFunc 의 메서드 시그니처를 따르는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다시 말해 func(w http.ResponseWriter, r *http.Request) 와 같은 형태를 가지면 http.HandlerFunc 타입으로 변환되어 http.Handler 인터페이스를 구현하는데 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1701062110880&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func pathHandler(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case &quot;/&quot;:
		homeHandler(w, r)
	case &quot;/contact&quot;:
		contactHandler(w, r)
	default:
		pageNotFoundHandler(w, r)
	}
}

func main() {
  var router http.HandlerFunc
  router = pathHandler
  fmt.Println(&quot;Starting the server on :3000...&quot;)
  http.ListenAndServe(&quot;:3000&quot;, router)
}


func main() {
  fmt.Println(&quot;Starting the server on :3000...&quot;)
  // int(r) 이나 float64(a) 처럼 pathHandelr 를 변환하는 것이다.
  // 즉, router 변수를 선언하고 pathHandler 를 값으로 넣은 것을 
  // http.HandleFunc(pathHandler) 로 줄인 것
  http.ListenAndServe(&quot;:3000&quot;, http.HandlerFunc(pathHandler)) 
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;http.ListenAndServe 와 Interface&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 server.go 에 작성된 http 패키지 코드이다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

...

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
	server := &amp;amp;Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http.ListenAndServe 메소드는 Handler 인터페이스를 매개변수로 입력받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Handler 인터페이스는 ServeHttp(ResponseWriter, *Request) 메소드를 구현하면 Handler 인터페이스로 취급을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드처럼 Router 구조체를 만들고 해당 구조체가 ServeHttp(ResponseWriter, *Request) 메소드를 구현하여 Handler 인터페이스 타입이 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 http.ListenAndServe 메소드에 매개변수로 router 를 입력할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 Router 구조체에 필드를 추가하여 ServeHTTP 메소드에서 구조체 필드를 가져와 사용하는 등의 작업을 할 수 있다.(db 커넥션 등..)&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
)

type Router struct {
}

func (s Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case &quot;/&quot;:
		homeHandler(w, r)
	case &quot;/contact&quot;:
		contactHandler(w, r)
	default:
		pageNotFoundHandler(w, r)
	}
}

func main() {
	var router Router
	fmt.Println(&quot;Starting the server on :3000!&quot;)
	http.ListenAndServe(&quot;:3000&quot;, router)
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Golang</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/285</guid>
      <comments>https://ssunw.tistory.com/entry/golang-http-%ED%8C%A8%ED%82%A4%EC%A7%80#entry285comment</comments>
      <pubDate>Mon, 27 Nov 2023 14:23:21 +0900</pubDate>
    </item>
    <item>
      <title>Redis Eviction</title>
      <link>https://ssunw.tistory.com/entry/Redis-Eviction</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Redis에서 데이터를 저장할 때 메모리가 부족할 경우 &quot;eviction&quot;이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Eviction은 Redis가 메모리를 확보하기 위해 일부 데이터를 제거하는 프로세스를 의미하고, 일반적으로 이러한 eviction은 다음과 같은 이유로 발생할 수 있다..&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 한계 도달&lt;/b&gt;: Redis는 메모리가 가득 차면 eviction 정책을 트리거하여 데이터를 삭제한다. 이때 Redis는 설정된 eviction 정책에 따라 어떤 데이터를 삭제할지 결정한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;maxmemory 설정&lt;/b&gt;: Redis에서는 메모리 사용량을 제한하는 maxmemory 옵션이 있고, 이 옵션을 설정하면 Redis는 설정된 한계에 도달했을 때 eviction을 수행하여 메모리 사용량을 제한한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;expire 설정&lt;/b&gt;: 데이터가 만료되면(설정된 TTL이 만료되는 경우) Redis는 eviction을 수행하여 만료된 데이터를 삭제한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LRU(Least Recently Used) 정책&lt;/b&gt;: Redis는 LRU 정책을 사용하여 가장 최근에 사용되지 않은 데이터를 삭제한다. 이는 데이터가 오랫동안 사용되지 않은 경우 eviction을 유발할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Eviction은 메모리 관리를 위해 필수적인 과정이지만, 잘못된 eviction 정책 설정 또는 메모리 부족으로 인해 예상치 못한 데이터 손실이 발생할 수 있다. 따라서 Redis를 운영할 때는 메모리 사용량을 모니터링하고 적절한 eviction 정책을 설정하여 데이터 손실을 방지해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Eviction 을 방지하는 가장 쉬운 방법은 인스턴스 타입을 높여 가용 메모리를 늘리는 방법이다..&lt;/p&gt;</description>
      <category>AWS</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/284</guid>
      <comments>https://ssunw.tistory.com/entry/Redis-Eviction#entry284comment</comments>
      <pubDate>Wed, 8 Nov 2023 18:34:42 +0900</pubDate>
    </item>
    <item>
      <title>jvm heap memory</title>
      <link>https://ssunw.tistory.com/entry/jvm-heap-memory</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;jvm heap memory&lt;/h2&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;java -XX:InitialRAMPercentage=60.0 -XX:MaxRAMPercentage=60.0 -jar myapp.jar 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;java heapsize 를 할당받은 메모리의 60% 로 설정&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;java -XX:+PrintFlagsFinal -version | grep HeapSize 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;리눅스에서 자바 max 힙사이즈 크기 확인&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;자바 애플리케이션의 최대 힙 메모리 크기를 퍼센테이지로 가져갈 때 몇 퍼센트를 가져가는 것이 좋은지 여부는 애플리케이션의 특성에 따라 다릅니다. 일반적으로 애플리케이션이 사용하는 메모리 크기의 60~70%를 최대 힙 메모리 크기로 설정하는 것이 좋다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이렇게 하면 애플리케이션이 충분한 메모리를 사용할 수 있으면서도 메모리 부족 오류가 발생할 가능성을 줄일 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그러나 애플리케이션의 특성에 따라 최대 힙 메모리 크기를 더 높거나 낮게 설정해야 할 수도 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;예를 들어, 애플리케이션이 많은 양의 메모리를 사용하는 경우 최대 힙 메모리 크기를 더 높게 설정해야  한다.&lt;br&gt;반대로, 애플리케이션이 적은 양의 메모리를 사용하는 경우 최대 힙 메모리 크기를 더 낮게 설정해야 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;최대 힙 메모리 크기를 설정할 때는 애플리케이션의 성능 테스트를 통해 최적의 크기를 찾는 것이 좋다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;자바 힙메모리 사이즈란?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;자바 힙 메모리 크기는 자바 가상 머신이 할당할 수 있는 최대 메모리 크기를 결정 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;힙 메모리는 자바 객체를 저장하는 데 사용되므로, 힙 메모리 크기가 클수록 더 많은 자바 객체를 저장할 수 있다.&lt;br&gt;그러나 힙 메모리 크기가 너무 크면 네이티브 메모리 부족 오류가 발생할 수 있다. 따라서 애플리케이션의 특성에 맞는 적절한 힙 메모리 크기를 설정하는 것이 중요하다.&lt;br&gt;&amp;nbsp;&lt;br&gt;일반적으로 애플리케이션이 사용하는 메모리 크기의 60~70%를 최대 힙 메모리 크기로 설정하는 것이 좋다.&lt;br&gt;이렇게 하면 애플리케이션이 충분한 메모리를 사용할 수 있으면서도 메모리 부족 오류가 발생할 가능성을 줄일 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다음은 힙 메모리 크기가 애플리케이션에 미치는 영향에 대한 몇 가지 예시다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;힙 메모리 크기가 너무 작으면 애플리케이션이 충분한 메모리를 사용할 수 없어 성능이 저하될 수 있다.&lt;/li&gt;&lt;li&gt;힙 메모리 크기가 너무 크면 메모리 부족 오류가 발생할 수 있다.&lt;/li&gt;&lt;li&gt;힙 메모리 크기가 적절하면 애플리케이션이 충분한 메모리를 사용할 수 있어 성능이 향상될 수 있다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;GC가 발생하지 않았는데 Java 애플리케이션이 OOM이 발생했다면, 다음과 같은 원인이 있을 수 있다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;Max Heapsize가 너무 크다.&lt;/b&gt; Max Heapsize는 애플리케이션이 사용할 수 있는 최대 heap 크기를 지정한다. Max Heapsize가 너무 크면 애플리케이션이 너무 많은 메모리를 사용해서 OOM이 발생할 수 있다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;애플리케이션이 메모리를 너무 많이 사용한다.&lt;/b&gt; 애플리케이션이 너무 많은 메모리를 사용하면 OOM이 발생할 수 있다. 애플리케이션이 메모리를 많이 사용하는지 확인하고, 메모리 사용량을 줄일 수 있는 방법을 찾아야 한다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;애플리케이션에 메모리 누수가 있다.&lt;/b&gt; 애플리케이션에 메모리 누수가 있으면 OOM이 발생할 수 있다. 메모리 누수가 있는지 확인하고, 메모리 누수를 수정해야 한다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;운영 체제의 메모리 제한이 있다.&lt;/b&gt; 운영 체제의 메모리 제한이 있으면 OOM이 발생할 수 있다. 운영 체제의 메모리 제한을 확인하고, 애플리케이션이 사용할 수 있는 메모리량을 늘려야 한다.&lt;/li&gt;&lt;/ul&gt;</description>
      <category>Spring Boot</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/283</guid>
      <comments>https://ssunw.tistory.com/entry/jvm-heap-memory#entry283comment</comments>
      <pubDate>Tue, 31 Oct 2023 21:17:10 +0900</pubDate>
    </item>
    <item>
      <title>SGP(Security Group for Pod)</title>
      <link>https://ssunw.tistory.com/entry/SGPSecurity-Group-for-Pod</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;SGP&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC CNI 는 노드(instance)의 ENI 와 연결된 보안 그룹을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Node 에 띄워지는 모든 Pod 들은 Node 의 보안 그룹을 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말은 Node 보안 그룹을 Pod 가 사용하기 때문에 불필요한 리소스에 접근을 할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 Security Group for Pods 를 사용하면 개별 Pod 에 Security Group 을 사용할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qdVHx/btszyFuqDNT/OYmhrwKPzfdaLYqfmnala0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qdVHx/btszyFuqDNT/OYmhrwKPzfdaLYqfmnala0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qdVHx/btszyFuqDNT/OYmhrwKPzfdaLYqfmnala0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqdVHx%2FbtszyFuqDNT%2FOYmhrwKPzfdaLYqfmnala0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;773&quot; height=&quot;704&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC CNI 에서 ENABLE_POD_ENI=true 를 설정하면 vpc cni 애드온이 각 클러스터의 노드에 vpc.amazonaws.com/has-trunk-attached=true Lable 을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;aws-k8s-trunk-eni&amp;rdquo; 라는 trunk interface 를 생성하여 노드에 연결한다. 노드의 인스턴스 타입에 따라 브랜치 네트워크 인터페이스 갯수와 IP capacity 가 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;m5.large 타입의 경우 ENI 를 최대 29개 사용할 수 있다. 즉, 최대 29개의 Pod 가 뜰 수 있다는 것이다. &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-increase-ip-addresses.html&quot;&gt;노드의 최대 파드 수를 조정할 수 있기는 하다.&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701062473663&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Amazon EC2 노드에 사용 가능한 IP 주소 증량 - Amazon EKS&quot; data-og-description=&quot;관리형 노드 그룹은 maxPods의 값에 최대 수를 적용합니다. vCPU가 30개 미만인 인스턴스의 경우 최대 수는 110이고 다른 모든 인스턴스의 경우 최대 수는 250입니다. 이 최대 수는 접두사 위임의 활성&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-increase-ip-addresses.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-increase-ip-addresses.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-increase-ip-addresses.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-increase-ip-addresses.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Amazon EC2 노드에 사용 가능한 IP 주소 증량 - Amazon EKS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;관리형 노드 그룹은 maxPods의 값에 최대 수를 적용합니다. vCPU가 30개 미만인 인스턴스의 경우 최대 수는 110이고 다른 모든 인스턴스의 경우 최대 수는 250입니다. 이 최대 수는 접두사 위임의 활성&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SGP 를 할당받은 pod 가 node의 kubelet 에서 probe(startup 이나 readiness) 통신이 안 될 경우, DISABLE_TCP_EARLY_DEMUX=true 로 변경해야 한다.(initContainer 섹션에서 DISABLE_TCP_EARLY_DEMUX의 값을 true로 변경)&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/282</guid>
      <comments>https://ssunw.tistory.com/entry/SGPSecurity-Group-for-Pod#entry282comment</comments>
      <pubDate>Tue, 31 Oct 2023 21:12:44 +0900</pubDate>
    </item>
    <item>
      <title>elasticache network exceeded</title>
      <link>https://ssunw.tistory.com/entry/elasticache-network-exceeded</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;redis&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;redis, memcached network exceeded 가 발생할 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;latency 가 밀린다.&lt;/li&gt;
&lt;li&gt;worker 가 일을 다하고 있어서, 과부하 상태가 된다.(php의 경우 fpm 을 max 까지 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;redis memory 가 꽉 찼을 경우 어떤 일이 발생?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;eviction 발생 &amp;rArr; 키가 버려짐(LRU 방식)&lt;/li&gt;
&lt;li&gt;swap memory 를 많이 사용하게 됨(why? 사용 가능한 메모리가 적기 때문)&lt;/li&gt;
&lt;li&gt;어떤 키를 제거할까? &amp;rArr; get cmd 를 오랫동안 하지 않는 key, set cmd 한지 오래된 key?, ttl, key expire 등&amp;hellip; 전략은 다양하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;1145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZJexx/btszvH7jISb/wKDk27hoWhJlvQKsb90iH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZJexx/btszvH7jISb/wKDk27hoWhJlvQKsb90iH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZJexx/btszvH7jISb/wKDk27hoWhJlvQKsb90iH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZJexx%2FbtszvH7jISb%2FwKDk27hoWhJlvQKsb90iH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1333&quot; height=&quot;1145&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;1145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AWS</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/281</guid>
      <comments>https://ssunw.tistory.com/entry/elasticache-network-exceeded#entry281comment</comments>
      <pubDate>Tue, 31 Oct 2023 21:11:44 +0900</pubDate>
    </item>
    <item>
      <title>HikariPoolConnection Error</title>
      <link>https://ssunw.tistory.com/entry/HikariPoolConnection-Error</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;HikariPoolConnection Error&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB CPU 사용률이 95% 까지 치솟자 애플리케이션에서 HikariPoolConnetion Error 발생 =&amp;gt; Connectio Timeout 발생 &amp;rArr; AWS Target Group 에 연결된 애플리케이션이 Kill(exit signal) Signal 을 통해 종료 &amp;rArr; ALB 단에서 502 를 응답&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <category>Connection Error</category>
      <category>hikaripool</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/280</guid>
      <comments>https://ssunw.tistory.com/entry/HikariPoolConnection-Error#entry280comment</comments>
      <pubDate>Tue, 31 Oct 2023 21:09:40 +0900</pubDate>
    </item>
    <item>
      <title>Elasticache NetworkBytes In/Out</title>
      <link>https://ssunw.tistory.com/entry/Elasticache-NetworkBytes-InOut</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Elasticache Redis&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis NetworkBytes In/Out 이 높을 경우 생기는 문제
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;네트워크 정체: Redis 서버가 대량의 수신 및 발신 데이터를 처리하는 경우 높은 네트워크 바이트 입출력은 네트워크 정체를 나타낼 수 있습니다. 이는 전체 네트워크 성능에 영향을 미치고 대기 시간이 증가하거나 응답 시간이 느려질 수 있습니다.&lt;/li&gt;
&lt;li&gt;대역폭 사용량 증가: 높은 네트워크 바이트 입/출력은 Redis 서버가 네트워크를 통해 많은 양의 데이터를 전송하고 있음을 의미합니다. 이것은 특히 Redis가 많은 수의 클라이언트에 서비스를 제공하거나 상당한 데이터 로드를 처리하는 시나리오에서 상당한 네트워크 대역폭을 소비할 수 있습니다.&lt;/li&gt;
&lt;li&gt;잠재적인 성능 영향: Redis 서버가 높은 네트워크 트래픽을 처리하는 데 어려움을 겪고 있는 경우 성능이 저하될 수 있습니다. 네트워크 바이트 입/출력이 증가하면 CPU 및 메모리와 같은 서버 리소스에 추가 부담이 가해져 대기 시간이 길어지고 응답 시간이 느려질 수 있습니다.&lt;/li&gt;
&lt;li&gt;네트워크 최적화의 필요성: 네트워크 바이트 입출력이 지속적으로 높은 경우 잠재적인 네트워크 최적화 전략을 조사할 가치가 있습니다. 여기에는 Redis 서버 구성 최적화, 네트워크 인프라 개선 또는 빈번한 네트워크 요청의 필요성을 줄이기 위한 캐싱 메커니즘 구현이 포함될 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Redis Commands Metric
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis에서 &quot;commands&quot; 메트릭은 Redis 서버에서 실행한 총 명령 수의 측정값&lt;/li&gt;
&lt;li&gt;Redis 서버의 워크로드 및 사용량을 모니터링하고 이해하는 데 유용한 메트릭&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AWS</category>
      <category>elasticache</category>
      <category>Redis</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/279</guid>
      <comments>https://ssunw.tistory.com/entry/Elasticache-NetworkBytes-InOut#entry279comment</comments>
      <pubDate>Tue, 31 Oct 2023 21:08:00 +0900</pubDate>
    </item>
    <item>
      <title>ec2 인스턴스 기반에서 동작하는 java application 502 발생</title>
      <link>https://ssunw.tistory.com/entry/ec2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EA%B8%B0%EB%B0%98%EC%97%90%EC%84%9C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94-java-application-502-%EB%B0%9C%EC%83%9D</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ec2 인스턴스에서 java 애플리케이션이 OOM 이 나면서 실제 애플리케이션에 접근할 때 502 를 떨구지만, ASG 는 healthy 상태로 판단하고 인스턴스 교체를 안 할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OOM 이 발생해도 인스턴스 상태체크를 통과할 수 있는 원인 중 하나는 Java 프로세스가 OS전체 메모리가 아닌 정해놓은 heap 메모리에 도달하게 되면 해당 프로세스는 OOM이 발생되어 해당 프로세스만 Kill이 되는데요, 이때 시스템 전체 메모리 사용률이 과도하게 사용되지 않는다면 인스턴스 통신에는 문제가 없기 때문에 인스턴스 상태체크는 통과하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;지속적으로 발생할 경우 프로세스의 Heap 메모리 사용량을 조정해볼수 있지만 Auto Scaling 그룹에 Elastic Load Balancing 상태 확인을 추가하여 EC2의 상태체크 뿐 아니라 Elastic Load Balancing 상태 검사에도 실패할 경우에 인스턴스를 비정상으로 간주하여 교체할 수 있습니다. ALB의 경우 Application 의 상태체크를 하기 때문에 이 방법을 고려하여 추가해 보시길 바랍니다.
이 부분은 Network 의 범위이기 때문에 Linux profile의 엔지니어로써 Best Effort 로 최대한 관련 자료를 제공드린 점 양해해 주시면 감사하겠습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AWS</category>
      <category>ASG</category>
      <category>AWS</category>
      <category>EC2</category>
      <category>heap</category>
      <category>java</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/278</guid>
      <comments>https://ssunw.tistory.com/entry/ec2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EA%B8%B0%EB%B0%98%EC%97%90%EC%84%9C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94-java-application-502-%EB%B0%9C%EC%83%9D#entry278comment</comments>
      <pubDate>Tue, 31 Oct 2023 21:05:33 +0900</pubDate>
    </item>
    <item>
      <title>pods/exec 서브리소스 권한</title>
      <link>https://ssunw.tistory.com/entry/podsexec-%EC%84%9C%EB%B8%8C%EB%A6%AC%EC%86%8C%EC%8A%A4-%EA%B6%8C%ED%95%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins agent pod 를 사용하면서 pod 에 적절한 권한을 부여하는 과정에 pods/exec 의 권한에 대해 알아볼 기회가 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pods/exec 의 get/watch/list 권한과 get/watch/list/create 권한의 차이점은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. get/watch/list 권한을 부여했을 때:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 파드 내부의 컨테이너에서 실행되는 명령 경로에 대한 정보를 가져올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 파드에 대한 리소스 사용량 정보, 실행 중인 컨테이너의 상태 정보 등을 가져올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 작업자의 작업에 대한 초기 진단 및 디버깅을 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 컨테이너의 리소스 정보를 모니터링하고 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. get/watch/list/create 권한을 부여했을 때:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- create 서브리소스에 대한 권한을 갖게 되며, 파드 내부의 컨테이너에서 실행할 명령 경로를 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 작업자의 작업에 대한 모니터링, 진단 및 디버깅, 그리고 컨테이너의 리소스 관리를 수행할 수 있으면서도 보안 요소를 고려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 따라서, 보안을 강화하기 위해서는 권한 설정 시에 파드 내부 상세 정보를 기반으로 적절한 권한 부여 및 서비스 계정 설정을 수행해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, get/watch/list/create 권한을 부여하면 파드 내부의 컨테이너를 직접적으로 조작할 수 있는 권한을 가지게 되며, 보안 상의 고려사항이 더욱 중요해집니다. 이에 반해, get/watch/list 권한은 파드 내부의 상태 정보 및 리소스 사용량 정보를 가져올 수 있지만, 직접적으로 컨테이너를 조작하는 권한은 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'파드 내부의 컨테이너에서 실행할 명령 경로'는 exec API를 사용하여 쿠버네티스 클러스터에서 작동되고 있는 Pod의 컨테이너에서 실행할 Shell 명령어 또는 프로세스를 지정합니다.예를 들어, 다음 명령은 nginx라는 컨테이너가 실행되고 있는 Pod의 /var/log/nginx/access.log 파일의 일부분을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680229498727&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl exec &amp;lt;pod-name&amp;gt; -c nginx -- tail -f /var/log/nginx/access.log&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 명령에서&amp;nbsp;exec&amp;nbsp;API는 쿠버네티스 클러스터 내에서 실행되고 있는 Pod에 지정한 Shell 명령어&amp;nbsp;tail -f /var/log/nginx/access.log를 실행시키라고 지시하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 과정에서&amp;nbsp;get/watch/list/create&amp;nbsp;서브리소스 엔드포인트를 통해 컨테이너의 로그를 확인하거나 컨테이너에 명령을 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서,&amp;nbsp;파드 내부의 컨테이너에서 실행할 명령 경로를 지정할 수 있다는 것은 쿠버네티스 클러스터에서 실행 중인 파드의 컨테이너에서 Shell 명령어 또는 프로세스를 실행할 수 있다는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/277</guid>
      <comments>https://ssunw.tistory.com/entry/podsexec-%EC%84%9C%EB%B8%8C%EB%A6%AC%EC%86%8C%EC%8A%A4-%EA%B6%8C%ED%95%9C#entry277comment</comments>
      <pubDate>Fri, 31 Mar 2023 11:26:02 +0900</pubDate>
    </item>
    <item>
      <title>AWS IAM</title>
      <link>https://ssunw.tistory.com/entry/AWS-IAM</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Policy&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS IAM 정책은 5가지 형태의 구조를 가지고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Effect&lt;/li&gt;
&lt;li&gt;Principal&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;li&gt;Action&lt;/li&gt;
&lt;li&gt;Condition&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누가(Principal) 대상(Resources)에 작업(Action)을 조건(Condition)에 따라 허용(Effect)할지 말지를 정해 놓은 정책이다. 이 정책이 적용할 대상은 Identity 가 될 수도 resource 가 될 수도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Identity 기반 정책&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;누가(Principal)&lt;/b&gt; 대상(Resource)에 작업(Action)을 조건(Condition)에 따라 허용여부(Effect)를 결정하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 말하자면 행위자(사람)에게 부여하는 정책이다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
	&quot;Version&quot;: &quot;2012-10-17&quot;,
	&quot;Statement&quot;: [
		{
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Action&quot;: &quot;s3:GetObject&quot;,
			&quot;Resource&quot;: &quot;arn:aws:s3:::bizhub.hy&quot;
		}
	]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 정책은 &amp;ldquo;bizhub.hy 라는 s3 버킷에 읽기 권한을 부여함&amp;rdquo; 이라는 정책이다. 그런데 누가? Principal 이 빠져있다. 왜냐하면 이 정책은 사용자에게 부여하기만 하면 그 사용자는 &amp;ldquo;bizhub.hy 라는 s3 버킷에 읽기 권한을 부여함&amp;rdquo; 이라는 권한을 흭득하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 사용자는 IAM user, group, SAML 인증된 사용자 등을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일일이 누가(Principal)을 정의하지 않아도 되기 때문에 모듈화 되어 재활요이 가능하다는 특징이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 100명의 유저에게 &amp;ldquo;bizhub.hy 라는 s3 버킷에 읽기 권한을 부여함&amp;rdquo; 정책을 주려면 100개의 정책을 만들어 일일이 누구에게를 지정해야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자, 그룹 등 대상에게 정책을 추가하기만 하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7RB3s/btrOYoy3U2K/eRTbcKuBlYGn10MLiSLlkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7RB3s/btrOYoy3U2K/eRTbcKuBlYGn10MLiSLlkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7RB3s/btrOYoy3U2K/eRTbcKuBlYGn10MLiSLlkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7RB3s%2FbtrOYoy3U2K%2FeRTbcKuBlYGn10MLiSLlkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;621&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;621&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Resource 기반 정책&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상(Resource)이 누구(Principal)에 작업(Action)을 조건(Condition)에 따라 허용 여부(Effect)를 결정하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상에게 부여되는 정책입니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
	&quot;Version&quot;: &quot;2012-10-17&quot;,
	&quot;Statement&quot;: [
		{
			&quot;Sid&quot;: &quot;AllowS3GetObject&quot;,
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Principal&quot;: {
				&quot;AWS&quot;: &quot;arn:aws:iam:123456789:user/a-user-1&quot;
			},
			&quot;Action&quot;: &quot;S3:GetObject&quot;,
			&quot;Resource&quot;: &quot;arn:aws:s3:::bizhub.hy&quot;
		}
	]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 정책은 &amp;ldquo;bizhub.hy 라는 S3 버킷에 읽기 권한을 계정 123456789 의 a-user-1 에게 부여함&amp;rdquo; 이라는 정책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identity 기반 정책과 다르게 Principal 이라는 &amp;ldquo;누구&amp;rdquo;가 정의되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identity 기반 정책인 정의된 정책들을 유저에게 부여했다면 리소스 기반 정책은 해당하는 대상에 직접 정의해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xEtV0/btrOYolyEN0/VI224cDKg0MQlsuI4xKOK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xEtV0/btrOYolyEN0/VI224cDKg0MQlsuI4xKOK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xEtV0/btrOYolyEN0/VI224cDKg0MQlsuI4xKOK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxEtV0%2FbtrOYolyEN0%2FVI224cDKg0MQlsuI4xKOK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;693&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 콘솔의 버킷 정책에서 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCe0KR/btrO0al3AqP/0MoIYkdNGkeiz7MzgvudxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCe0KR/btrO0al3AqP/0MoIYkdNGkeiz7MzgvudxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCe0KR/btrO0al3AqP/0MoIYkdNGkeiz7MzgvudxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCe0KR%2FbtrO0al3AqP%2F0MoIYkdNGkeiz7MzgvudxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;270&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람에게 정책을 부여해야 할지 똔느 대상에게 정책을 부여해야 할지 또는 둘 다에게 부여해야 할지 고민될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Account1 의 사용자 user-1 과 s3 버킷 myS3 가 있다고 가정하고 이들에게 읽기 권한을 주고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Account1 의 user1 는 Account1 의 myS3 에 대해 읽기 권한을 허용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;or&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Account1 의 myS3 에는 Account1 의 user1 에게 읽기 권한을 허용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 중 하나의 정책만 부여되기만 하면 권한을 부여 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약, Account2 계정의 user-2 가 Account1 계정의 S3 버킷 myS3 에 읽기 권한을 얻고자 한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Account2 의 user-2 는 Account1 의 myS3 에 읽기 권한을 허용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;or&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Account1 의 myS3 에는 Account2 의 user-2 에게 읽기 권한을 허용함&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 의 정책이 사용자(Identity) 와 자원(Resource) 에 부여됨과 차이점에 대해 알 수 있었다. 정책의 경우 한 번 부여되게 되면 영구적으로 권한을 가지게 된다는 특징을 가지고 있어 보안상 우려가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Condition 을 통해 특정 아이피, 특정 시간대에만 권한을 허용해 줄 수 있지만 기능이 한정적이다. 권한을 임시적으로 부여할 수 있는 역할(Role) 이 존재한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Role&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정책은 역할과 IAM 사용자에게 부여&lt;/b&gt;할 수 있다. 정책은 한 사람, 한 그룹 단위로 할당해주지만 &lt;b&gt;역할은 다수의 사람이 역할을 맡을 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액세스 키 또는 암호를 알고 있다면 일부로 이를 바꾸지 않는 한 영원한 크레덴셜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시 보안 자격 증명은 유효한 시간이 지난 후에는 사용 불가능한 크레덴셜이다. IAM 사용자가 역할을 맡는다는 것은 임시 보안 자격 증명을 통해 임시적인 시간(에를 들어, 1시간) 동안만 역할을 맡고 그 이후에는 역할을 반납한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정책은 한 번 부여한 뒤 회수하지 않는 한&lt;/b&gt; 영구적으로 권한을 부여받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2rk1M/btrOYIqiw6q/rdxYy8SoSc9uGZaJ9T7bYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2rk1M/btrOYIqiw6q/rdxYy8SoSc9uGZaJ9T7bYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2rk1M/btrOYIqiw6q/rdxYy8SoSc9uGZaJ9T7bYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2rk1M%2FbtrOYIqiw6q%2FrdxYy8SoSc9uGZaJ9T7bYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;276&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 사용자가 S3 버킷에 접근하고 싶을 때, 두 가지 방식으로 권한을 부여할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정책을 사용자에게 할당한다.&lt;/li&gt;
&lt;li&gt;역할을 위임받는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 정책을 생성한다. bizhubcentrify 라는 s3 버킷에 대해 읽기 쓰기 권한을 얻는 정책을 만들고 이 정책을 IAM 사용자에게 할당한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;AllowS3ReadWrite&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;s3:PutObject&quot;,
                &quot;s3:GetObject&quot;,
                &quot;s3:ListBucket&quot;
            ],
            &quot;Resource&quot;: [
                &quot;arn:aws:s3:::bizhubcentrify&quot;
            ]
        },
        {
            &quot;Sid&quot;: &quot;AllowList&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;s3:ListAllMyBuckets&quot;,
                &quot;s3:GetBucketLocation&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sid : 아주 간단하게 보자면 제목, 부제 정도로 생각하면 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Effect : 해당 Resource 에 Action 을 허용할 것인지 거부할 것인지를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Resource : 내가 접근하고자 하는 AWS Resource 에 대한 arn 을 작성한다. 이 부분은 더 찾아볼 것&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책(Policy)이 아닌 역할(Role)을 통해 권한을 부여받는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할을 생성한 뒤 정책을 연결한다. 그리고 이 역할을 IAM 사용자에게 임시적으로 부여할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 사용자가 S3AccessS3 assume 역할을 위임받은 것을 볼 수 있다. 이 역할은 1시간 뒤에 반납되게 된다. 1시간 뒤에는 다시 아무런 권한을 가지지 못한 상태로 남게 된다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
	&quot;Version&quot;: &quot;2012-10-17&quot;,
	&quot;Statement&quot;: [
		{
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Principal&quot;: {
				&quot;AWS&quot;: &quot;arn:aws:iam::598944169130:user/root&quot;
			},
			&quot;Action&quot;: &quot;sts:AssumeRole&quot;
		}
	]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Principal : AWS: arn&amp;hellip;. &amp;rarr; 해당 iam user 에게 모든 권한을 OPEN&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action : sts:AssumeRole &amp;rarr; 임시 권한이라는 뜻&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할을 사용하게 되면 정책을 부여하는 방식과 권한을 따로 회수하지 않아도 되는 장점이 있다. 예를 들어 s3 버킷에 파일 하나를 업로드하려 할 때 s3 버킷 쓰기 역할을 위임받은 후 필요한 작업을 완료하면 된다. 1시간이 지난 후 자동으로 역할에서 해제된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pG9Wr/btrO0HKpyd5/CvRoK46oOkq0fgI5DM5fkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pG9Wr/btrO0HKpyd5/CvRoK46oOkq0fgI5DM5fkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pG9Wr/btrO0HKpyd5/CvRoK46oOkq0fgI5DM5fkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpG9Wr%2FbtrO0HKpyd5%2FCvRoK46oOkq0fgI5DM5fkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;408&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책은 부여, 회수를 통해 권한 관리가 가능하다. AWS 에서는 사용자에게 부여된 정책이 언제 사용되고 얼마 동안 사용되지 않은지를 보여주는 Access manager 를 통해 정책의 관리를 도와주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그만큼 정책관리는 매우 중요한 업무이고 역할을 통해 권한 관리를 하게 되면 관리 측면에서 매우 편해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책과 역할을 잘 관리할 수 있느냐는 클라우드 보안에 있어 매우 중요한 부분을 차지하고 있다.&lt;/p&gt;</description>
      <category>SRE</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/276</guid>
      <comments>https://ssunw.tistory.com/entry/AWS-IAM#entry276comment</comments>
      <pubDate>Tue, 18 Oct 2022 21:27:27 +0900</pubDate>
    </item>
    <item>
      <title>SLA/SLO/SLI</title>
      <link>https://ssunw.tistory.com/entry/SLASLOSLI</link>
      <description>&lt;h1 id=&quot;SLI/SLO/SLA란?-SLA/SLO/SLI&quot;&gt;SLA/SLO/SLI&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 란 서비스의 성능 또는 안정성에 대한 정량적 측정이다. SLI 는 하나 이상의 모니터 기반 또는 메트릭 기반이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLO 란 특정 기간 동안의 SLI 에 대한 목표 백분율이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLA 란 클라이언트의 신뢰성 기대와 이를 충족하지 못한 서비스 제공자의 결과를 규정하는 클라이언트와 서비스 제공자 간의 명시적 또는 묵시적 합의&lt;/p&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-KPI(KeyPerformanceIndicator)&quot; data-ke-size=&quot;size23&quot;&gt;KPI(Key Performance Indicator)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KPI 는 성공을 측정하는데 사용될 수 있는 메트릭이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KPI 는 목표나 목적과는 다른, 목표를 달성하는 과정 중에 있는지 측정하는 메트릭이다. 따라서 수반하는 목표가 필요하다. 또한 KPI 를 모니터링하는 것은 목표를 달성하기 위해 필수적이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SMART 법칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Specific&lt;/b&gt;: KPI 는 구체적이어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Measurable&lt;/b&gt;: 모니터링하기 위해서 KPI 는 측정 가능해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Acheivable&lt;/b&gt;: 100% 는 성취하기 어렵다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Relevent&lt;/b&gt;: 관련있지 않은 KPI 는 목표 달성을 이끌어 낼 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Time-bound&lt;/b&gt;: 99% available-Per Year? Per month? Per day?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종류&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Business KPI
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Return on Investment (ROI)&lt;/li&gt;
&lt;li&gt;Earning before interest and taxes (EBIT)&lt;/li&gt;
&lt;li&gt;Employee turnover, Customer churn&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Technical or Software KPI
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Page view, User registration, Clickthroughs, Checkouts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-ServiceLevel&quot; data-ke-size=&quot;size23&quot;&gt;Service Level&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Indicators &amp;rarr; Objectives &amp;rarr; Agreement&lt;/p&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-SLI(ServiceLevelIndicators)&quot; data-ke-size=&quot;size23&quot;&gt;SLI(Service Level Indicators)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sre.google/workbook/implementing-slos/&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 측정 가능한 특성, A KPI&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 는 시간이 정해지고 측정 가능해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&amp;nbsp;&lt;span style=&quot;color: #202124;&quot;&gt;availability (i. e., how many requests are succeeding), latency (i.e., how long a request takes), throughput, correctness, data freshness, etc.)&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-적절한SLI설정&quot; data-ke-size=&quot;size23&quot;&gt;적절한 SLI 설정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 직접 대면하는 서비스의 경우 주로 가용성, 응답 시간 그리고 처리량이 중요하다. 즉, 요청에 올바르게 응답할 수 있는지, 응답 시간은 얼마나 오래 걸리는지, 얼마나 많은 요청을 처리할 수 있는지가 중요하다는 뜻이다.&lt;/li&gt;
&lt;li&gt;저장소 시스템의 경우 주로 응답 시간, 가용성 그리고 내구성이 중요하다. 즉, 데이터를 읽고 쓰는 데 어느 정도의 시간이 걸리는지, 필요할 때 데이터에 액세스할 수 있는지, 데이터는 안전하게 저장되어 있는지 등이 중요하다.&lt;/li&gt;
&lt;li&gt;데이터 처리 파이프라인 같은 빅데이터 시스템은 처리량과 종단 간 응답 시간이 중요하다. 즉, 얼마나 많은 데이터를 처리할 수 있는지, 데이터가 유입된 이후로 작업을 완료하기까지의 시간은 얼마나 걸리는지(일부 파이프라인은 개별 처리 단계별로 목표 응답 시간을 설정하기도 한다.) 등이 중요하다.&lt;/li&gt;
&lt;li&gt;모든 시스템은 정확성(correctness) 역시 중요하다. 올바른 응답이 리턴되었는지, 올바른 데이터를 조회했는지, 분석은 정확히 이루어졌는지 등을 고려해야 한다. 정확성은 시스템의 상태를 추적하기 위한 척도로서 매우 중요하지만 인프라스트럭처보다는 시스템의 데이터에 대한 것이므로 이를 달성하기 위해 SRE 가 관여하지는 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-WhattoMeasure:UsingSLIs&quot; data-ke-size=&quot;size23&quot;&gt;What to Measure: Using SLIs&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공 HTTP request 수 / 총 HTTP request (성공률) (&lt;span style=&quot;color: #515154;&quot;&gt;Number of successful HTTP requests / total HTTP requests)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;속도가 100ms 미만인&amp;nbsp;성공한 gRPC request / 총 gRPC request (Number of gRPC calls that completed successfully in &amp;lt; 100 ms / total gRPC requests)&lt;/li&gt;
&lt;li&gt;전체 corpus 를 사용한 검색 결과 수 / 총 검색 결과 수, (Number of search results that used the entire corpus / total number of search results, including those that degraded gracefully)&lt;/li&gt;
&lt;li&gt;10분 이내의 최신 재고 데이터를 사용한 상품 검색의 &amp;ldquo;재고 확인 횟수&amp;rdquo; request / 총 재고 확인 request (Number of &amp;ldquo;stock check count&amp;rdquo; requests from product searches that used stock data fresher than 10 minutes / total number of stock check requests)&lt;/li&gt;
&lt;li&gt;사용자가 서비스 에러 없이 사용한 시간[분] / 총 사용자 사용 시간[분] (Number of &amp;ldquo;good user minutes&amp;rdquo; according to some extended list of criteria for that metric / total number of user minutes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 범위는 0% ~ 100% 이며, 0% 는 아무 것도 작동하지 않는 것이고, 100% 는 아무것도 손상되지 않은 것이다. 이 척도를 통해 매우 직관적으로 오류 예산(error budget)을 계산할 수 있다. SLO 는 목표로 하는 백분율이고 오류 예산은 100%에서 SLO 를 뺀 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, SLO 가 99.9% 인 경우에, 한달 동안 3백만 개의 요청을 수신하는 서비스의 경우 한달 동안 3,000(0.1%) 개의 오류 예산(error budget)&amp;nbsp;을 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 에서 I 는 측정 방법과 관계없이 사용자에게 중요하다고 생각하는 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLI 사양&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) Ratio of home page requests that loaded in &amp;lt; 100 ms&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLI 구현&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 SLI 사양에는 여러 SLI 구현이 있을 수 있으며 각각은 품질(고객의 사용 경험을 얼마나 정확하게 포착하는지), 적용 범위(모든 경험을 얼마나 잘 포착하는지) 등에서 장단점(비용 등..)이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 및 SLO 에 대한 첫 번째 시도가 정확할 필요는 없다. 가장 중요한 목표는 무언가를 제자리에 놓고 측정하고 피드백 루프를 설정하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://sre.google/workbook/implementing-slos/#continuous-improvement-of-slo-targets&quot;&gt;개선할 수 있도록 하는 것&lt;/a&gt;이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLI 측정 방법 선택&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상적인 측정 방법으로는 고객 서비스 환경과 가장 근접하며 최소한의 노력만으로 측정할 수 있는 측정 방법을 선택해야 한다. 다음은 시간이 지남에 따라 구현하는 데 필요한 노력을 들여야 하는 순으로 나열한 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;애플리케이션 서버 exports 및 인프라 metric 사용.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;일반적으로 이런 metric 은 바로 접근할 수 있고 값을 빠르게 제공한다. 몇몇 툴에서는 기본적으로 SLO 를 설정해주는 도구가 포함되어 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 계측 사용(APM).&lt;/b&gt;&amp;nbsp;기존 시스템에는 일반적으로 내장된 클라이언트 계측이 없으므로 클라이언트 계측을 제공하는 APM 제품군 또는 FE 프레임워크를 사용하면 고객 만족도에 대한 유용한 정보를 빠르게 얻을 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로그 처리 사용.&lt;/b&gt;&amp;nbsp;만약 로그가 있는 경우 로그 처리가 가장 좋은 방법이 될 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;합성 테스트 구현.&lt;/b&gt;&amp;nbsp;고객이 서비스를 사용하는 방법에 대한 기본 사항을 이해한 상태에서 서비스 수준을 테스트한다. 이 테스트는 트래픽이 적은 서비스처럼 쉽게 관측할 수 없는 장애들을 찾는데 도움이 될 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLI 구성 요소 유형&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 설정을 시작하는 가장 쉬운 방법은 시스템을 몇 가지 일반적인 유형의 구성 요소로 추상화하는 것입니다.&amp;nbsp;그런 다음 각 구성 요소에 대해 제안된 SLI 목록을 사용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;서비스와 가장 관련성이 높은 항목을 선택&lt;/b&gt;할 수 있습니다. 요청 기반, pipelinem, Storage Type 이 존재.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 앱은 사용자의 핸드폰에서 클라우드 상의 HTTP API 와 상호작용 한다. 이 API 는 영구적인 스토리지 시스템에 변동사항을 작성하고, 파이프라인은 주기적으로 API 를 실행하여 데이터를 일자별로 쿼리하여 가장 높은 점수를 리그 테이블에 데이터를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점수 데이터는 3개의 분리된 DB 중 리그 테이블 DB 에 create 된다. 그리고 이 저장된 데이터들을 모바일 앱이나 웹사이트에서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, API 를 통해 게임이나 웹사이트에서 사용하는 사용자 정보를 유저 DB 에 저장 및 업로드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 아키텍쳐 구조에서, 우리는 사용자가 시스템과 어떻게 상호작용하는지 생각해 볼 수 있다. 그리고 어떤 종류의 SLI 가 다양한 관점의 유저 경험을 측정할 것인지 생각해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자에게 가장 중요한 기능을 나타내는 SLI 타입을 선택하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 사용자 경험과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://www.yeonsu.info/news/articleView.html?idxno=32081&quot;&gt;long tail 법칙&lt;/a&gt;을 모두 포착하기 위해 여러 등급의 SLOs 로 여러 타입의 SLIs 를 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 90% 의 사용자들이 100ms 안으로 request 를 처리할 때, 10% 의 사용자들은 request 를 처리하는데 10초가 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;latency 기반 SLO 는 다양한 임계값을 설정하여 user base 를 포착한다. 90% 의 request 는 100ms 보다 빠르고 99% 의 request 는 400ms 보다 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 원칙은 사용자의 서비스 경험을 측정하는 매개변수가 있는 모든 SLI 에 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 테이블 2-1 은 다양한 유형의 서비스에서 사용할 수 있는 공통 SLIs 이다.&lt;/p&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-SLI구현&quot; data-ke-size=&quot;size23&quot;&gt;SLI 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 SLI 를 구현할 때는 최소한의 작업이 필요한 SLI 를 구현한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 를 측정하기 위한 정보로는&amp;nbsp;&lt;span style=&quot;color: #515154;&quot;&gt;availability, latency 를 사용할 수 있고,&amp;nbsp;availability&lt;/span&gt;&amp;nbsp;를 측정하기 위해서는 성공/실패 상태를 알아야 하고, slow requests 의 경우 request 를 처리하는 시간을 알아야 한다. 즉, SLI 를 위해서는 해당 정보를 알기 위한 충분한 metric 들을 계측할 수 있어야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 기반의 서비스를 사용할 경우 availability, latency 를 클라우드 기반의 모니터링 대시보드에서 사용할 수도 있다.(CloudWatch 메트릭을 바탕으로 자동 생성해주는 대시보드 등..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각각의 장단점이 존재하는 예제 아키텍쳐의 SLI 구현을 위한 다양한 옵션들을 확인해보자.&lt;/p&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-APIandHTTPserveravailabilityandlatency&quot; data-ke-size=&quot;size23&quot;&gt;API and HTTP server availability and latency&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP status code 기반으로 SLI 를 구현할 수 있다.&amp;nbsp; 쉽게 말하자면 availability SLI 는 successful 한 requests 의 비율이고, latency SLI 는 정의된 임계값보다 빠른 requests 의 비율이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 는 구체적이고 측정가능해야 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;What to Measure: Using SLIs&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;에 있는 잠재적인 SLI 후보군들을 요약하자면, SLI 로 아래와 같은 sources 를 하나 이상 사용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Application server logs&lt;/li&gt;
&lt;li&gt;Load balancer monitoring&lt;/li&gt;
&lt;li&gt;Black-box monitoring&lt;/li&gt;
&lt;li&gt;Client-side instrumentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 SLI 를 구현할 때는 최소한의 작업이 필요한 SLI 를 구현하는 것이 좋다.&lt;/p&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-Pipelinefreshness,coverage,andcorrectness&quot; data-ke-size=&quot;size23&quot;&gt;Pipeline freshness, coverage, and correctness&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리그 DB 의 테이블을 업데이트하는 파이프라인의 경우, 데이터에 데이터가 업데이트 된 시간의 timestamp 를 포함한 watermark 가 기록된다. 아래 몇가지 SLI 구현 예제를 확인하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리그 DB 에서 주기적으로 새로운 레코드의 총합과 모든 레코드의 총합을 계산하는 쿼리를 실행한다. 이렇게 쿼리를 실행할 경우 얼마나 많은 사용자가 데이터를 확인하든 상관 없이 오래된(stale) 레코드를 확인할 수 있고 처리할 수 있다(의역).(&lt;span style=&quot;color: #515154;&quot;&gt;This will treat each stale record as equally important, regardless of how many users saw the data.)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;리그 DB 의 모든 클라이언트가 새로운 데이터를 Read 할 때 timestamp 가 적힌 watermark 를 확인하고 데이터가 request 됐음을 알리는 metric counter 를 증가시킨다. 만약 데이터가 미리 정의된 임계값보다 최신일 경우 다른 카운터를 증가시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두가지 options 들은 client-side 에서 구현이다. 그렇기 때문에 사용자 경험과 훨씬 더 밀접하게 연관된 SLI 를 제공한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파이프라인은 처리해야 하는 레코드 수와 성공적으로 처리한 레코드 수를 exports 한다. 만약 파이프라인이 잘못 구성되었다면 이로 인해 올바른 메트릭을 잃을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;correctness 를 측정하기 위한 몇 가지의 접근법이 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;known outputs 를 시스템 상에 밀어 넣었을 때 나오는 출력값이 기댓값과 일치하는 비율을 계산한다. 즉, 올바른 응답값을 받았는지에 대한 비율을 계산한다.(Inject data with known outputs into the system, and count the proportion of times that the output matches our expectations.)&lt;/li&gt;
&lt;li&gt;데이터를 입력 받아 정확한 결과를 계산하기 위한 방법으로 파이프라인 자체의 중복을 제거하는 방법이 있다.(가격이 비싸기 때문에 적합하지 않다.)입출력 쌍을 사용하여 올바른 응답값의 비율을 계산한다. (Use a method to calculate correct output based on input that is distinct from our pipeline itself (and likely more expensive, and therefore not suitable for our pipeline). Use this to sample input/output pairs, and count the proportion of correct output records. This methodology assumes that creating such a system is both possible and practical.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-척도의표준화&quot; data-ke-size=&quot;size23&quot;&gt;척도의 표준화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 각각의 척도들의 최우선 원칙이 무엇인지를 매번 고민할 필요가 없도록 SLI 들에 대한 일반적인 정의를 표준화하기를 권장한다. 표준화된 정의 템플릿을 따르는 모든 수치들은 개별 SLI 의 명세서에서 생략해도 무방하다. 예를 들어보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;집계 간격: '평균 1분'&lt;/li&gt;
&lt;li&gt;집계 범위: '하나의 클러스터에서 수행되는 모든 태스크들'&lt;/li&gt;
&lt;li&gt;측정 빈도: '매 10초'&lt;/li&gt;
&lt;li&gt;집계에 포함할 요청들: '블랙박스 모니터링 잡이 수집한 HTTP GET 요청들'&lt;/li&gt;
&lt;li&gt;데이터의 수집 방식: '모니터링 시스템에 의해 서버에서 수집'&lt;/li&gt;
&lt;li&gt;데이터 액세스 응답 시간: '데이터의 마지막 바이트가 전송된 시간'&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 모든 범용 지표에 대해 재사용 가능한 SLI 템플릿을 설정해두면 많은 노력을 절감할 수 있다. 또한 모든 사람들이 특정 SLI 가 의미하는 바를 좀 더 쉽게 이해할 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-SLO(ServiceLevelObjectives)&quot; data-ke-size=&quot;size23&quot;&gt;SLO(Service Level Objectives)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cloud.google.com/blog/products/management-tools/practical-guide-to-setting-slos&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLOs allow you to quantifiably measure customer happiness &amp;rArr; 사용자 경험을 정량적으로 측정할 수 있게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 SLI 로 성취하고 싶은 목표나 숫자 지표 ex) 95%, 99% or 99.99% availability&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLO 는 성취 가능하고 관련 있어야 한다. 가능한 높은 목표를 세우는 것이 아니라, 사용자를 만족시킬 만큼에서 가격 효율적인 SLO 를 선택해야 한다. SLO 가 높을수록 높은 비용과 노력을 초래하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLO process overview&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 경험과 결제같은 비즈니스 측면에서 악영향을 끼칠 수 있는 metric 리스트를 뽑는다.&lt;/li&gt;
&lt;li&gt;사용자 경험을 가장 정확하게 추적하기 위해서 SLI 로 사용할 metric 을 정한다.&lt;/li&gt;
&lt;li&gt;SLO 목표 값과 SLO 측정 기간을 정한다.&lt;/li&gt;
&lt;li&gt;SLI, SLO 및 오류 예산(error budget) 콘솔을 생성한다.&lt;/li&gt;
&lt;li&gt;SLO 알람을 생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 1: 사용자 경험과 결제같은 비즈니스 측면에서 악영향을 끼칠 수 있는 action 리스트를 뽑는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자들이 서비스를 사용할 때 어떤 action 들이 있는지 생각한다. 온라인 이커머스 서비스이기 때문에 사용자들이 실제로 어떤 action 들을 하는지 생각해보면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상품 목록&lt;/li&gt;
&lt;li&gt;결제&lt;/li&gt;
&lt;li&gt;장바구니 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 액션들의 중요도를 따라 리스트 순위를 정해야 한다. 이커머스 서비스이기 때문에 사용자가 물건을 구매하는 것이 가장 중요하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결제&lt;/li&gt;
&lt;li&gt;장바구니 추가&lt;/li&gt;
&lt;li&gt;상품 목록&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 목록의 우선 순위가 제일 밑인 것이 의아할 수 있다. 하지만 상품 목록은 결국 결제와 장바구니 추가에 의존하기 때문이다(아마 비즈니스에 대한 뜻 인 듯). 결제나 장바구니 추가보다 상품 목록이 더 중요하다고 말할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요하다고 생각한다면 상품 목록이 있어야 물건을 장바구니에 추가하고, 결제를 할 수 있다고 생각하는 것일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 목록은 하루종일 띄워져 있다. 그러나 장바구니에 물건을 추가하고 결제를 하는 작업은 그렇지 않다. 그렇기 때문에 구매 흐름에 따라 물건을 장바구니에 담고 결제를 하는 작업은 비즈니스 상 매우매우 중요한 작업이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;step 2: 사용자 경험을 가장 정확하게 추적하기 위해 SLI 로 사용할 indicators 를 결정한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 를 위한 다양한 indicators 중에 하나를 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;availability(request 성공 횟수), latency(request 지연율), 처리량, correctness, data freshness 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 등식은 올바른 이벤트 / 유효한 이벤트 * 100 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제라는 중요한 이벤트에 대해서 SLI 를 측정하기 위해서는 어떤 과정들이 일어나는지 확인할 필요가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객이 매장에서 제품을 구매하는 과정을 상상해보자. 먼저, 고객들은 어떤 물건을 살지 탐색하고 찾는다. 그 후에 물건을 카트에 담는다. 마지막으로 결제를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 고객들이 결제하는 과정까지 진행했다면 고객의 비즈니스를 확보했다고 할 수 있다. 그렇기 때문에 고객이 물건을 결제할 수 있도록 하는 것이 무척이나 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Availability SLI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 고객이 우리 서비스의 결제 기능을 사용하는 것을 목표로 하기 때문에 Availability SLI 를 선택할 것 이다. 우리가 원하는 것은 가용성 측면에서 결제 서비스가 얼마나 잘 수행되고 있는지를 알려주는 메트릭이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이커머스 플랫폼의 경우, 얼마나 많은 사용자가 결제를 시도했고 얼마 만큼의 request 가 실제로 성공했는지 모니터링 하기를 원한다. 즉, 성공한 request 의 수가 &quot;good(올바른, 양호한)&quot; 메트릭이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적으로 무엇을 측정하고 어디서 측정할 것인지가 중요하므로 availability SLI 는 다음과 같아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(전체 요청 - HTTP Status 5XX 오류) / 전체 요청 * 100&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3XX, 4XX 가 제외되는 이유는 서비스(서버)의 오류를 나타내는 이벤트를 계산하려 하기 때문에 전체 request 에서 3XX 리다이렉션, 4XX 오류를 제외한 5XX 만 체크한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Latency SLI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cloud.google.com/architecture/adopting-slos?hl=ko&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객들이 결제할 때 임계값 안의 시간으로 주문에 대한 response 가 오는지 체크한다. 즉, successful response 를 받는데 걸리는 시간을 측정하여 latency SLI 로 설정한다. 결제 비즈니스 상 response 가 반환되는 임계값을 500ms 로 설정한다. 즉, response latency 의 99% 는 500ms 이내에 수행되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;일반적으로 99번째 백분위수 지연 시간(p99)은 중앙값 지연 시간(median, p50)의 3~5배를 초과해서는 안 된다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;이전 백분위수를 기준으로 지연 시간 임곗값을 결정한 다음 각 버킷에 속하는 요청 수를 측정하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 3: SLO 목표 설정 및 SLO 측정 기간 결정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;예를 들어 한 달 동안 10,000개의 HTTP 요청이 있고 그 중 9,990개만 SLI 설정에 따라 성공적인 response 를 반환하는 경우 해당 월의 가용성은 9,990/10,000 or 99.9% 이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;alert 가 의미 있도록 달성 가능한 SLO 를 설정하는 것이 중요하다. 보통 SLO 를 설정할 때는 historical trends 에서 시작하여 대다수의 고객들이 서비스에 만족하고 있다고 가정하는 것이 좋다. 즉, 비즈니스 상에서 원하는 목표와 수치를 설정하는 것이 이상적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;SLO 를 100% 만족하는 것은 현실성이 없으며 기대할 수도 없다. 이를 충족하려 하면 혁신과 배포의 속도가 저하되며 높은 비용을 소비하거나 지나치게 보수적인 솔루션이 된다. 그래서 오류 예산(error budget: SLO 를 만족하지 못하는 비율)을 산정하고 이를 일단위 혹은 주단위로 추적하는 것이 훨씬 더 나은 방안이다. 오류 예산(error budget)&amp;nbsp;역시 다른 SLO 들을 만족하기 위한 또 다른 SLO 일 뿐이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;어떤 SLO 를 달성하지 못한 비율은 사용자가 인지한 서비스의 상태에 대한 유용한 척도가 된다. SLO 들을 일단위 혹은 주단위로 추적해서 &lt;/span&gt;&lt;span style=&quot;color: #252525;&quot;&gt;트렌드를 파악하고 잠재적인 문제가 실제로 발생하기 전에 미리 그 조짐을 파악하는 것은 매우 유용하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;목표치, 즉 SLO 를 설정하는 것은 순수한 기술적 활동이라고 보기는 어렵다. 그 이유는 SLI 와 SLO 를 어떻게 설정하는지가 제품과 사업에 영향을 미치기 때문이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #252525;&quot;&gt;현재의 성능을 기준으로 목표를 설정하지 말 것&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;시스템의 장점과 한계를 이해하는 것이 기본이므로 이것을 고려하지 않고 목표치를 설정하면 목표치를 달성하기 위해 시스템에 엄청난 노력을 투입하게 되고 결국 방대한 재설계 없이는 시스템의 향상이 불가능하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #252525;&quot;&gt;최대한 단순하게 생각할 것&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;SLI 를 복잡하게 집계하면 시스템의 성능 변화를 명확하게 반영하지 못하고 그 원인을 파악하기 어렵게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #252525;&quot;&gt;자기 만족에 얽매이지 말 것&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;응답 시간의 저하 없이 시스템의 부하를 '무한정' 확장하는 것은 상당히 매력적인데다 '언제든지' 가능하기는 하지만 이런 요구는 현실성이 없다. 이런 이상을 실현 가능한 시스템은 디자인하고 구축하는 데 긴 시간을 필요로 할 뿐 아니라 운영 비용도 엄청나다. 게다가 십중팔구 사용자가 만족할 수 있는 수준을 필요 이상으로 초과할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #252525;&quot;&gt;가능한 적은 수의 SLO 를 설정할 것&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;시스템의 특성을 잘 확인할 수 있는 최소한의 SLO 를 선택하는 것이 중요하다. 그리고 선택한 SLO 를 옹호할 수 있어야 한다. 만일 특정 SLO 를 인용했음에도 불구하고 우선순위에 대한 논의에서 밀린다면 그 SLO 는 굳이 설정할 필요가 없는 것이다. 그러나 제품의 모든 특성이 SLO 로 선정하기에 적합한 것은 아니다. SLO 를 이용해서 '사용자의 만족'을 정의하는 것은 상당히 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #252525;&quot;&gt;처음부터 완벽하게 하려고 하지 말 것&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #252525;&quot;&gt;SLO 정의와 목표는 시간이 지남에 따라 시스템의 동작을 살피면서 언제든지 다시 정의할 수 있다. 처음부터 지나치게 높은 목표를 설정해서 나중에 가서야 달성이 불가능한 것을 발견하고 그 목표를 완화하는 것보다는 우선은 조금 느슨한 목표를 설정한 후 조금씩 강화하는 것이 낫다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;SLI/SLO/SLA란?-SLA(ServiceLevelAgreements)&quot; data-ke-size=&quot;size23&quot;&gt;SLA(Service Level Agreements)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서비스가 특정 기대를 못 미쳤을 때, 고객 보상을 제공해주는 구속력있는 계약 = more restrictive version of SLO&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLA 에는 만약 서비스가 특정 가용성을 또는 퍼포먼스 기준을 유지하지 못했을 때 제공자에 가해지는 penalty 에 대해 적는다. 그리고 SLA 가 깨지면 고객은 제공자로부터 보상을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 서비스에 SLA 가 있어야 하는 것은 아니지만 SLO 는 있어야 하며 SLO 의 기준은 SLA 보다 높아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 SLA 가 95% availabilty 일 경우 SLO 는 95% availabilty 보다 높아야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 SLI, SLO, SLA 에 대한 예시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 로는 200 응답에 대한 latency 를 잡았고 SLO 로는 99% 의 latency(p99) 가 200ms 보다 작아야 한다. 또한, SLA 로는 99% latency 가 300ms 를 초과할 경우 보상을 지불하도록 설정됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SRE</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/275</guid>
      <comments>https://ssunw.tistory.com/entry/SLASLOSLI#entry275comment</comments>
      <pubDate>Mon, 17 Oct 2022 19:29:24 +0900</pubDate>
    </item>
    <item>
      <title>SLA/SLO/SLI 란?</title>
      <link>https://ssunw.tistory.com/entry/SLASLOSLI-%EB%9E%80</link>
      <description>&lt;h1&gt;SLA/SLO/SLI&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;KPI(Key Performance Indicator)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KPI 는 성공을 측정하는데 사용될 수 있는 메트릭이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KPI 는 목표나 목적과는 다른, 목표를 달성하는 과정 중에 있는지 측정하는 메트릭이다. 따라서 수반하는 목표가 필요하다. 또한 KPI 를 모니터링하는 것은 목표를 달성하기 위해 필수적이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SMART 법칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Specific&lt;/b&gt;: KPI 는 구체적이어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Measurable&lt;/b&gt;: 모니터링하기 위해서 KPI 는 측정 가능해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Acheivable&lt;/b&gt;: 100% 는 성취하기 어렵다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Relevent&lt;/b&gt;: 관련있지 않은 KPI 는 목표 달성을 이끌어 낼 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Time-bound&lt;/b&gt;: 99% available-Per Year? Per month? Per day?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종류&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Business KPI
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Return on Investment (ROI)&lt;/li&gt;
&lt;li&gt;Earning before interest and taxes (EBIT)&lt;/li&gt;
&lt;li&gt;Employee turnover, Customer churn&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Technical or Software KPI
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Page view, User registration, Clickthroughs, Checkouts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Service Level&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Indicators &amp;rarr; Objectives &amp;rarr; Agreement&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SLI(Service Level Indicators)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 측정 가능한 특성, A KPI&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 는 시간이 정해지고 측정 가능해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) Availability&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SLO(Service Level Objectives)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 SLI 로 성취하고 싶은 목표나 숫자 지표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 95%, 99% or 99.99% availability&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLO 는 성취 가능하고 관련 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 높은 목표를 세우는 것이 아니라, 사용자를 만족시킬 만큼에서 가격 효율적인 SLO 를 선택해야 한다. SLO 가 높을수록 높은 비용과 노력을 초래하기 때문이다. SLO 를 최소화 해야 하고 어플리케이션이 SLO 를 크게 능가해서는 안된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SLA(Service Level Agreements)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서비스가 특정 기대를 못 미쳤을 때, 고객 보상을 제공해주는 구속력있는 계약 = more restrictive version of SLO&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLA 에는 만약 서비스가 특정 가용성을 또는 퍼포먼스 기준을 유지하지 못했을 때 제공자에 가해지는 penalty 에 대해 적는다. 그리고 SLA 가 깨지면 고객은 제공자로부터 보상을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 서비스에 SLA 가 있어야 하는 것은 아니지만 SLO 는 있어야 하며 SLO 의 기준은 SLA 보다 높아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 SLA 가 95% availabilty 일 경우 SLO 는 95% availabilty 보다 높아야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 SLI, SLO, SLA 에 대한 예시이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J3luH/btrNu7dCDET/PKXtUi2p7XOQal3Jzkl43K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J3luH/btrNu7dCDET/PKXtUi2p7XOQal3Jzkl43K/img.png&quot; data-alt=&quot;SLI/SLO/SLA&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J3luH/btrNu7dCDET/PKXtUi2p7XOQal3Jzkl43K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ3luH%2FbtrNu7dCDET%2FPKXtUi2p7XOQal3Jzkl43K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;386&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SLI/SLO/SLA&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI 로는 200 응답에 대한 latency 를 잡았고 SLO 로는 99% 의 latency 가 200ms 보다 작아야 한다. 또한, SLA 로는 99% latency 가 300ms 를 초과할 경우 보상을 지불하도록 설정됐다.&lt;/p&gt;</description>
      <category>SRE</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/274</guid>
      <comments>https://ssunw.tistory.com/entry/SLASLOSLI-%EB%9E%80#entry274comment</comments>
      <pubDate>Fri, 30 Sep 2022 19:48:45 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] 아파치 카프카</title>
      <link>https://ssunw.tistory.com/entry/Spring-Boot-%EC%95%84%ED%8C%8C%EC%B9%98-%EC%B9%B4%ED%94%84%EC%B9%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아파치 카프카는 ActiveMQ, Artemis, RabbitMQ 와 유사한 메시지 브로커이다. 그러나 카프카는 특유의 아키텍쳐를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카는 높은 확장성을 제공하는 클러스터로 실행되도록 설계되었다. 그리고 클러스터의 모든 카프카 인스턴스에 걸쳐 토픽을 파티션으로 분할하여 메시지를 관리한다. RabbitMQ 가 거래소와 큐를 사용해서 메시지를 처리하는 반면, 카프카는 토픽만 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 토픽은 클러스터의 모든 브로커에 걸쳐 복제된다. 클러스터의 각 노드는 하나 이상의 토픽에대한 리더로 동작하며, 토픽 데이터를 관리하고 클러스터의 다른 노드로 데이터를 복제한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Oz51B/btrK0GBYxEP/PoXrhpPmBkeCe4HXGBo36k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Oz51B/btrK0GBYxEP/PoXrhpPmBkeCe4HXGBo36k/img.png&quot; data-alt=&quot;카프카 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Oz51B/btrK0GBYxEP/PoXrhpPmBkeCe4HXGBo36k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOz51B%2FbtrK0GBYxEP%2FPoXrhpPmBkeCe4HXGBo36k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;530&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;카프카 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 토픽은여러 개의 파티션으로 분할될 수 있다. 이 경우 클러스터의 각 노드는 한 토픽의 하나 이상의 파티션(토픽 전체가 아니다)의 리더가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카는 특유의 아키텍처를 갖고 있으므로 해당 아키텍쳐에 대해 깊게 파고들어야 한다. 여기서는 스프링을 사용하여 카프카로부터 메시지를 전송 및 수신하는 방법에 대해 초점을 둔다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카프카 사용을 위한 스프링 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카를 사용해서 메시지를 처리하려면 이에 적합한 의존성을 빌드에 추가해야 한다. 그러나 JMS 나 RabbitMQ 와 달리 카프카는 스프링 부트 스타터가 없지만 의존성을 추가하면 된다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.kafka&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-kafka&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 의존성을 추가하면 스프링 부트가 카프카 사용을 위한 자동 구성을 해준다. 이제부터 kafkaTemplate 을 주입하고 메시지를 전송, 수신하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 메시지를 전송 및 수신하기에 앞서 카프카를 사용할 때 편리한 몇 가지 속성을 알면 좋다. 특히 kafkaTemplate 은 기본적으로 localhost 에서 실행되면서 9092 포트를 리스닝하는 카프카 브로커를 사용한다. 애플리케이션을 개발할 때는 로컬의 카프카 브로커를 사용하면 좋다. 그러나 프로덕션 환경에서 사용할 때는 다른 호스트와 포트로 구성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring.kafka.bootstrap-servers 속성에는 카프카 클러스터로의 초기 연결에 사용되는 하나 이상의 카프카 서버들의 위치를 설정한다. 예를 들어, 클러스터의 카프카 서버 중 하나가 kafka.test.com 에서 실행되고 9092 포트를 리스닝한다면, 이 서버의 위치를 다음과 같이 YAML 파일에 구성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
    kafka:
        bootstrap-servers:
        - kafka.test.com:9092&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring.kafka.bootstrap-servers 는 복수형이며, 서버 리스트를 받으므로 클러스터의 여러 서버를 지정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;spring:
    kafka:
        bootstrap-servers:
        - kafka.test.com:9092
        - kafka.test.com:9093
        - kafka.test.com:9094&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 프로젝트에 카프카 설정이 되었으므로 메시지를 전송, 수신할 준비가 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;KafkaTemplate 을 사용해서 메시지 전송하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KafkaTemplate 을 사용해서 Order 객체를 카프카로 전송해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 메시지 전송 메소드를 확인해보자.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; send(String topic, V data);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; send(String topic, K key, V data);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; send(String topic, Integer partition, K key, V data);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; send(String topic, Integer partition, Long timestamp, K key, V data);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; send(ProducerRecord&amp;lt;K, V&amp;gt; record);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; send(Message&amp;lt;?&amp;gt; message);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; sendDefault(V data);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; sendDefault(K key, V data);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; sendDefault(Integer partition, K key, V data);
ListenableFuture&amp;lt;SendResult&amp;lt;K, V&amp;gt;&amp;gt; sendDefault(Integer partition, Long timestamp, K key, V data);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KafkaTemplate 은 제너릭 타입을 사용하고, 메시지를 전송할 때 직접 도메인 타입을 처리할 수 있다. 따라서 모든 send() 메소드가 컨버팅 기능을 갖고 있다고 생각하면 된다. 카프카에서 메시지를 전송할 때는 메시지가 전송되는 방법을 알려주는 다음 매개 변수를 지정할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지가 전송될 토픽(send() 에 필요)&lt;/li&gt;
&lt;li&gt;토픽 데이터를 쓰는 파티션(optional)&lt;/li&gt;
&lt;li&gt;레코드 전송 키(optional)&lt;/li&gt;
&lt;li&gt;타임 스탬프(optional, 기본값은 System.currentTimeMillis())&lt;/li&gt;
&lt;li&gt;페이로드(메시지에 적재된 순수한 데이터, 여기서는 Order 객체, required)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토픽과 페이로드는 가장 중요한 매개변수들이다. 파티션과 키는 send() 와 sendDefault() 에 매개변수로 제공되는 추가 정보일 뿐 KafkaTemplate 을 사용하는 방법에는 거의 영향을 주지 않는다. 여기서는 지정된 토픽에 메시지 페이로드를 전송하는 데 초점을 둘 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;send() 메서드에는 ProducerRecord 를 전송하는 것이 있다. ProducerRecord 는 모든 선행 매개변수들을 하나의 객체에 담은 타입이다. 또한, Message 객체를 전송하는 send() 메소드도 있지만 이 경우 도메인 객체를 Message 객체로 변환해야 한다. 보통 ProducerRecord 나 Message 객체를 생성 및 전송하는 것보다는 다른 send() 메소드 중 하나를 사용하는 것이 더 간편하고 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KafkaTemplate 과 send() 메소드를 사용하여 주문 데이터를 전송하기 위한 코드는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class KafkaOrderMessagingService implements OrderMessagingService {

    private final KafkaTemplate&amp;lt;String, Order&amp;gt; kafkaTemplate;

    @Override
    public void sendOrder(Order order) {
        kafkaTemplate.send(&quot;my.orders.topic&quot;, order);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sendOrder() 메소드는 주입된 KafkaTemplate 의 send() 메소드를 사용하여 my.orders.topic 이라는 이름의 토픽으로 Order 객체를 전송한다. 만일 기본 토픽을 설정한다면 sendOrder() 메소드를 더 간단하게 만들 수 있다. 기본 토픽을 설정하는 방법은 아래처럼 YAML 파일에 정의하는 방법이다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
    kafka:
        template:
            default-topic: my.orders.topic&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 sendOrder() 메소드에서 send() 대신 sendDefault() 메소드를 호출하면 된다. 이 때는 토픽 이름을 인자로 전달하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;@Override
public void sendOrder(Order order) {
    kafkaTemplate.sendDefault(order);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카프카 리스너 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;send() 와 sendDefault() 메소드 외에도 KafkaTemplate 은 메시지를 수신하는 메소드를 제공하지 않는다. 따라서 스프링을 사용해서 카프카 토픽의 메시지를 가져오는 유일한 방법은 메시지 리스너를 작성하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 경우 메시지 리스너는 @KafkaListener 어노테이션이 지정된 메소드에 정의된다. @KafkaListener 는 @JmsListener 나 @RabbitListener 와 거의 유사하며, 동일한 방법으로 사용된다. 아래 코드를 확인하자.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class OrderListener {

    private final KitchenUI ui;

    @KafkaListener(topics=&quot;my.orders.topic&quot;)
    public void handle(Order order) {
        ui.displayOrder(order);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;my.orders.topic 이라는 이름의 토픽에 메시지가 도착할 때 자동 호출되어야 한다는 것을 나타내기 위해 handle() 메소드에는 @KafkaListenr 어노테이션이 지정되었다. 그리고 페이로드인 Order 객체만 handle() 의 인자로 받는다. 그러나 메시지의 추가적인 메타데이터가 필요하다면 ConsumerRecord 나 Message 객체도 인자로 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 handle() 메서드에는 수신된 메시지의 파티션과 타임스탬프를 로깅하기 위해 ConsumerRecord 를 인자로 받는다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;@KafkaListener(topics=&quot;my.orders.topic&quot;)
public void handle(Order order, ConsumerRecord&amp;lt;Order&amp;gt; record) {
    log.info(&quot;Received from partition {} with timestamp {}&quot;, record.partition(), record.timestamp());
    ui.displayOrder(order);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 유사하게 ConsumerRecord 대신 Message 객체를 요청하여 같은 일을 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;@KafkaListener(topics=&quot;my.orders.topic&quot;)
public void handle(Order order, Message&amp;lt;Order&amp;gt; message) {
    MessageHeaders headers = message.getHeaders();
    log.info(&quot;Received from partition {} with timestamp {}&quot;, headers.get(KafkaHeaders.RECEIVED_PARTITION_ID), headers.get(KafkaHeaders.RECEIVED_TIMESTAMP));
    ui.displayOrder(order);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 페이로드는 ConsumerRecord.value() 나 Message.getPayload() 를 사용해도 받을 수 있다. handle() 의 매개변수로 직접 Order 객체를 요청하는 대신 ConsumerRecord 나 Message 객체를 통해 Order 객체를 요청할 수 있다.&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/273</guid>
      <comments>https://ssunw.tistory.com/entry/Spring-Boot-%EC%95%84%ED%8C%8C%EC%B9%98-%EC%B9%B4%ED%94%84%EC%B9%B4#entry273comment</comments>
      <pubDate>Tue, 30 Aug 2022 23:13:01 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Spring Cloud Eureka Server</title>
      <link>https://ssunw.tistory.com/entry/Spring-Boot-Spring-Cloud-Eureka-Server</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;유레카란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스가 서로를 찾을 때 사용되는 서비스 레지스트리의 이름이다. 유레카는 마이크로서비스 애플리케이션에 있는 모든 서비스의 중앙 집중 레지스트리로 작동한다. 유레카 자체도 마이크로서비스로 생각할 수 있고 더 큰 애플리케이션에서 서로 다른 서비스들이 서로를 찾는 데 도움을 주는 것이 목적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카의 역할 때문에 서비스를 등록하는 유레카 서비스 레지스트리를 가장 먼저 설정하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 인스턴스가 시작될 때 해당 서비스는 자신의 이름을 유레카에 등록한다. 동일한 이름을 갖는 서비스 인스턴스가 여러개 생성될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 순간 다른 서비스가 some-service 를 사용해야 할 때 이 때 some-service 의 특정 호스트 이름과 포트 정보를 other-service 코드에 하드 코딩하지 않는다. 대신 other-service 는 some-service 라는 이름을 유레카에서 찾으면 된다. 그러면 유레카는 모든 some-service 인스턴스의 정보를 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 oter-service 는 some-service 의 어떤 인스턴스를 사용할지 결정해야 한다. 이때 특정 인스턴스를 매번 선택하는 것을 피하기 위해 클라이언트 측에서 동작하는 로드 밸런싱 알고리즘을 사용한다. 이 때 사용될 수 있는 것이 넷플릭스 프로젝트인 리본(Ribbon)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;some-service 의 인스턴스를 찾고 선택하는 것은 other-service 가 해야할 일이지만, 이것을 리본에게 맡길 수 있다. 리본은 other-service 를 대신하여 some-service 인스턴스를 선택하는 클라이언트 측의 로드 밸런서이다. 그리고 other-service 는 리본이 선택하는 인스턴스에 대해 필요한 요청을 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카 서버 스타터 의존성을 아래와 같이 설정한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;
&amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-cloud-starter-netflix-eureka-server&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카 스타터 의존성이 지정되었으므로 이제 유레카 서버를 활성화시킨다. 애플리케이션이 시작되는 부트스트랩 클래스에서 @EnableEurekaServer 어노테이션을 추가한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistryApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceRegistryApplication.class, args);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 애플리케이션을 실행하고 localhost:8080 포트로 접속을 하면 유레카 웹 대시보드가 나올 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카 대시보드에서 여러 정보를 제공하는데 특히 어떤 서비스 인스턴스가 유레카에 등록되었는지를 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 서비스를 등록할 때 기대한 대로 잘됐는지 확인하기 위해 유레카 대시보드를 확인하면 된다. 현재는 아무 서비스도 등록되지 않았기 때문에 &quot;No instances available' 메시지가 나올 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유레카 구성하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나보다는 여러 개의 유레카 서버가 함께 동작하는 것이 안전하므로 SPOF 를 방지하기 위해서 유레카 서버들이 클러스터로 구성되는 것이 좋다. 기본적으로 유레카는 다른 유레카 서버로부터 서비스 레지스트리를 가져오거나 다른 유레카 서버의 서비스로 자기 자신을 등록할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕션 설정에서는 유레카의 고가용성이 바람직하다. 그러나 개발 시에 두 개 이상의 유레카 서버를 실행하는 것은 불편하기도하고 불필요하다. 개발 목적으로는 하나의 유레카 서버면 충분하기 때문이다. 유레카 서버를 올바르게 구성하지 않으면 30초마다 예외의 형태로 로그 메시지를 출력한다. 유레카는 30초 마다 다른 유레카 서버와 통신하면서 자신이 작동 중임을 알리고 레지스트리 정보를 공유하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에서 유레카 환경 설정을 구성하는 코드는 아래와 같다. application.yaml 에 아래처럼 구성하자.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;server:
  port: 8761
eureka:
  instance:
    hostname: localhost
  client:
    fetchRegistry: false
    registerWithEureka: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;server.port 속성을 8761 로, eureka.instance.hostname 속성을 localhost 로 설정했다. 유레카가 실행되는 호스트 이름과 포트를 나타낸다. 이 속성은 생략가능하고 만약 지정하지 않았다면 유레카가 환경 변수를 참고하여 생성한다.&lt;br /&gt;속성 값을 확실하게 알려주기 위해서 지정하는 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eureka.client.fetchRegistry 와 eureka.client.regiterWithEureka 는 유레카와 상호 작용하는 방법을 알려주기 위해 다른 마이크로 서비스에 설정할 수 있는 속성들이다. 유레카도 마이크로 서비스이기 때문에 이 두 속성은 유레카 서버가 다른 유레카 서버와 상호 작용하는 방법을 알려주기 위해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 속성의 기본값은 true 이다. 즉, 해당 유레카 서버가 다른 유레카 서버로부터 레지스트리 정보를 가져오며, 다른 유레카 서버의 서비스로 자신을 등록해야 한다는 것을 나타낸다. 여기서는 개발 환경 구성이기 때문에 다른 유레카 서버들이 필요 없으므로 두 속성의 값을 false 로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 eureka.client.serviceUrl 속성을 설정했다. 이 속성은 영역(zone) 이름과 이 영역에 해당하는 하나 이상의 유레카 서버 URL 을 포함하고 이 값은 Map 에 저장된다. Map 의 키인 defualtZone 은 클라이언트(여기서는 유레카 자기 자신)가 자신이 원하는 영역을 지정하지 않았을 때 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 유레카가 하나만 있으므로 defaultZone 에 해당하는 URL 이 유레카 자신의 URL 을 나타내며, 중괄호 안에 지정된 다른 속성(eureka.instance.hostname 과 server.port)의 값으로 대체된다. 따라서 defualtZone 은 &lt;a href=&quot;http://localhost:8761/eureka/&quot;&gt;http://localhost:8761/eureka/&lt;/a&gt; 가 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자체-보존 모드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eureka.server.enableSelfPreservation 속성이 있다. 유레카 서버는 서비스 인스턴스(유레카 서버의 클라이언트)가 자신을 등록하고 등록 생신 요청을 30초마다 전송하기를 기대한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 해당 서비스가 정상적으로 동작하고 있는지 확인하는 것이다. 일반적으로 세 번 갱신하는 동안 서비스 인스턴스로부터 등록 갱신 요청을 유레카 서버가 받지 못하면 해당 서비스 인스턴스의 등록을 췻고하게 된다. 레지스트리에서 서비스 인스턴스가 삭제되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 중단되는 서비스의 개수가 임계값을 초과하면 유레카 서버는 네트워크 문제가 생긴 것으로 간주하고 레지스트리에 등록된 나머지 서비스 데이터를 보존하기 위해 자체-보존 모드가 된다. 따라서 추가적인 서비스 인스턴스의 등록 취소가 방지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕션 환경에서는 자체-보존 모드를 true 로 설정하는 것이 좋다. 실제로 네트워크 문제가 생겨서 유레카로의 갱신 요청이 중단되었을 때 나머지 활성화 된 서비스들의 등록 취소를 방지할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 유레카를 처음 시작한 상태에 어떤 서비스도 등록되지 않았을 때는 오히려 문제가 발생할 수 있다. 이때는 eureka.server.enableSelfPreservation 속성을 false 로 설정하여 자체-보존 모드를 비활성화시킬 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;eureka:
    ...
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    enableSelfPreservation: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 문제 외에도 여러 가지 이유로 유레카가 갱신 요청을 받을 수 없는 개발 환경에서는 이 속성을 false 로 설정하는 것이 유용하다. 서비스 인스턴스들의 상태가 자주 변경될 수 있는 개발 환경에서 자체-보존 모드를 활성화하면 중단된 서비스의 등록이 계속 유지되어 다른 서비스가 해당 서비스를 사용하려고 할 때 문제를 발생시킬 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에서는 자체-보존 모드를 비활성화 시켜도 좋지만 프로덕션 환경에서는 활성화해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로덕션 환경의 스프링 클라우드 서비스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로 서비스를 프로덕션 환경으로 배포할 때는 고려해야 할 부분이 많다. 유레카의 고가용성과 보안은 개발 시에는 중요하지 않은 관점이지만, 프로덕션에서는 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개 이상의 유레카 인스턴스를 구성하는 가장 쉽고 간단한 방법은 application.yaml 파일에 스프링 프로파일을 지정하는 것이다. 그리고 한 번에 하나씩 프로파일을 사용해서 유레카를 두 번 시작시키면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 스프링 프로파일을 사용해서 두 개의 유레카 서버를 구성했다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;
eureka:
  client:
    service-url:
      defaultZone: http://${other.eureka.host}:${other.eureka.port}/eureka

---
spring:
  profiles: eureka-1
  application:
    name: eureka-1

server:
  port: 8761

eureka:
  instance:
    hostname: eureka1.tacocloud.com

other:
  eureka:
    host: localhost
    port: 8762

---
spring:
  profiles: eureka-2
  application:
    name: eureka-2

server:
  port: 8762

eureka:
  instance:
    hostname: eureka1.tacocloud.com

other:
  eureka:
    host: localhost
    port: 8762&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 설정을 갖는 기본 프로파일에는 eureka.client.serviceUrl.defualtZone 을 설정하였다. 여기에 지정된 other.eureka.host 와 other.eureka.port 변수의 값은 그 다음에 있는 각 프로파일 구성에서 설정된 값으로 대체된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 프로파일 다음에는 두 개의 프로파일인 eureka-1 과 eureka-2 가 구성되어 있으며, 각 프로파일에는 자신의 포트와 eureka.instance.hostname 이 설정되어 있다. 그리고 각 프로파일에 설정된 다른 유레카 인스턴스를 참조하기 위해 other.eureka.host 와 other.eureke.port 속성도 설정되어 있다. 이 속성들은 프레임워크와는 관계없으며, 기본 프라일에 지정된 other.eureka.host 와 other.eureka.port 변수의 값을 대체하기 위해 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eureka.client.fetchRegistry 와 eureka.client.regiterWithEureka 를 따로 설정하지 않아도 기본값이 true 이기 때문에 자동으로 구성 환경에 설정값으로 세팅된다. 따라서 각 유레카 서버가 다른 유레카 서버에 자신을 등록하고 레지스트리의 등록 정보를 가져온다.&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/272</guid>
      <comments>https://ssunw.tistory.com/entry/Spring-Boot-Spring-Cloud-Eureka-Server#entry272comment</comments>
      <pubDate>Wed, 24 Aug 2022 18:44:26 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] Transactional</title>
      <link>https://ssunw.tistory.com/entry/SpringBoot-Transactional</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;JDBC 트랜잭션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 에서 JDBC 를 사용하여 트랜잭션을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1661042083072&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 데이터베이스를 사용하기 위해 연결을 진행
Connection connection = dataSource.getConnection(); 
try (connection) {
    // Java 에서 데이터베이스 트랜잭션을 시작하는 유일한 방법이다. 
    // setAutoCommit(false) 는 트랜잭션을 직접 관리할 수 있게 해준다. 개발자가 원할 대 커밋 또는 롤백이 가능
    connection.setAutoCommit(false); 
    // 쿼리문 작성
    
    connection.commit(); // 커밋을 진행
} catch (SQLException e) {
    connection.rollback(); // 예외가 발생한 경우 롤백
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드처럼 간단한 케이스라면 상관이 없겠지만 트랜잭션을 여러개 발생해야 하는 경우에 복잡도가 올라간다. 두 개 이상의 DB에 접근해야 하는 작업을 하나의 트랜잭션으로 만들 수가 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Transactional&lt;/h3&gt;
&lt;pre id=&quot;code_1661042368548&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserService {
    @Transactional
    public Long registerUser(User user) {
        // 쿼리문 실행
        // 유저 데이터 DB 에 저장
        // userDao.save(user);
        return id;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 많이 사용되는 선언적 트랜잭션 방식으로 @Transactional 어노테이션을 붙여서 사용한다. 스프링 부트에서는 자동으로 @Transactional 어노테이션 사용 설정인 @EnableTransactionManagement 어노테이션 설정이 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional 이 있으면 JDBC 에서 필요한 코드를 삽입해준다.(getConnection(), setAutoCommit(false), 메소드 종료 시 커밋, 예외 발생 시 롤백)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션 경계 설정 전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 시작과 종료는 Service 레이어 내부 메소드에 달려 있다. 트랜잭션의 경계를 설정하는 방법으로는 PlatformTransactionManager 를 사용하여 트랜잭션 코드를 통해 임의로 지정하는 방법과 AOP 를 이용하여 지정하는 방법으로 나뉘며 AOP 를 활용한 @Transactional 어노테이션이 주로 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1661042735600&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본 전파 속성은 REQUIRED
@Transactional
public void invoke() {
    System.out.println(&quot;*** invoke start&quot;);
    insert1();
    insert2();
    System.out.println(&quot;*** invoke end&quot;);
}

// 기본 전파 속성은 REQUIRED
public void insert1() {
    bookRepository.save(new Book(&quot;책1&quot;));
}

// 기본 전파 속성은 REQUIRED
public void insert2() {
    bookRepository.save(new Book(&quot;책2&quot;));
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1661042763898&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DEBUG JpaTransactionManager : Creating new transaction with name [dev.highright96.springstudy.transaction.BookServiceImpl.invoke]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG JpaTransactionManager : Opened new EntityManager [SessionImpl(2076486718&amp;lt;open&amp;gt;)] for JPA transaction
DEBUG JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@351fadfa]
*** invoke start
DEBUG JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2076486718&amp;lt;open&amp;gt;)] for JPA transaction
DEBUG JpaTransactionManager : Participating in existing transaction
Hibernate: insert into book (isbn, flag, name) values (null, ?, ?)
DEBUG JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2076486718&amp;lt;open&amp;gt;)] for JPA transaction
DEBUG JpaTransactionManager : Participating in existing transaction
Hibernate: insert into book (isbn, flag, name) values (null, ?, ?)
*** invoke end
DEBUG JpaTransactionManager : Initiating transaction commit
DEBUG JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(2076486718&amp;lt;open&amp;gt;)]
DEBUG JpaTransactionManager : Closing JPA EntityManager [SessionImpl(2076486718&amp;lt;open&amp;gt;)] after transaction&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 살펴보면 JpaTransactionManager 가 트랜잭션 관리를 하는 것을 알 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;invoke start
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;invoke() 메소드가 시작되기 전에 DB 커넥션을 얻는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Participating in existing transaction
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;invoke() 메소드 내에서 insert1() 과 insert2() 에서 실행되는 트랜잭션은 기존 트랜잭션에 참가하며, 기본 트랜잭션 전파 설정은 REQUIRED 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;invoke end
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;invoke() 메소드가 실행된 이후, 트랜잭션을 커밋하고 커넥션을 반환환다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 시작되는 지점은 invoke() 메소드에 대한 프록시 메소드 내부이고, 다음 순서대로 호출 경계가 설정된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프록시 객체 호출&lt;/li&gt;
&lt;li&gt;Proxy.invoke() 시작&lt;/li&gt;
&lt;li&gt;트랜잭션 시작&lt;/li&gt;
&lt;li&gt;트랜잭션 전파 설정에 따른 내부 메소드 트랜잭션 처리&lt;/li&gt;
&lt;li&gt;트랜잭션 커밋&lt;/li&gt;
&lt;li&gt;Proxy.invoke() 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 메소드가 끝날 때까지 커밋 또는 커넥션 반환이 이루어지지 않고 트랜잭션 내부 메소드 내에서 발생하는 SQL 은 동일한 커넥션을 사용한다. 따라서 처리 시간이 긴 메소드의 경우에는 트랜잭션 단위를 조정해서 DB Lock 지속 시간이 지나치게 길어지거나 DB 커넥션 풀의 커넥션 개수가 모자라지 않도록 해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션 전파&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 전파는 임의의 한 트랜잭션이 경계에서 이미 진행 중인 트랜잭션이 존재할 때, 혹은 존재하지 않을 때 동작 방식을 결정하는 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 라는 트랜잭션이 시작되고 트랜잭션 A 가 끝나지 않을 시점에서 트랜잭션 B 메소드가 호출될 때 B 는 어느 트랜잭션에서 동작할까.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7CKSX/btrJ9t49I42/NvkkLqXSkCpPzlJbux1ep0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7CKSX/btrJ9t49I42/NvkkLqXSkCpPzlJbux1ep0/img.png&quot; data-alt=&quot;A, B 트랜잭션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7CKSX/btrJ9t49I42/NvkkLqXSkCpPzlJbux1ep0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7CKSX%2FbtrJ9t49I42%2FNvkkLqXSkCpPzlJbux1ep0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;241&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;A, B 트랜잭션&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 전파 설정에 따라 여러 시나리오가 존재하지만 아주 간단하게 두 가지만 확인하도록 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;A 라는 트랜잭션이 시작되었고 아진 진행 중인 상태라면 B 는 새로운 트랜잭션을 만들지 않고 A 에서 시작된 트랜잭션에 참여하게 된다. 이 경우 B 를 호출한 B.method() 까지 마치고 이후 작업에서 예외가 발생한다면 A 와 B 가 모두 A 트랜잭션에 하나로 묶여 있기 때문에 전체가 롤백된다.&lt;/li&gt;
&lt;li&gt;트랜잭션 B 와 트랜잭션 A 를 별도의 트랜잭션으로 구분하여 실행시킬 수 있다. 이 경우에 트랜잭션 B 경계를 빠져 나가는 순간, B 트랜잭션은 독립적으로 커밋되거나 롤백된다. 트랜잭션 A 는 B 트랜잭션에 영향을 받지 않고 진행된다. 즉, A 의 (2) 에서 예외가 발생하더라도 트랜잭션 A 만 롤백되고, 트랜잭션 B 는 아무런 영향을 받지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 A 트랜잭션과 B 트랜잭션은 서로 다른 Service 레이어에 속해야 한다. 만약 한 Service 레이어에서 A 트랜잭션을 실행하고, A 트랜잭션 코드에서 다시 내부 B 트랜잭션을 실행한다면 B 트랜잭션은 트랜잭션이 적용되지 않은 일반 코드가 실행된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REQUIRED(기본값)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 사용되는 트랜잭션 전파 속성으로 이미 진행 중인 트랜잭션이 없으면 새로 시작하고 진행 중인 트랜잭션이 있다면 기존 트랜잭션에 참여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 위 예시에서 1번 예시에 해당한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9QLcG/btrJ7aZD9Ic/HVsRjifO5eCqPaHhvIMAo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9QLcG/btrJ7aZD9Ic/HVsRjifO5eCqPaHhvIMAo1/img.png&quot; data-alt=&quot;REQUIRED&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9QLcG/btrJ7aZD9Ic/HVsRjifO5eCqPaHhvIMAo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9QLcG%2FbtrJ7aZD9Ic%2FHVsRjifO5eCqPaHhvIMAo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;491&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;REQUIRED&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 는 새로운 트랜잭션을 생성하고 B 는 트랜잭션을 생성하지 않고 진행 중인 트랜잭션 즉, A 트랜잭션에 합류한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 현재 서비스 레이어에서 실행되는 메소드는 A 라는 하나의 트랜잭션만 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REQUIRES_NEW&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 새로운 트랜잭션을 시작하는 방식으로 이미 진행중인 트랜잭션이 있든 없든 간에 항상 새로운 트랜잭션을 만들어 독립적으로 동작시킨다. 즉, 독립적인 트랜잭션이 보장되어야 하는 코드에 적용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lg0uJ/btrJ8HCASm7/qmpwM4FXhWSWWnNVsbKqi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lg0uJ/btrJ8HCASm7/qmpwM4FXhWSWWnNVsbKqi0/img.png&quot; data-alt=&quot;REQUIRES_NEW&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lg0uJ/btrJ8HCASm7/qmpwM4FXhWSWWnNVsbKqi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flg0uJ%2FbtrJ8HCASm7%2FqmpwM4FXhWSWWnNVsbKqi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;341&quot; height=&quot;494&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;REQUIRES_NEW&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 트랜잭션이 생성되고, B 트랜잭션이 생성된다. 각각의 트랜잭션은 커밋, 롤백을 따로따로 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B 기능이 끝날 때 B 트랜잭션이 커밋되고, A 기능이 끝날 때 A 트랜잭션이 커밋된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MANDATORY&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 진행중인 트랜잭션이 있으면 해당 트랜잭션에 합류하는 방식으로 REQUIRED 와 동일한 방식처럼 보이지만 진행 중인 트랜잭션이 없다면 예외를 발생시킨다는 점이 REQUIRED 와 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독립적인 트랜잭션을 생성하면 안되는 경우에 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NESTED&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 실행 중인 트랜잭션이 존재할 경우 중첩 트랜잭션을 생성한다. 중첩 트랜잭션이란 트랜잭션 내부에 다시 트랜잭션을 만드는 것으로 부모 트랜잭션에서 새로운 트랜잭션을 내부에 만드는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩 트랜잭션은 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만 중첩 트랜잭션은 부모 트랜잭션에 영향을 주지 않는다. REQUIRED 와 마찬가지로 부모 트랜잭션, 이미 진행중인 트랜잭션이 존재하지 않을 경우에는 독립적으로 트랜잭션을 생성해서 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn9hGb/btrJ6VuSGYb/jnU19aJxeWFk8X4xVAvUjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn9hGb/btrJ6VuSGYb/jnU19aJxeWFk8X4xVAvUjK/img.png&quot; data-alt=&quot;NESTED&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn9hGb/btrJ6VuSGYb/jnU19aJxeWFk8X4xVAvUjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn9hGb%2FbtrJ6VuSGYb%2FjnU19aJxeWFk8X4xVAvUjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;342&quot; height=&quot;492&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NESTED&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 라는 부모 트랜잭션이 존재하기 때문에 중첩 트랜잭션 B 를 생성한다. B 트랜잭션이 모두 끝나도 모든 커밋은 부모 트랜잭션인 A 트랜잭션의 끝에서 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 A 트랜잭션에서 예외가 발생해 롤백이 될 경우 B 트랜잭션도 같이 롤백이 된다. 하지만 중첩 트랜잭션인 B 트랜잭션에서 예외가 발생해 롤백이 발생할 경우 해당 롤백을 부모 트랜잭션인 A 트랜잭션에게 전파하지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NEVER&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션을 사용하지 않도록 강제하는 방식으로 트랜잭션을 사용하지 않는 방식이다. NOT_SUPPORTED 와 다른 점은 NOT_SUPPORTED 는 트랜잭션을 무시하고 보류하는 반면에 NEVER 는 트랜잭션이 존재하면 예외를 발생 시킨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIvWhV/btrJ8ZbxVbH/3qbFBtv6OMO7ppKhOqa020/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIvWhV/btrJ8ZbxVbH/3qbFBtv6OMO7ppKhOqa020/img.png&quot; data-alt=&quot;NEVER&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIvWhV/btrJ8ZbxVbH/3qbFBtv6OMO7ppKhOqa020/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIvWhV%2FbtrJ8ZbxVbH%2F3qbFBtv6OMO7ppKhOqa020%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;348&quot; height=&quot;496&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NEVER&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NOT_SUPPORTED&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 속성을 사용하면 트랜잭션 자체를 무시한다. 즉, 트랜잭션 없이 동작한다는 뜻이다. 트랜잭션의 경계 설정 대부분은 AOP 를 이용하여 여러 메소드를 일괄적으로 적용한다. 따라서 특별한 임의의 메소드 하나에만 트랜잭션을 적용하지 않기 위한 전파 속성이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Transactionl 주의점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional 어노테이션을 붙이면, 트랜잭션 처리를 위해 빈 객체에 대한 프록시 객체를 생성한다. 이때 프록시 타겟 클래스를 상속하여 생성된다. 따라서 상속이 불가능한 private 메소드의 경우 @Transactional 어노테이션을 붙여도 트랜잭션이 동작하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBMS 의 종류에 따라 DB Lock 지속 시간이나 Read Consistency 의 차이, 그리고 이로 인한 서비스 동시성 문제 등을 생각하면 메소드 단위로 경계가 설정되는 AOP 방식의 트랜잭션이 비효율적일 수 있다. 예를 들어, 실행 시간이 긴 메소드에 AOP 로 트랜잭션을 붙였을 경우 불필요하게 DB 커넥션을 점유하거나 DB Lock 이 유지되는 시간이 길어질 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1661045893993&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TransactionInvoker {
    private final A1Dao a1dao;
    private final A2Dao a2dao;
    
    @Transactionl
    public void invoke() {
        // 긴 Business Login
        doInternalTransaction();
    }
    
    public void doInternalTransaction() {
        a1dao.insertA1();
        a2dao.insertA2();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에를 들어, 위와 같은 상황에서는 비즈니스 로직이 트랜잭션에 포함되는 비효율이 발생할 수 있다. 이러한 경우에 개발자가 직접 트랜잭션의 경계를 설정할 필요가 있고, 이 때 TransactionTemplate 이 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1661046907156&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TransactionInvoker {
    private final A1Dao a1dao;
    private final A2Dao a2dao;
    private final TransactionTemplate transactionTemplate;
    
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
        this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    }
    
    public void invoke() throws Exception {
        // 비즈니스 로직
        doInternalTransaction();
    }
    
    private void doInternalTransaction() throws Exception {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    a1dao.insertA1();
                    a2dao.insertA2();
                } catch (Exception e) {
                    status.setRollbackOnly();
                }
                return;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 에서 setter 를 통해 TransactionTemplate 을 주입받는다. 그 후 TransactionTemplate 을 생성 및 Transaction 속성을 설정한다.&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/271</guid>
      <comments>https://ssunw.tistory.com/entry/SpringBoot-Transactional#entry271comment</comments>
      <pubDate>Sun, 21 Aug 2022 10:55:48 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] kubernetes 아키텍쳐</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-kubernetes-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;585&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blwvQl/btrIADBw9yy/VEQOW3SCkkdgvUdNAPCIkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blwvQl/btrIADBw9yy/VEQOW3SCkkdgvUdNAPCIkK/img.png&quot; data-alt=&quot;kubrenetes 공식 문서에서 설명하고 있는 아키텍쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blwvQl/btrIADBw9yy/VEQOW3SCkkdgvUdNAPCIkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblwvQl%2FbtrIADBw9yy%2FVEQOW3SCkkdgvUdNAPCIkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1252&quot; height=&quot;585&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;585&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kubrenetes 공식 문서에서 설명하고 있는 아키텍쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터는 물리적 혹은 논리적으로 여러개의 리소스를 하나로 묶어서 사용하는 것을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Docker 는 클러스터라는 기능은 없다. 도커 컨테이너들은 각각 따로 관리된다. 이를 하나로 한꺼번에 관리할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 형태를 stand alone 이라고 부르며, stand alone 형태는 관리하기가 힘들다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 클러스터는 리소스를 하나로 묶어서 한번에 관리를 하는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿠버네티스 클러스터의 핵심&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Control Plane&lt;/li&gt;
&lt;li&gt;Node&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Node(구 명칭 worker, Minions)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Control Plane 과 Node 라는 VM 형태로 나눠진다. 즉 역할이 분리되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node 는 실제 컨테이너를 실행해준다. 즉, Node 안에 도커(podman, CRIO...)가 설치되있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Control Plane 보다 Node 의 갯수가 더 많다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Control Plane(구 명칭 Master)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node 를 관리해주는 녀석이다. 그래서 Control Plane 에 장애가 발생하면 Node 를 관리할 수 없다. 그래서 SPoF 를 방지하기 위해 여러 대의 Control Plane 을 띄워둔다. 일반적으로는 3대이상을 Control Plane 으로 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Control Plane 을 클러스터링 할 때 절대 짝수개로 두면 안된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Control Plane 컴포넌트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;kube-apiserver&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kube-apiserver 는 쿠버네티스 API를 노출하는 쿠버네티스 컨트롤 플레인 컴포넌트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 통신은 kube-apiserver 을 통해 통신이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 API 서버의 주요 구현은&amp;nbsp;kube-apiserver&amp;nbsp;이다. kube-apiserver는 수평으로 확장되도록 디자인되었다. 즉, 더 많은 인스턴스를 배포해서 확장할 수 있다. 여러 kube-apiserver 인스턴스를 실행하고, 인스턴스간의 트래픽을 균형있게 조절할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;kube-controller-manager&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 핵심으로 쿠버네티스 전체를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 컨테이너를 관리하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 Pod 를 관리하고, Pod 는 컨테이너가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러는 다음을 포함한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드 컨트롤러: 노드가 다운되었을 때 통지와 대응에 관한 책임을 가진다.&lt;/li&gt;
&lt;li&gt;레플리케이션 컨트롤러: 시스템의 모든 레플리케이션 컨트롤러 오브젝트에 대해 알맞은 수의 파드들을 유지시켜 주는 책임을 가진다. (쿠버네티스의 핵심은 컨테이너의 복제이다.)&lt;/li&gt;
&lt;li&gt;엔드포인트 컨트롤러: 엔드포인트 오브젝트를 채운다(즉, 서비스==Network(LB) 와 파드를 연결시킨다.)&lt;/li&gt;
&lt;li&gt;서비스 어카운트 &amp;amp; 토큰(인증) 컨트롤러: 새로운 네임스페이스에 대한 기본 계정과 API 접근 토큰을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;cloud-controller-manager&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스가 클라우드에 있을 경우(EKS, AKS, GKE), 클러스터를 클라우드 공급자의 API에 연결하고, 해당 클라우드 플랫폼과 상호 작용하는 컴포넌트와 클러스터와만 상호 작용하는 컴포넌트를 구분할 수 있게 해 준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;kube-scheduler&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드가 배정되지 않은 새로 생성된&amp;nbsp;Pod 를 감지하고, 실행할 노드를 선택하는 컨트롤 플레인 컴포넌트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Pod 를 어느 노드에 어떻게 배치할 것인지 설정해준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;etcd(etc daemon)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 클러스터 데이터를 담는 쿠버네티스 뒷단의 key value 스토리지이다.(DB)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 클러스터에서 etcd를 뒷단의 저장소로 사용한다면, 이 데이터를&amp;nbsp;백업하는 계획은 필수이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Node 컴포넌트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;kubelet&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터의 각&amp;nbsp;노드에서 실행되는 에이전트이다. Kubelet은&amp;nbsp;파드에서&amp;nbsp;컨테이너가 확실하게 동작하도록 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kube-apiserver 에 컨테이너를 만들어달라고 요청을 보내면 kubelet 이 파드를 만들고 파드에서 컨테이너가 동작하도록 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;kube-proxy&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker proxy 는 네트워크의 정책(iptables 정책)을 관리한다. 예를 들어 포트 포워딩 할 때 이 녀석을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kube-proxy 도 똑같이 네트워크 정책을 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 얘기하자면 kubelet 은 컨테이너를 담당하는 녀석이고, kube-proxy 는 네트워크를 담당하는 녀석이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Control Plane 의 컴포넌트들도 모두 컨테이너이기 때문에 해당 컨테이너를 관리하는 kubelet 과 kube-proxy 가 Control Plane 에도 존재한다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/270</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-kubernetes-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90#entry270comment</comments>
      <pubDate>Mon, 1 Aug 2022 17:57:33 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] 빌더 패턴</title>
      <link>https://ssunw.tistory.com/entry/SpringBoot-%EB%B9%8C%EB%8D%94-%ED%8C%A8%ED%84%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity 클래스를 생성할 때, 주의할 것은 &lt;b&gt;무분별한 setter 메소드 생성이다&lt;/b&gt;. 자바빈 규약을 생각하시면서 getter/setter를 무작정 생성하는데, 이렇게 되면 해당 클래스의 인스턴스 값들이&amp;nbsp;&lt;b&gt;언제 어디서 변해야하는지 코드상으로 명확히 구분할수가 없어, 차후 기능변경시 정말 복잡&lt;/b&gt;해진다.해당 필드의 값 변경이 필요하면&amp;nbsp;&lt;b&gt;명확히 그 목적과 의도를 나타낼 수 있는 메소드&lt;/b&gt;를 추가해야만 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 주문 취소 메소드를 만든다고 가정하면 아래 코드로 비교해보시면 될 것 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;잘못된 사용
public class Order{
    public void setStatus(boolean status){
        this.status = status
    }
}

public void 주문서비스의_취소메소드 (){
   order.setStatus(false);
}

올바른 사용
public class Order{
    public void cancelOrder(){
        this.status = false;
    }
}
public void 주문서비스의_취소메소드 (){
   order.cancelOrder();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그러면 여기서 한가지 궁금한 점이 생길 수 있다. 기본 생성자도&amp;nbsp;AccessLevel.PROTECTED로 막아놓고,&amp;nbsp;&lt;b&gt;setter 메소드도 없는 이 상황에서 어떻게 값을 채워 DB에 insert 해야할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 구조는 생성자를 통해 최종 값을 채운후 DB에 Insert 하는것이며, 값 변경이 필요한 경우 해당 이벤트에 맞는 public 메소드를 호출하여 변경하는 것을 전제로 한다.여기서 생성자 대신에&amp;nbsp;@Builder를 통해 제공되는 빌더 클래스를 사용합니다. 생성자나 빌더나&amp;nbsp;&lt;b&gt;생성 시점에 값을 채워주는 역할은 똑같다&lt;/b&gt;. 다만, 생성자의 경우 지금&amp;nbsp;&lt;b&gt;채워야할 필드가 무엇인지 명확히 지정할수가 없다&lt;/b&gt;. 예를 들어 아래와 같은 생성자가 있다면&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public Example(String a, String b){
    this.a = a;
    this.b = b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가&amp;nbsp;new Example(b,a)처럼 a와 b의 위치를 변경 해도 실제로 코드를 실행하기전까진 전혀 문제를 찾을수가 없다.하지만 빌더를 사용하게 되면 아래와 같이&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;Example.builder()
    .a(a)
    .b(b)
    .build();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어느 필드에 어떤 값을 채워야 할지&lt;/b&gt;&amp;nbsp;명확하게 인지할 수 있다.&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/269</guid>
      <comments>https://ssunw.tistory.com/entry/SpringBoot-%EB%B9%8C%EB%8D%94-%ED%8C%A8%ED%84%B4#entry269comment</comments>
      <pubDate>Mon, 1 Aug 2022 08:57:29 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] N+1 문제</title>
      <link>https://ssunw.tistory.com/entry/SpringBoot-N1-%EB%AC%B8%EC%A0%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;LAZY 로딩과 EAGER 로딩의 차이를 먼저 알아야 한다.(지연 로딩, 즉시 로딩)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지연로딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Team &amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash; Member 는 서로 OneToMany, ManyToOne 관계를 맺고 있다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class Team extends CoreEntity {

	// ... 중략

	@OneToMany(mappedBy = &quot;team&quot;, cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
	private List&amp;lt;Member&amp;gt; members = new ArrayList()&amp;lt;&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class Member extends CoreEntity {

	// ... 중략

	@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
	@JoinColumn(name = &quot;member_idx&quot;)
	private Team team;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형태로 Team 엔티티에 연관관계를 맺고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Member 객체를 조회할 때 Team 객체를 불러오는 방식에 차이가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 FetchType.LAZY &amp;rarr; 지연로딩을 사용했기 때문에 Team 객체를 프록시 객체로서 가져오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 객체란 무엇이냐? 프록시 객체 내부의 속성을 조회하기 전 까지는 DB 에 쿼리문을 날리지 않는 null 값을 가진 객체라고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Team 객체는 프록시 객체이기 때문에 우리가 직접 조회하기 전 까지는 DB 에 쿼리문을 날리지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Member 객체를 조회할 때 select 쿼리문을 한 번만 날리고 프록시 객체를 조회할 경우에 다시 쿼리를 날린다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;즉시로딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 로딩은 말 그대로 객체를 조회할 때 연관관계를 맺은 객체까지 전부 조회하는 것을 뜻 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 N+1 문제가 발생하며, ManyToOne, OneToOne 의 경우 기본적으로 즉시 로딩이며 OneToMany, ManyToMany 는 지연 로딩으로 설정되어 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;N+1 문제의 원인&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉시 로딩으로 모든 연관관계 객체들을 불러올 경우 select 로 연관관계 까지 조회하므로 N+1 발생&lt;/li&gt;
&lt;li&gt;지연 로딩으로 조회를 하고나서 연관관계 객체를 다시 조회할 경우 N+1 문제 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public void N1_test() {

	List&amp;lt;Team&amp;gt; teams = teamRepository.findAll();

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Member 클래스의 Team 객체와의 연관관계 속성이 즉시 로딩이였을 경우 위의 케이스에서 N+1 문제 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연 로딩일 경우에도 프록시 객체를 다시 조회하는 경우에 JPQL 이 실행되어 N+1 문제가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class Team extends CoreEntity {

	// ... 중략

	@OneToMany(mappedBy = &quot;team&quot;, cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
	private List&amp;lt;Member&amp;gt; members = new ArrayList()&amp;lt;&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Transactional
public void N1_test() {
	
	List&amp;lt;Team&amp;gt; teams = teamRepository.findAll();

	for(Team team : teams) {
		System.Out.Println(team.getMembers.getSize());
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 경우 team 레포지토리에서 모든 team 의 값들을 리스트로 뽑아오고, 해당하는 각각의 팀들의 멤버를 조회하는 로직이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 멤버 정보를 조회하면 팀 객체에 대한 조회는 이미 끝난 상태여서 JOIN 으로 쿼리가 생성이 안되다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단지, 팀 객체에 대한 ID 로 조회할 수 밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 where team.memberId = ? 형식으로 JPQL 쿼리를 생성하고, 이로 인해 매번 조회 쿼리가 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N 번 실행되는 이슈가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버를 조회할 때, 팀 객체에 대한 조회는 이미 끝난 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 JOIN 을 사용하지 못하기 때문에 JPQL 쿼리를 만들어서 반복문 횟수만큼 쿼리문을 날린다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;N+1 문제 해결법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연 로딩에서 문제가 생기는 이유 &amp;rarr; DB 통신은 findAll 에서 끝났는데 프록시 객체를 조회해서 다시 JPQL 쿼리를 날리기 때문에 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, join 이 되지 않아서 생기는 문제라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 join 을 해주면 해결될 문제가 아닐까? &amp;rArr; Spring 에선 fetch join 을 통해 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 레포지토리에서 네이티브 쿼리를 만들어서 해결할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;@Query(&quot;select DISTINCT t from Team t join fetch t.members&quot;)
List&amp;lt;Team&amp;gt; findAllJoinFetch();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 N+1 문제가 해결이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 fetch join 도 문제가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 째로 JPA 가 제공하는 Pageable 기능 사용 X, 둘 째로 1:N 관계가 2개인 엔티티를 fetch join 불가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fetch join&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sql 조인 종류가 아니고, JPQL 에서 성능 최적화를 위해 제공하는 기능으로 연관 관계를 맺고 있는 엔티티를 SQL 한 번에 함께 조회하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 로딩과 똑같지만 fetch join 은 쿼리로 내가 원하는 객체, 그래프를 명시적, 동적으로 뽑을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 조인 시에는 연관된 엔티티를 함께 조회해주지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페치 조인을 사용할 때만 연관 관계인 엔티티도 함께 조회한다.&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/268</guid>
      <comments>https://ssunw.tistory.com/entry/SpringBoot-N1-%EB%AC%B8%EC%A0%9C#entry268comment</comments>
      <pubDate>Mon, 1 Aug 2022 08:55:20 +0900</pubDate>
    </item>
    <item>
      <title>[minikube 오류] The connection to the server localhost:8443 was refused - did you specify the right host or port? output: stderr nThe connection to the server localhost:8443 was refused - did you specify the right host or port?</title>
      <link>https://ssunw.tistory.com/entry/minikube-%EC%98%A4%EB%A5%98-The-connection-to-the-server-localhost8443-was-refused-did-you-specify-the-right-host-or-port-output-stderr-nThe-connection-to-the-server-localhost8443-was-refused-did-you-specify-the-right-host-or-port</link>
      <description>&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;minikube delete --all --purge

minikube start
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조: &lt;a href=&quot;https://github.com/kubernetes/minikube/issues/12274#issuecomment-1006201978&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kubernetes/minikube/issues/12274#issuecomment-1006201978&lt;/a&gt;&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/267</guid>
      <comments>https://ssunw.tistory.com/entry/minikube-%EC%98%A4%EB%A5%98-The-connection-to-the-server-localhost8443-was-refused-did-you-specify-the-right-host-or-port-output-stderr-nThe-connection-to-the-server-localhost8443-was-refused-did-you-specify-the-right-host-or-port#entry267comment</comments>
      <pubDate>Thu, 28 Jul 2022 21:26:57 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD] EKS 에서 Jenkins, ArgoCD 를 활용한 파이프라인 구성</title>
      <link>https://ssunw.tistory.com/entry/CICD-EKS-%EC%97%90%EC%84%9C-Jenkins-ArgoCD-%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%84%B1-1</link>
      <description>&lt;h1&gt;CI/CD 미니 프로젝트&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목차&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I. 과제 및 배경 소개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍쳐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 일정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 GitHub 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EKS와 K8S의 차이점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;II. 구현 절차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. EC2 인스턴스 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 1-1. EC2 인스턴스에 부여 IAM 역할 부여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 1-2. 보안 그룹 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 1-3. 인스턴스에 접속해 도구 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Repository의 manifest로 eksctl 클러스터 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2-1. EC2 인스턴스 공개키 git 계정에 등록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2-2. git clone으로 manifest 가져오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2-3. 파일 기반 eksctl 클러스터 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. CI&amp;mdash;Jenkins&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 3-1. 기본 설정: 환경변수, 플러그인, 크레덴셜, 슬랙 알림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1) Global Tool Configuration에서 환경변수 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;2) Plugin 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;3) Credentials 등록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;4) Slack 알림을 위한 젠킨스 CI 도구 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 3-2. 파이프라인 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;1) Jenkins Job 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;2) Jenkinsfile의 내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;3) Jenkins CI-Slack Notification 실행 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. CD&amp;mdash;ArgoCD&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 4-1. ArgoCD 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;1) aws-load-balancer-controller 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;2) ArgoCD 설치 확인 및 웹 UI 접속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 4-2. Git 저장소에서 바로 애플리케이션 배포하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;1) Repository 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;2) ArgoCD Application 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;3) ArgoCD CLI&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.CI/CD 연동 확인&amp;mdash;Jenkins &amp;amp; ArgoCD&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 추가구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 6-1. metrics-server&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 6-2. Cluster Autoscaler&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;1) 수동 스케일링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;2) 자동 스케일링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 6-3. CloudWatch Container Insight&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. Troubleshooting&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;I. 과제 및 배경 소개&lt;/b&gt;&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 프로젝트의 목표는 클라우드 인프라 오케스트레이션의 구현이다. AWS EKS 상에 클러스터를 구성하고, CI/CD 도구인 Jenkins와 ArgoCD를 주로 사용하여 개발부터 배포까지 자동적이고 유기적인 방식으로 이루어지도록 구성한다. 인프라 구성은 아키텍처에서 상술한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아키텍처&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca8SR7/btrIiJ2nTvy/lOx1kPs9KJfPKr6wk03cY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca8SR7/btrIiJ2nTvy/lOx1kPs9KJfPKr6wk03cY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca8SR7/btrIiJ2nTvy/lOx1kPs9KJfPKr6wk03cY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca8SR7%2FbtrIiJ2nTvy%2FlOx1kPs9KJfPKr6wk03cY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;853&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자가 깃허브에 코드를 push 한다.&lt;/li&gt;
&lt;li&gt;Jenkins를 GitHub에 연결하고, Jenkins는 GitHub repository에 commit 된 내용을 감지한다.&lt;/li&gt;
&lt;li&gt;Pipeline을 생성하고 GitHub에 위치한 Jenkinsfile 스크립트를 찾아 자동 실행되게 설정한다.(checkout &amp;rarr; maven build)&lt;/li&gt;
&lt;li&gt;Dockerfile을 참조해 image를 빌드한다.&lt;/li&gt;
&lt;li&gt;Docker image를 Docker Hub에 push 한다.&lt;/li&gt;
&lt;li&gt;Jenkins 에서 manifest를 업데이트해 image tag를 수정한다.&lt;/li&gt;
&lt;li&gt;ArgoCD 에서 image tag 수정을 감지하고 싱크 한다.&lt;/li&gt;
&lt;li&gt;ArgoCD에서 docker image pull을 진행한다.&lt;/li&gt;
&lt;li&gt;ArgoCD에서 소스 코드에서 변경된 부분을 반영하여 업데이트하거나 파드를 재생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;환경 구성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chpfCO/btrIinZyVJw/c5QLWDHgmHuNuPLlj9wGsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chpfCO/btrIinZyVJw/c5QLWDHgmHuNuPLlj9wGsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chpfCO/btrIinZyVJw/c5QLWDHgmHuNuPLlj9wGsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchpfCO%2FbtrIinZyVJw%2Fc5QLWDHgmHuNuPLlj9wGsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;537&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 구성은 위에서 보이는 아키텍쳐와 같다. 자세한 설명은 아래 순서를 참고하여 실행하며 알아보도록 하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 GitHub 주소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/seongwoo-choi/mini-cicd-project.git&quot;&gt;https://github.com/seongwoo-choi/mini-cicd-project.git&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EKS와 K8S의 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EKS(Elastic Kubernetes Service)는 AWS에서 제공하는 '관리형 쿠버네티스' 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스를 따로 구축하고 관리할 때는 마스터 노드 및 etcd를 이중화, 삼중화 하는것이 매우 중요한데, EKS에서는 마스터노드와 etcd 노드를 EKS가 관리해주고 Private Link로 연결되어 내부망 통신으로 안전하게 워커노드와 통신 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. AWS Managed&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k8s는 Master Node와 Worker Node 둘다 관리해야 하지만 EKS를 사용하면 Master Node와 Worker Node 둘 다 AWS Managed에서 한번에 관리가 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 클러스터 업그레이드 용이성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k8s에서 클러스터를 업그레이드 하려고 하면 매뉴얼과 절차대로 진행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 EKS를 사용하게 되면 AWS에서 클러스터 업그레이드를 클릭 한번으로 업데이트를 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 네트워크 할당&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에서 파드를 생성하면 해당 파드는 클러스터 내에서만 볼 수 있게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;가상 네트워크&lt;/b&gt;를 부여받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 플러그인으로는 AWS CNI라는 플러그인이 사용되는데 해당 플러그인은 일반적인 CNI(Flannel, Callico)와 차이점이 있다.클러스터의 IP 대역을 VPC로 할당 받기 때문에 파드를 생성하면 VPC와 동일한 네트워크를 공유하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 파드는 클러스터를 생성하는 데 사용되는 서브넷의 IP를 사용하게 되고 Worker Node는 여러 네트워크 인터페이스(=ENI)로 파드(=오브젝트)의 IP를 관리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 인증방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EKS와 k8s와 다르게 EKS는 AWS IAM을 인증방식으로 사용해 사용자에게 역할을 할당시킨다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;II. 구현 절차&lt;/b&gt;&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. EC2 인스턴스 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Repository로 Docker Hub 또는 ECR 사용 가능, 본 프로젝트에서는 Docker Hub를 선택했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-1. EC2 인스턴스에 IAM 역할 부여&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 인스턴스에서 eksctl 을 사용하여 클러스터를 생성하기 때문에 AdministratorAccess 정책을 부여한 역할이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AdministratorAccess 정책으로 EKS용 역할 생성하기&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IAM 접속 &amp;rarr; 액세스 관리 &amp;rarr; 역할 &amp;rarr; 역할 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;823&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSMUji/btrIdynMVko/fYSFBbQJPb3jwLbBVxUdPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSMUji/btrIdynMVko/fYSFBbQJPb3jwLbBVxUdPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSMUji/btrIdynMVko/fYSFBbQJPb3jwLbBVxUdPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSMUji%2FbtrIdynMVko%2FfYSFBbQJPb3jwLbBVxUdPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;823&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;823&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 인스턴스에 역할을 부여할 것이므로 EC2 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTbyNF/btrIekvvEcy/Ii4nugjAzJCFFg3k6yAHe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTbyNF/btrIekvvEcy/Ii4nugjAzJCFFg3k6yAHe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTbyNF/btrIekvvEcy/Ii4nugjAzJCFFg3k6yAHe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTbyNF%2FbtrIekvvEcy%2FIi4nugjAzJCFFg3k6yAHe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;430&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 관리에서 모든 권한을 줄 수 있는 AdministratorAccess 정책을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한 추가 후 역할 생성 버튼 클릭하여 역할을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인스턴스의 IAM 역할 수정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인스턴스 선택 &amp;rarr; 작업 &amp;rarr; 보안 &amp;rarr; IAM 역할 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgYpin/btrIazAwwuf/yCT9rSph7ZOVmmRQPvEKmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgYpin/btrIazAwwuf/yCT9rSph7ZOVmmRQPvEKmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgYpin/btrIazAwwuf/yCT9rSph7ZOVmmRQPvEKmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgYpin%2FbtrIazAwwuf%2FyCT9rSph7ZOVmmRQPvEKmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;591&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 인스턴스에서 IAM 역할 수정 화면&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-2. 보안 그룹 생성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ssh 와 jenkins 에 접속하기 위한 8080 포트 추가&lt;/li&gt;
&lt;li&gt;접속 가능한 ip주소는 현재 인스턴스를 생성하고 실행할 컴퓨터인 나의 ip 주소를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;353&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rLady/btrIiLMIK9h/9T4Ysp007iT9hekBNECKmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rLady/btrIiLMIK9h/9T4Ysp007iT9hekBNECKmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rLady/btrIiLMIK9h/9T4Ysp007iT9hekBNECKmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrLady%2FbtrIiLMIK9h%2F9T4Ysp007iT9hekBNECKmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;353&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-3. 인스턴스에 접속해 도구 설치&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;awscli, aws-iam-authenticator, kubectl, eksctl&lt;/li&gt;
&lt;li&gt;Jenkins, Docker, Helm, ArgoCD 등&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo hostname jenkins
sudo su - jenkins&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;awscli 설치&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;curl &quot;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&quot; -o &quot;awscliv2.zip&quot; 
sudo apt install unzip
sudo unzip awscliv2.zip  
sudo ./aws/install
aws --version&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS configure&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 명령줄 인터페이스(AWS CLI)는 명령줄 셸에서 명령을 사용하여 AWS 서비스와 상호 작용할 수 있는 오픈 소스 도구다. AWS CLI를 사용함으로써&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;터미널의 명령 프롬프트&lt;/b&gt;에서 브라우저 기반인 aws 콘솔에서 제공하는 것과 동일한 기능을 구현하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;명령 실행을 시작&lt;/b&gt;할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws configure 명령어를 사용해 IAM에서 미리 생성해 둔 사용자로 aws 계정에 로그인이 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IAM 사용자 생성: IAM &amp;rarr; 액세스 관리 &amp;rarr; 사용자 &amp;rarr; 사용자 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpgkps/btrIekvvEmi/E0iXrazaB2ckzbkhhlHIzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpgkps/btrIekvvEmi/E0iXrazaB2ckzbkhhlHIzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpgkps/btrIekvvEmi/E0iXrazaB2ckzbkhhlHIzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbpgkps%2FbtrIekvvEmi%2FE0iXrazaB2ckzbkhhlHIzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;641&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 이름 지정 후 자격 증명 유형을 선택한다. 여기서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;액세스 키&lt;/b&gt;를 이용하여 로그인을 할 것이기 때문에 액세스 키 로그인 방식만 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgoQK3/btrIiFeCeA7/srkjkcc2MUCAatnsYaow30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgoQK3/btrIiFeCeA7/srkjkcc2MUCAatnsYaow30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgoQK3/btrIiFeCeA7/srkjkcc2MUCAatnsYaow30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgoQK3%2FbtrIiFeCeA7%2Fsrkjkcc2MUCAatnsYaow30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;492&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AdministratorAccess 권한을 부여하여 인스턴스에서 작업 수행이 가능하도록 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vvO9X/btrIh46MD51/tEVvrDu2mfspOELhm5FwhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vvO9X/btrIh46MD51/tEVvrDu2mfspOELhm5FwhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vvO9X/btrIh46MD51/tEVvrDu2mfspOELhm5FwhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvvO9X%2FbtrIh46MD51%2FtEVvrDu2mfspOELhm5FwhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;414&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태그는 생략 후 IAM을 최종으로 생성하면 아래와 같은 메세지가 출력된다. &amp;ldquo;.csv 다운로드&amp;rdquo; 후 파일 안에 있는 액세스 키 ID와 비밀 액세스 키를 복사하여 aws configure 로그인 시 아래와 같이 입력한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6jqjU/btrIeMTkwRa/th4Z4vsy3WGS2WsoqbW3Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6jqjU/btrIeMTkwRa/th4Z4vsy3WGS2WsoqbW3Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6jqjU/btrIeMTkwRa/th4Z4vsy3WGS2WsoqbW3Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6jqjU%2FbtrIeMTkwRa%2Fth4Z4vsy3WGS2WsoqbW3Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;564&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서대로 액세스 키 ID, 비밀 키, 사용할 리전, 그리고 아웃풋 포맷(보통 JSON으로 설정)을 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws sts get-caller-identity 명령어를 통해 로그인 된 ID 정보를 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;aws-load-balancer-controller&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/network-load-balancing.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/network-load-balancing.html&lt;/a&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/alb-ingress.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/alb-ingress.html&lt;/a&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/aws-load-balancer-controller.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/aws-load-balancer-controller.html&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;helm repo add eks &amp;lt;https://aws.github.io/eks-charts&amp;gt;
helm repo update
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;aws sts get-caller-identity # account 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--set image.repository=dkr.ecr.&lt;b&gt;region-code&lt;/b&gt;.amazonaws.com/amazon/aws-load-balancer-controller&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=***myeks-custom*** --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller --set image.repository=***602401143452***.dkr.ecr.ap-northeast-2.amazonaws.com/amazon/aws-load-balancer-controller
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;aws-iam-authentication 설치&lt;/h3&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;curl -o aws-iam-authenticator https://amazon-eks.s3.us-west-2.amazonaws.com/1.21.2/2021-07-05/bin/linux/amd64/aws-iam-authenticator
chmod +x ./aws-iam-authenticator
mkdir -p $HOME/bin &amp;amp;&amp;amp; cp ./aws-iam-authenticator $HOME/bin/aws-iam-authenticator &amp;amp;&amp;amp; export PATH=$PATH:$HOME/bin
echo 'export PATH=$PATH:$HOME/bin' &amp;gt;&amp;gt; ~/.bashrc

aws-iam-authenticator help&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 설정한 것 처럼, IAM 권한을 사용하여 AWS 서비스 및 리소스에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;액세스 권한을 지정하고 제어&lt;/b&gt;할 수 있다. IAM 역할에 권한을 부여하려면 액세스 유형, 수행할 수 있는 작업, 작업을 수행할 수 있는 리소스를 지정하는 정책을 연결하면 된다. IAM 정책을 사용하여 특정 AWS 서비스 API 및 리소스에 대한 액세스 권한을 부여할 수 있다. (여기서는 ec2 인스턴스에 권한을 부여하기 위해 설치하였다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;curl&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어로 aws-iam-authentication의 파일을 다운로드하고, **chmod**를 사용해 모두에게 실행 권한을 부여한다. 그리고 환경 변수를 설정하고 배쉬 설정 파일안에 세팅해준다. 마지막으로 help 명령어를 실행시켜 정상적으로 작동 여부를 확인 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kubectl 1.22.8 설치&lt;/h3&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo &quot;deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main&quot; | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
apt-cache madison kubectl | grep 1.22.8
sudo apt-get install kubectl=1.22.8-00 -y
sudo apt-mark hold kubectl&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 커맨드 라인 도구인 kubectl을 사용하면 쿠버네티스 클러스터에 대해 명령을 실행할 수 있다. kubectl 을 사용하여 애플리케이션을 배포하고, 클러스터 리소스를 검사 및 관리하고, 로그를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용할 버전은 1.22.8 버전이며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;apt-get&lt;/b&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;curl&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어를 사용해 설치한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;eksctl 설치&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;curl --silent --location &quot;https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz&quot; | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin
eksctl version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eksctl은 Amazon EKS에서 Kubernetes 클러스터를 생성 및 관리하기 위한 명령줄 유틸리티다. eksctl은 Amazon EKS용 노드가 있는 새 클러스터를 가장 쉽고 간편하게 생성하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eksctl 을 설치 수 있는 깃허브 파일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;curl&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어로 다운받고, 파일의 위치를 /usr/local/bin 밑으로 이동시킨다. 설치 후 eksctl version 명령어를 사용해 버전 확인이 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jenkins 설치&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;sudo apt update

sudo apt-get install default-jdk -y
java -version

sudo apt install maven -y
mvn --version

wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
echo deb http://pkg.jenkins.io/debian-stable binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list
sudo apt update
sudo apt install jenkins -y

sudo cat /var/lib/jenkins/secrets/initialAdminPassword # 젠킨스 접속시 사용되는 초기 암호 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;빌드와 테스트 등의 워크플로우에서 지속적인 통합(CI, Continous Integration) 서비스를 제공하는 툴&lt;/b&gt;이다. 반복되는 업무를 자동화 하기 위하여 구성된 도구로 한번의 설정으로 빌드를 자동화 시킬 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Docker 설치&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;sudo apt install docker.io -y
sudo usermod -aG docker $USER
sudo systemctl start docker
sudo systemctl enable docker
sudo systemctl status docker

sudo usermod -aG docker jenkins
sudo service jenkins restart
sudo systemctl daemon-reload
sudo service docker stop
sudo service docker start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 는 애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼이다. Docker는 소프트웨어를 컨테이너라는 표준화된 유닛으로 패키징하며, 이 컨테이너에는 라이브러리, 시스템 도구, 코드, 런타임 등 소프트웨어를 실행하는 데 필요한 모든 것이 포함되어 있다. 이 프로젝트 내에서는 이미지를 빌드하는 역할을 수행한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Helm CLI 설치&lt;/h3&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg &amp;gt; /dev/null
sudo apt-get install apt-transport-https --yes
echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main&quot; | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Helm 은 Kubernetes 클러스터에서 좀 더 쉽게 애플리케이션을 설치하고 관리하는 프로그램이다. 로컬 시스템에서 Helm CLI를 사용하여 차트를 설치하고 관리할 수 있다. 여기서는 aws의 AWS Load Balancer Controller를 설치하기 위해 Helm Chart를 설치하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ArgoCD CLI 설치&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;wget https://github.com/argoproj/argo-cd/releases/download/v2.2.5/argocd-linux-amd64 -O argocd
chmod +x argocd
sudo mv argocd /usr/local/bin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;argocd는 Kubernetes를 위한 CD(Continuous Delivery)툴로, GitOps방식으로 관리되는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Manifest 파일의 변경사항을 감시&lt;/b&gt;하며, 현재 배포된 환경의 상태와 Git에 정의된 Manifest 상태를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;동일하게 유지&lt;/b&gt;하는 역할을 수행 한다. 쉽게 말해 Jenkins 에서 빌드한 파일들을 자동으로 배포할 수 있게 만들고, 그 과정을 GUI 방식으로 볼 수 있도록 설계된 툴이다. 현재 프로젝트 내에서는 Jenkins를 CI로, argocd를 CD툴로 사용하였다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. Repository의 manifest로 eksctl 클러스터 생성&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. EC2 인스턴스 공개키 git 계정에 등록&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo su - jenkins
ssh-keygen
cat .ssh/id_rsa.pub&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIyVYv/btrIfRz5a6r/DjhKei88oKLs2flF2dvTvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIyVYv/btrIfRz5a6r/DjhKei88oKLs2flF2dvTvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIyVYv/btrIfRz5a6r/DjhKei88oKLs2flF2dvTvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIyVYv%2FbtrIfRz5a6r%2FDjhKei88oKLs2flF2dvTvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;707&quot; height=&quot;578&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;github: Settings &amp;rarr; SSH and GPG keys &amp;rarr; New SSH Key &amp;rarr; Key에 공개키(id_rsa.pub) 내용 입력&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-2. git clone으로 manifest 가져오기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃 계정에 현재 인스턴스의 jenkins 호스트 공개키를 등록했기 때문에 ssh 방식으로 깃 클론이 가능하다. 아래와 같이 소스를 가져와 eksctl 명령어를 통해 클러스터를 생성한다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;$ sudo su - jenkins
$ git clone git@github.com:XXXXXX.git&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-3. 파일 기반 eksctl 클러스터 생성&lt;/h2&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;$ cd mini-cicd-project
$ eksctl create cluster -f &amp;lt;파일명&amp;gt; 
# 15~20 분 정도 소요됨
$ kubectl get nodes&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 eksctl 클러스터를 생성할 때 사용한 파일의 각 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Cluster 환경을 설정하는 yaml 파일에 EKS 이름, 리전, k8s 버전, AZ 를 설정한다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# project_myeks.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: project-myeks
  region: ap-northeast-2
  version: &quot;1.22&quot;

# AZ
availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;,  &quot;ap-northeast-2c&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 IAM 부분은 EKS 에서 사용할 애드온들에 대한 serviceAccounts 계정을 생성 하는 부분으로 IAM역할을 serviceAccounts 계정에 적절히 바인딩한다. eks 입장에서 AWS IAM 은 외부 인증 서버이기 때문에 OIDC 를 true 로 설정해야 한다. 자주 사용되는 애드온들의 경우 IAM역할들이 wellKnownPolicies 에 정의되어 있다. 각각의 애드온들은 alb, nlb 를 사용할 수 있게 하는 aws-load-balancer-controller, ebs 크기 변경 및 스냅샷을 찍을 수 있게 하는 ebs-csi-controller-sa, 클러스터 오토 스케일링이 가능하게 하는 cluster-autoscaler 이다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# IAM OIDC &amp;amp; Service Account
iam:
  withOIDC: true
  serviceAccounts:
    - metadata:
        name: aws-load-balancer-controller
        namespace: kube-system
      wellKnownPolicies:
        awsLoadBalancerController: true
    - metadata:
        name: ebs-csi-controller-sa
        namespace: kube-system
      wellKnownPolicies:
        ebsCSIController: true
    - metadata:
        name: cluster-autoscaler
        namespace: kube-system
      wellKnownPolicies:
        autoScaler: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 워커 노드 그룹을 설정하는 부분으로 Production 용 노드만 만들었고 가용성을 높이기 위해 오토 스케일링을 사용한다. 또한 deploy 에서 생성되는 파드들이 해당 production 노드에 스케줄링되게 하기 위해서 labels: { role: production } 을 붙여주었으며, 노드 즉 EC2 인스턴스들이 모두 private subnet 에 배치되도록 설정했다. ssh 접속을 하기 위해 미리 키를 생성하여 인스턴스에 등록했다. 위에서 설정한 IAM 애드온들을 사용할 수 있도록 애드온 정책들을 true 로 설정했다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# Managed Node Groups
managedNodeGroups:
  # On-Demand Instance
  - name: production
    instanceType: t2.medium
    minSize: 1
    desiredCapacity: 2
    labels: { role: production }
    maxSize: 3
    privateNetworking: true
    ssh:
      allow: true
      publicKeyPath: ./keypair/myssh.pub
    availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;, &quot;ap-northeast-2c&quot;]
    iam:
      withAddonPolicies:
        autoScaler: true
        albIngress: true
        cloudWatch: true
        ebs: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EKS 로 생성되는 클러스터의 경우 컨트롤 플레인을 AWS 에서 자체적으로 관리해주기 때문에 어떤 작업이 이뤄졌는지 로그를 확인하기 어렵다. 그래서 아래와 같이 CloudWatch 에서 로그를 확인할 수 있도록 cloudWatch를 설정한다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# CloudWatch Logging
cloudWatch:
  clusterLogging:
    enableTypes: [&quot;*&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 코드는 아래와 같다.&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# project_myeks.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: project-myeks
  region: ap-northeast-2
  version: &quot;1.22&quot;

# AZ
availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;,  &quot;ap-northeast-2c&quot;]

# IAM OIDC &amp;amp; Service Account
iam:
  withOIDC: true
  serviceAccounts:
    - metadata:
        name: aws-load-balancer-controller
        namespace: kube-system
      wellKnownPolicies:
        awsLoadBalancerController: true
    - metadata:
        name: ebs-csi-controller-sa
        namespace: kube-system
      wellKnownPolicies:
        ebsCSIController: true
    - metadata:
        name: cluster-autoscaler
        namespace: kube-system
      wellKnownPolicies:
        autoScaler: true

# Managed Node Groups
managedNodeGroups:
  # On-Demand Instance
  - name: production
    instanceType: t2.medium
    minSize: 1
    desiredCapacity: 2
    labels: { role: production }
    maxSize: 3
    privateNetworking: true
    ssh:
      allow: true
      publicKeyPath: ./keypair/myssh.pub
    availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;, &quot;ap-northeast-2c&quot;]
    iam:
      withAddonPolicies:
        autoScaler: true
        albIngress: true
        cloudWatch: true
        ebs: true

# CloudWatch Logging
cloudWatch:
  clusterLogging:
    enableTypes: [&quot;*&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. CI&amp;mdash;Jenkins&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저에서 인스턴스의 IP:8080 으로 접속한다. 앞서 젠킨스를 설치하고 난 뒤 sudo cat /var/lib/jenkins/secrets/initialAdminPassword 로 확인한 초기 암호를 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 설정과 권장 플러그인 설치를 끝내면 젠킨스의 메인 화면이 나타난다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-1. 기본 설정: 환경변수, 플러그인, 크레덴셜, 슬랙 알림&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) Global Tool Configuration에서 환경변수 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven을 다음과 같이 설정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Name 에 지정한 값은 앞으로 젠킨스에서 구성할 작업에서 변수로 사용된다.&lt;/li&gt;
&lt;li&gt;MAVEN_HOME의 위치: /usr/share/maven&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cixZJv/btrIdYmiqI6/uCNzVgYVRVx5OUz6L1x21k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cixZJv/btrIdYmiqI6/uCNzVgYVRVx5OUz6L1x21k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cixZJv/btrIdYmiqI6/uCNzVgYVRVx5OUz6L1x21k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcixZJv%2FbtrIdYmiqI6%2FuCNzVgYVRVx5OUz6L1x21k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;712&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;maven 세팅 화면&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) Plugin 설치&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Jenkins 관리 &amp;rarr; 플러그인 관리 &amp;rarr; 설치 가능 &amp;rarr; Search&lt;/li&gt;
&lt;li&gt;Docker, Docker pipeline, kubernetes CLI, Slack Notification&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 플러그인은 Restart가 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Credentials 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커허브, 깃허브, 슬랙에 대한 3개의 Credentials 를 설정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Jenkins 관리 &amp;rarr; Manage Credentials &amp;rarr; Domains (global) &amp;rarr; Global credentials &amp;rarr; Add Credentials&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LgVsz/btrIhiRJ3FX/QNFKKOjRWoVk1iATDwUlX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LgVsz/btrIhiRJ3FX/QNFKKOjRWoVk1iATDwUlX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LgVsz/btrIhiRJ3FX/QNFKKOjRWoVk1iATDwUlX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLgVsz%2FbtrIhiRJ3FX%2FQNFKKOjRWoVk1iATDwUlX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;987&quot; height=&quot;159&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GitHub 계정 설정하기&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kind: Username with Password&lt;/li&gt;
&lt;li&gt;Username: GitHub 계정명&lt;/li&gt;
&lt;li&gt;Password: GitHub에서 발급받은 토큰 값(Settings-Developer settings-Personal access tokens)&lt;/li&gt;
&lt;li&gt;ID: 젠킨스 내에서 해당 크레덴셜을 가리키기 위해 사용할 변수 이름을 지정한다.&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1015&quot; data-origin-height=&quot;653&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tJtqJ/btrIfSlr9mH/vYObcJGujQG0JEnII45Qik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tJtqJ/btrIfSlr9mH/vYObcJGujQG0JEnII45Qik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tJtqJ/btrIfSlr9mH/vYObcJGujQG0JEnII45Qik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtJtqJ%2FbtrIfSlr9mH%2FvYObcJGujQG0JEnII45Qik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1015&quot; height=&quot;653&quot; data-origin-width=&quot;1015&quot; data-origin-height=&quot;653&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Docker Hub Registry 계정 설정하기&lt;/b&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dockerhub: Account Settings &amp;rarr; Security &amp;rarr; New Access Token &amp;rarr; Generate&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbl2V5/btrIdE8Syn9/Ec4DWC7qUinvihbc0BneP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbl2V5/btrIdE8Syn9/Ec4DWC7qUinvihbc0BneP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbl2V5/btrIdE8Syn9/Ec4DWC7qUinvihbc0BneP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbl2V5%2FbtrIdE8Syn9%2FEc4DWC7qUinvihbc0BneP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;660&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;아래와 같은 화면에서 생성된 토큰을 복사해 젠킨스 크레덴셜에 입력한다. Kind는 깃헙과 동일하게 &amp;ldquo;Username with Password&amp;rdquo;로 선택한다.&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDPEUd/btrIeM6VhEW/WRiAcIS4c00xvmaVSp13sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDPEUd/btrIeM6VhEW/WRiAcIS4c00xvmaVSp13sK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDPEUd/btrIeM6VhEW/WRiAcIS4c00xvmaVSp13sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDPEUd%2FbtrIeM6VhEW%2FWRiAcIS4c00xvmaVSp13sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;927&quot; height=&quot;690&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Slack 통합 토큰 자격 증명 ID 설정하기&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 다음 단계인 Slack 알림 설정 과정에서 서술한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) Slack 알림을 위한 젠킨스 CI 앱 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://hello-startup.tistory.com/19&quot;&gt;https://hello-startup.tistory.com/19&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI/CD의 각 단계가 순조롭게 진행되고 있는지를 실시간으로 확인하는 방법에는 여러가지가 있으나, 본 프로젝트에서는 Slack 메시지를 받는 방법을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 메시지를 받아볼 Slack 워크스페이스를 개설하고, Jenkins 에서 알람이 올 채널을 하나 개설하고, 슬랙 앱 메뉴에서 아래처럼 Jenkins CI를 검색한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M6CAZ/btrIfSsf32V/1CIavua4kbYIsQKY9u2KsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M6CAZ/btrIfSsf32V/1CIavua4kbYIsQKY9u2KsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M6CAZ/btrIfSsf32V/1CIavua4kbYIsQKY9u2KsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM6CAZ%2FbtrIfSsf32V%2F1CIavua4kbYIsQKY9u2KsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;356&quot; height=&quot;475&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 화면에서 Jenkins CI를 추가하면 링크가 열리고 Slack에 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTVnU/btrIdevPa0W/wLPLd8k5CjkIVHXZRu1K81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTVnU/btrIdevPa0W/wLPLd8k5CjkIVHXZRu1K81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTVnU/btrIdevPa0W/wLPLd8k5CjkIVHXZRu1K81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTVnU%2FbtrIdevPa0W%2FwLPLd8k5CjkIVHXZRu1K81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;251&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림을 포스트할 채널을 고른 뒤, 통합 앱을 추가하면 설정 지침 페이지로 이동하며, 이곳에서 기타 젠킨스에서 앱을 설정하는 방법을 상세히 설명한다. 설정 지침에서 다음과 같이 팀 하위 도메인, 통합 토큰 자격 증명 ID 값이 부여되었음을 확인할 수 있다. 이 두 가지 값을 복사한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFFC5/btrIc92L2ZX/sE7rbJfvVwSrXP0qikgxt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFFC5/btrIc92L2ZX/sE7rbJfvVwSrXP0qikgxt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFFC5/btrIc92L2ZX/sE7rbJfvVwSrXP0qikgxt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFFC5%2FbtrIc92L2ZX%2FsE7rbJfvVwSrXP0qikgxt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;135&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;젠킨스로 돌아와, Jenkins 관리 &amp;rarr; 시스템 설정 맨 하단 Slack 설정 부분을 찾는다. (Slack Notification plugin이 설치되어 있지 않으면 보이지 않음) Workspace에는 팀 하위 도메인 값을 붙여넣고, Default channel 에는 Slack 에서 생성한 채널 이름을 넣는다. Credential에는 열쇠 아이콘 Add 버튼을 눌러 Jenkins Credentials Provider를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7yL5x/btrIipQDOH1/aFWEeVnHXGhqxfxLVxMQQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7yL5x/btrIipQDOH1/aFWEeVnHXGhqxfxLVxMQQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7yL5x/btrIipQDOH1/aFWEeVnHXGhqxfxLVxMQQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7yL5x%2FbtrIipQDOH1%2FaFWEeVnHXGhqxfxLVxMQQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;963&quot; height=&quot;498&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Credential 추가를 위해 아래와 같이 Kind는 토큰 ID 값만을 사용하기 때문에 Secret text를 선택하고, Secret에 앞서 Jenkins CI 도구에서 복사해둔 통합 토큰 자격 증명 ID 값을 입력한다. ID는 임의 입력해도 무방하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UpCq1/btrIdx3qtBx/sk1tSrzxiCAAz6K6jMBqgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UpCq1/btrIdx3qtBx/sk1tSrzxiCAAz6K6jMBqgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UpCq1/btrIdx3qtBx/sk1tSrzxiCAAz6K6jMBqgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUpCq1%2FbtrIdx3qtBx%2Fsk1tSrzxiCAAz6K6jMBqgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;721&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가한 크레덴셜의 ID를 드롭다운 메뉴에서 고르고 저장한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-2. 파이프라인 생성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) Jenkins Job 구성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 item &amp;rarr; Pipeline 선택 &amp;rarr; Pipeline Tab(맨 하단 Pipeline으로 이동) &amp;rarr; Definition에서 Pipeline script from SCM 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림과 같이 해당 Repository URL 에 Jenkins Credential 을 통해 인증하고 main 브랜치로 이동하여 Jenkinsfile 의 파이프라인 스크립트를 실행하도록 설정했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byvJBX/btrIazUPrvd/xqSn2rcvPt9DvQXfafRHg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byvJBX/btrIazUPrvd/xqSn2rcvPt9DvQXfafRHg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byvJBX/btrIazUPrvd/xqSn2rcvPt9DvQXfafRHg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyvJBX%2FbtrIazUPrvd%2FxqSn2rcvPt9DvQXfafRHg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1018&quot; height=&quot;642&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UwSBR/btrIiEteSM5/09WzpiKWESK40b1Q3T1mb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UwSBR/btrIiEteSM5/09WzpiKWESK40b1Q3T1mb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UwSBR/btrIiEteSM5/09WzpiKWESK40b1Q3T1mb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUwSBR%2FbtrIiEteSM5%2F09WzpiKWESK40b1Q3T1mb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1004&quot; height=&quot;695&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;695&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) Jenkinsfile의 내용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 Script Path에 설정한 바와 같이 Jenkinsfile은 GitHub repository 최상위 디렉토리에 위치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 스크립트는 pipeline { } 안에 작성되며 이하 각 부분 별로 스크립트를 설명한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tools 안에는 Global Tool Configuration 에서 설정한 Maven3 를 사용할 수 있도록 maven 이라는 명칭을 부여했다.&lt;/li&gt;
&lt;li&gt;environment 안에는 Jenkinsfile 스크립트에서 사용할 로컬 변수들을 설정했다. 도커 허브 레지스트리, 도커 허브 계정, 깃 허브 계정, 이메일, 이름 등을 변수로 설정해주었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;  // Global Tool Configuration 에서 설정한 maven 의 명칭
  tools {
    maven 'Maven3' 
  }

  // 해당 스크립트 내에서 사용할 로컬 변수들 설정
  // 레포지토리가 없으면 생성됨
  // Credential들에는 젠킨스 크레덴셜에서 설정한 ID를 사용
  environment {
    dockerHubRegistry = 'how0326/tomcat' 
    dockerHubRegistryCredential = 'docker' 
    githubCredential = 'git_hub'
    gitEmail = 'how0326@gmail.com'
    gitName = 'seongwoo-choi'
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이하는 stages { } 안에 작성되며 그 하위 요소인 stage { } 에서 단계별 작업을 지정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;steps { } 내의 스크립트 실행이 끝난 후에 post { }안의 스크립트가 실행되며, failure { } 내부의 스크립트는 steps 가 실패할 경우, success { } 는 steps 가 성공할 경우에 실행된다.&lt;/li&gt;
&lt;li&gt;우선 checkout 을 사용하여 깃허브에서 레포지토리를 클론해온다. 만약 프라이빗 레포지토리일 경우에는 environment 에서 지정한 깃허브 계정으로 클론한다.&lt;/li&gt;
&lt;li&gt;두 번째 stage 는 tools 에서 지정한 maven 도구를 사용하여 Jar 파일을 생성하는 단계이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;    // 깃허브 계정으로 레포지토리를 클론한다.
    stage('Checkout Application Git Branch') {
      steps {
        checkout([$class: 'GitSCM', branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[credentialsId: githubCredential, url: 'https://github.com/mini-cicd-project/mini-cicd-project.git']]])
      }
      // steps 가 끝날 경우 실행한다.
      // steps 가 실패할 경우에는 failure 를 실행하고 성공할 경우에는 success 를 실행한다.
      post {
        failure {
          echo 'Repository clone failure' 
        }
        success {
          echo 'Repository clone success' 
        }
      }
    }

    // Maven 을 사용하여 Jar 파일 생성    
    stage('Maven Jar Build') {
      steps {
        sh 'mvn clean install'  
      }
      post {
        failure {
          echo 'Maven war build failure' 
        }
        success {
          echo 'Maven war build success'
        }
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 stage는 도커를 사용해 이미지를 빌드하는 단계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계부터는 성공, 실패 시에 슬랙에 알람이 오도록 설정했다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;    stage('Docker Image Build') {
      steps {
        // 도커 이미지를 빌드하며 빌드한 횟수에 따라 순차적으로 증가하는 젠킨스 자체 변수를 태그로 자동 지정한다.
        sh &quot;docker build . -t ${dockerHubRegistry}:${currentBuild.number}&quot;
        sh &quot;docker build . -t ${dockerHubRegistry}:latest&quot;
      }
      // 성공, 실패 시 슬랙에 알람오도록 설정
      post {
        failure {
          echo 'Docker image build failure'
          slackSend (color: '#FF0000', message: &quot;FAILED: Docker Image Build '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
        success {
          echo 'Docker image build success'
          slackSend (color: '#0AC9FF', message: &quot;SUCCESS: Docker Image Build '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
      }
    }  &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네번째 스테이지는 도커에 이미지를 푸시하는 단계이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 부분은 젠킨스의 &amp;ldquo;Pipeline Syntax&amp;rdquo; 기능을 활용하여 도커 허브에 로그인하는 샘플 스크립트를 생성, 수정해 사용했다. 이후 도커 명령어로 도커 허브에 앞선 단계에서 생성한 이미지 두 개를 푸시한다.&lt;/li&gt;
&lt;li&gt;이미지를 푸시한 이후에 10 초 쉰 후 후속작업을 실행하도록 했으며, 푸시가 성공하든 실패하든 현재 빌드된 이미지를 삭제하도록 했다. 이는 불필요한 스토리지 사용량을 줄이며, 다음 이미지 빌드 시에 캐시 사용으로 변경사항 미적용 오류 등을 방지하기 위한 방법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;    stage('Docker Image Push') {
      steps {
        // 젠킨스에 등록한 크레덴셜로 도커 허브에 이미지 푸시
        withDockerRegistry(credentialsId: dockerHubRegistryCredential, url: '') {
          sh &quot;docker push ${dockerHubRegistry}:${currentBuild.number}&quot;
          sh &quot;docker push ${dockerHubRegistry}:latest&quot;
          // 10초 쉰 후에 다음 작업을 이어나가도록 함
          sleep 10
        } 
      }

      post {
        failure {
          echo 'Docker Image Push failure'
          sh &quot;docker rmi ${dockerHubRegistry}:${currentBuild.number}&quot;
          sh &quot;docker rmi ${dockerHubRegistry}:latest&quot;
          slackSend (color: '#FF0000', message: &quot;FAILED: Docker Image Push '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
        success {
          echo 'Docker Image Push success'
          sh &quot;docker rmi ${dockerHubRegistry}:${currentBuild.number}&quot;
          sh &quot;docker rmi ${dockerHubRegistry}:latest&quot;
          slackSend (color: '#0AC9FF', message: &quot;SUCCESS: Docker Image Push '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 스테이지는 deploy/production.yaml 의 이미지 태그를 변경하여 깃허브에 푸시하는 단계이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pipeline Syntax sample을 사용하여 깃허브에 로그인하는 스크립트를 작성하고, sed 명령어를 사용하여 deploy/production.yaml 의 이미지 태그를 변경한 뒤 메인 브랜치에 푸시하도록 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;    stage('K8S Manifest Update') {
      steps {
        // git 계정 로그인, 해당 레포지토리의 main 브랜치에서 클론
        git credentialsId: githubCredential,
            url: 'https://github.com/mini-cicd-project/mini-cicd-project.git',
            branch: 'main'  

        // 이미지 태그 변경 후 메인 브랜치에 푸시
        sh &quot;git config --global user.email ${gitEmail}&quot;
        sh &quot;git config --global user.name ${gitName}&quot;
        sh &quot;sed -i 's/tomcat:.*/tomcat:${currentBuild.number}/g' deploy/production.yaml&quot;
        sh &quot;git add .&quot;
        sh &quot;git commit -m 'fix:${dockerHubRegistry} ${currentBuild.number} image versioning'&quot;
        sh &quot;git branch -M main&quot;
        sh &quot;git remote remove origin&quot;
        sh &quot;git remote add origin git@github.com:mini-cicd-project/mini-cicd-project.git&quot;
        sh &quot;git push -u origin main&quot;
      }
      post {
        failure {
          echo 'K8S Manifest Update failure'
          slackSend (color: '#FF0000', message: &quot;FAILED: K8S Manifest Update '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
        success {
          echo 'K8s Manifest Update success'
          slackSend (color: '#0AC9FF', message: &quot;SUCCESS: K8S Manifest Update '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 코드는 아래와 같다.&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;pipeline {
  agent any
  // Global Tool Configuration 에서 설정한 Name
  tools {
    maven 'Maven3' 
  }

  // 해당 스크립트 내에서 사용할 로컬 변수들 설정
  // 레포지토리가 없으면 생성됨
  // Credential들에는 젠킨스 크레덴셜에서 설정한 ID를 사용
  environment {
    dockerHubRegistry = 'how0326/tomcat' 
    dockerHubRegistryCredential = 'docker' 
    githubCredential = 'git_hub'
    gitEmail = 'how0326@gmail.com'
    gitName = 'seongwoo-choi'
  }

  stages {

    // 깃허브 계정으로 레포지토리를 클론한다.
    stage('Checkout Application Git Branch') {
      steps {
        checkout([$class: 'GitSCM', branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[credentialsId: githubCredential, url: 'https://github.com/mini-cicd-project/mini-cicd-project.git']]])
      }
      // steps 가 끝날 경우 실행한다.
      // steps 가 실패할 경우에는 failure 를 실행하고 성공할 경우에는 success 를 실행한다.
      post {
        failure {
          echo 'Repository clone failure' 
        }
        success {
          echo 'Repository clone success' 
        }
      }
    }

    // Maven 을 사용하여 Jar 파일 생성    
    stage('Maven Jar Build') {
      steps {
        sh 'mvn clean install'  
      }
      post {
        failure {
          echo 'Maven war build failure' 
        }
        success {
          echo 'Maven war build success'
        }
      }
    }
    stage('Docker Image Build') {
      steps {
        // 도커 이미지 빌드
        sh &quot;docker build . -t ${dockerHubRegistry}:${currentBuild.number}&quot;
        sh &quot;docker build . -t ${dockerHubRegistry}:latest&quot;
      }
      // 성공, 실패 시 슬랙에 알람오도록 설정
      post {
        failure {
          echo 'Docker image build failure'
          slackSend (color: '#FF0000', message: &quot;FAILED: Docker Image Build '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
        success {
          echo 'Docker image build success'
          slackSend (color: '#0AC9FF', message: &quot;SUCCESS: Docker Image Build '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
      }
    }  

    stage('Docker Image Push') {
      steps {
        // 젠킨스에 등록한 계정으로 도커 허브에 이미지 푸시
        withDockerRegistry(credentialsId: dockerHubRegistryCredential, url: '') {
          sh &quot;docker push ${dockerHubRegistry}:${currentBuild.number}&quot;
          sh &quot;docker push ${dockerHubRegistry}:latest&quot;
          // 10초 쉰 후에 다음 작업 이어나가도록 함
          sleep 10
        } 
      }

      post {
        failure {
          echo 'Docker Image Push failure'
          sh &quot;docker rmi ${dockerHubRegistry}:${currentBuild.number}&quot;
          sh &quot;docker rmi ${dockerHubRegistry}:latest&quot;
          slackSend (color: '#FF0000', message: &quot;FAILED: Docker Image Push '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
        success {
          echo 'Docker Image Push success'
          sh &quot;docker rmi ${dockerHubRegistry}:${currentBuild.number}&quot;
          sh &quot;docker rmi ${dockerHubRegistry}:latest&quot;
          slackSend (color: '#0AC9FF', message: &quot;SUCCESS: Docker Image Push '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
      }
    }

    stage('K8S Manifest Update') {
      steps {
        // git 계정 로그인, 해당 레포지토리의 main 브랜치에서 클론
        git credentialsId: githubCredential,
            url: 'https://github.com/mini-cicd-project/mini-cicd-project.git',
            branch: 'main'  

        // 이미지 태그 변경 후 메인 브랜치에 푸시
        sh &quot;git config --global user.email ${gitEmail}&quot;
        sh &quot;git config --global user.name ${gitName}&quot;
        sh &quot;sed -i 's/tomcat:.*/tomcat:${currentBuild.number}/g' deploy/production.yaml&quot;
        sh &quot;git add .&quot;
        sh &quot;git commit -m 'fix:${dockerHubRegistry} ${currentBuild.number} image versioning'&quot;
        sh &quot;git branch -M main&quot;
        sh &quot;git remote remove origin&quot;
        sh &quot;git remote add origin git@github.com:mini-cicd-project/mini-cicd-project.git&quot;
        sh &quot;git push -u origin main&quot;
      }
      post {
        failure {
          echo 'K8S Manifest Update failure'
          slackSend (color: '#FF0000', message: &quot;FAILED: K8S Manifest Update '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
        success {
          echo 'K8s Manifest Update success'
          slackSend (color: '#0AC9FF', message: &quot;SUCCESS: K8S Manifest Update '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})&quot;)
        }
      }
    }

  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Jenkins CI-Slack Notification 실행 확인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pipeline 구성을 마치고 Save/Apply &amp;rarr; Build Now&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드가 시작되며 깃허브 레포지토리에서 Jenkinsfile 을 가져와 스크립트를 실행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ5qYm/btrIioD9bwO/WWnVYWnsKxkr1gF3hp5ek0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ5qYm/btrIioD9bwO/WWnVYWnsKxkr1gF3hp5ek0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ5qYm/btrIioD9bwO/WWnVYWnsKxkr1gF3hp5ek0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ5qYm%2FbtrIioD9bwO%2FWWnVYWnsKxkr1gF3hp5ek0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;603&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Stage View를 통해 오류 없이 Jenkinsfile의 실행이 성공한 것을 확인할 수 있다. slackSend가 설정된 각 스테이지의 실행이 성공(또는 실패)할 때마다 슬랙으로 알림이 전송된다. 아래 이미지는 도커 이미지 빌드, 푸시, 깃허브에 푸시가 성공했음을 알리는 슬랙 메시지이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VUVPU/btrIdeP7vrE/SUXJPcm3oKxllhlskTXAc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VUVPU/btrIdeP7vrE/SUXJPcm3oKxllhlskTXAc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VUVPU/btrIdeP7vrE/SUXJPcm3oKxllhlskTXAc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVUVPU%2FbtrIdeP7vrE%2FSUXJPcm3oKxllhlskTXAc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;261&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 아래와 같이 도커허브에 새로운 빌드 번호를 tag로 단 이미지가 푸시된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tCYhr/btrIeLtlEyC/GCzgbkS52CpPkugjAEmjX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tCYhr/btrIeLtlEyC/GCzgbkS52CpPkugjAEmjX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tCYhr/btrIeLtlEyC/GCzgbkS52CpPkugjAEmjX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtCYhr%2FbtrIeLtlEyC%2FGCzgbkS52CpPkugjAEmjX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;544&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. CD&amp;mdash;ArgoCD&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-1. ArgoCD 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/getting_started/&quot;&gt;https://argo-cd.readthedocs.io/en/stable/getting_started/&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) aws-load-balancer-controller 설치&lt;/h3&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;helm repo add eks https://aws.github.io/eks-charts
helm repo list
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=project-myeks --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller --set image.repository=602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/amazon/aws-load-balancer-controller&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) ArgoCD 설치 확인 및 웹 UI 접속&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 끝나면 argocd-server 서비스의 타입을 로드 밸런서로 변경한 후, 해당 로드 밸런서의 DNS 이름 주소로 접속할 수 있게 한다.&lt;/p&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;kubectl patch svc argocd-server -n argocd -p '{&quot;spec&quot;: {&quot;type&quot;: &quot;LoadBalancer&quot;}}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl -n argocd get all로 생성된 리소스들을 확인하고 service/argocd-server의 주소를 확인한다. 서버는 위에서 설정한 대로 LoadBalancer 타입이며 external-ip 에 도메인 주소가 부여된 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;jenkins@jenkins:~$ kubectl -n argocd get all
NAME                                                    READY   STATUS    RESTARTS   AGE
pod/argocd-application-controller-0                     1/1     Running   0          3h58m
pod/argocd-applicationset-controller-66689cbf4b-xpm7g   1/1     Running   0          3h58m
pod/argocd-dex-server-66fc6c99cc-nlrlx                  1/1     Running   0          3h58m
pod/argocd-notifications-controller-8f8f46bd6-lrm5d     1/1     Running   0          3h58m
pod/argocd-redis-d486999b7-bh2f5                        1/1     Running   0          3h58m
pod/argocd-repo-server-7db4cc4b45-2b2dd                 1/1     Running   0          3h58m
pod/argocd-server-79d86bbb47-64w7s                      1/1     Running   0          3h58m

NAME                                              TYPE           CLUSTER-IP       EXTERNAL-IP                                                                   PORT(S)                      AGE
service/argocd-applicationset-controller          ClusterIP      10.100.204.233   &amp;lt;none&amp;gt;                                                                        7000/TCP                     3h58m
service/argocd-dex-server                         ClusterIP      10.100.68.12     &amp;lt;none&amp;gt;                                                                        5556/TCP,5557/TCP,5558/TCP   3h58m
service/argocd-metrics                            ClusterIP      10.100.113.208   &amp;lt;none&amp;gt;                                                                        8082/TCP                     3h58m
service/argocd-notifications-controller-metrics   ClusterIP      10.100.153.105   &amp;lt;none&amp;gt;                                                                        9001/TCP                     3h58m
service/argocd-redis                              ClusterIP      10.100.110.65    &amp;lt;none&amp;gt;                                                                        6379/TCP                     3h58m
service/argocd-repo-server                        ClusterIP      10.100.161.179   &amp;lt;none&amp;gt;                                                                        8081/TCP,8084/TCP            3h58m
service/argocd-server                             LoadBalancer   10.100.0.145     ad7af453685d843d8a0a210edf174fba-610535311.ap-northeast-2.elb.amazonaws.com   80:32388/TCP,443:30461/TCP   3h58m
service/argocd-server-metrics                     ClusterIP      10.100.61.142    &amp;lt;none&amp;gt;                                                                        8083/TCP                     3h58m

NAME                                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/argocd-applicationset-controller   1/1     1            1           3h58m
deployment.apps/argocd-dex-server                  1/1     1            1           3h58m
deployment.apps/argocd-notifications-controller    1/1     1            1           3h58m
deployment.apps/argocd-redis                       1/1     1            1           3h58m
deployment.apps/argocd-repo-server                 1/1     1            1           3h58m
deployment.apps/argocd-server                      1/1     1            1           3h58m

NAME                                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/argocd-applicationset-controller-66689cbf4b   1         1         1       3h58m
replicaset.apps/argocd-dex-server-66fc6c99cc                  1         1         1       3h58m
replicaset.apps/argocd-notifications-controller-8f8f46bd6     1         1         1       3h58m
replicaset.apps/argocd-redis-d486999b7                        1         1         1       3h58m
replicaset.apps/argocd-repo-server-7db4cc4b45                 1         1         1       3h58m
replicaset.apps/argocd-server-79d86bbb47                      1         1         1       3h58m

NAME                                             READY   AGE
statefulset.apps/argocd-application-controller   1/1     3h58m                                                                  &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인한 주소로 브라우저에서 ArgoCD의 웹 UI에 접속한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/meUih/btrIi0W5SsK/GNj2s1N7PsDUgmRSmLs0q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/meUih/btrIi0W5SsK/GNj2s1N7PsDUgmRSmLs0q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/meUih/btrIi0W5SsK/GNj2s1N7PsDUgmRSmLs0q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmeUih%2FbtrIi0W5SsK%2FGNj2s1N7PsDUgmRSmLs0q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1077&quot; height=&quot;968&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;admin 계정에 대한 암호는 아르고CD의 시작문서에서 안내하는 대로 다음 명령어를 통해 구할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=&quot;{.data.password}&quot; | base64 -d; echo&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-2. Git 저장소에서 바로 애플리케이션 배포하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) Repository 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private repository 와 연동하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Repositories&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정 메뉴에서 아래 화면처럼 &amp;ldquo;Connect repo using HTTPS&amp;rdquo;를 선택한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Repository의 type은 git을 선택&lt;/li&gt;
&lt;li&gt;ArgoCD가 바라볼 git 주소를 Repository URL에 입력&lt;/li&gt;
&lt;li&gt;private repository 이므로 git hub ID와 Password(GitHub에서 발급받은 토큰 값) 입력&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ino5o/btrIiLy6SKC/qppXDT3Hs3D5lGK51hjkF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ino5o/btrIiLy6SKC/qppXDT3Hs3D5lGK51hjkF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ino5o/btrIiLy6SKC/qppXDT3Hs3D5lGK51hjkF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIno5o%2FbtrIiLy6SKC%2FqppXDT3Hs3D5lGK51hjkF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;713&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;713&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) ArgoCD Application 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 화면 가운데의 &amp;ldquo;CREATE APPLICATION&amp;rdquo;으로 아래와 같은 앱 구성 화면을 연다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;General 섹션: 생성할 Application 이름을 지정하고 Sync 정책을 설정한다. git 저장소의 변경 사항을 자동으로 운영에 반영하기 위해 Automatic으로 설정하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djWUyU/btrIiLlypQs/171IsmwmNhqYkHKz2nkQ8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djWUyU/btrIiLlypQs/171IsmwmNhqYkHKz2nkQ8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djWUyU/btrIiLlypQs/171IsmwmNhqYkHKz2nkQ8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjWUyU%2FbtrIiLlypQs%2F171IsmwmNhqYkHKz2nkQ8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1025&quot; height=&quot;614&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Source 섹션: repository URL에서 미리 등록한 git repository를 선택한 다음, 배포할 application의 위치(git repository에서의 위치)를 Path에 입력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nhEgQ/btrIftThbEH/C868lw1OcXZQWCrzytAXyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nhEgQ/btrIftThbEH/C868lw1OcXZQWCrzytAXyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nhEgQ/btrIftThbEH/C868lw1OcXZQWCrzytAXyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnhEgQ%2FbtrIftThbEH%2FC868lw1OcXZQWCrzytAXyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;532&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Destnation 섹션: kubernetes의 어느 cluster에 배포할지(아래 그림에서는 기본선택), 그리고 어떤 namespace에 배포할지를 결정한다. 마지막으로, 구성화면 최상단의 create를 눌러 앱을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sVlkI/btrIi6ptNW2/hfRVLr0DfbeXQWRMKzHMDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sVlkI/btrIi6ptNW2/hfRVLr0DfbeXQWRMKzHMDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sVlkI/btrIi6ptNW2/hfRVLr0DfbeXQWRMKzHMDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsVlkI%2FbtrIi6ptNW2%2FhfRVLr0DfbeXQWRMKzHMDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1023&quot; height=&quot;401&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 생성된 애플리케이션을 확인하는 화면이다. 구성 시에 설정한 automatic sync policy에 따라 생성 직후 &amp;ldquo;Synced&amp;rdquo; 상태임을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blLGOr/btrIejwCBDd/Xjg6mtqjz0LrtUqN5R9rkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blLGOr/btrIejwCBDd/Xjg6mtqjz0LrtUqN5R9rkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blLGOr/btrIejwCBDd/Xjg6mtqjz0LrtUqN5R9rkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblLGOr%2FbtrIejwCBDd%2FXjg6mtqjz0LrtUqN5R9rkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1099&quot; height=&quot;462&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 해당 애플리케이션의 배포 및 연결 상태가 시각적으로 표시되는 화면이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be8l91/btrIejQVMJj/QLD5CqZtsOkRZlhkcE50tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be8l91/btrIejQVMJj/QLD5CqZtsOkRZlhkcE50tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be8l91/btrIejQVMJj/QLD5CqZtsOkRZlhkcE50tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe8l91%2FbtrIejQVMJj%2FQLD5CqZtsOkRZlhkcE50tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;521&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포된 App에 접속하려면 위 화면에서 myweb-ing(ALB)의 링크 셰어 아이콘 :을 누르거나, App Details에서 URLs를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 배포된 앱에 접속했을 때 볼 수 있는 화면이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1255&quot; data-origin-height=&quot;639&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJ2fX/btrIdDB48sf/EGcjMQ08gEWs2ig19vwtvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJ2fX/btrIdDB48sf/EGcjMQ08gEWs2ig19vwtvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJ2fX/btrIdDB48sf/EGcjMQ08gEWs2ig19vwtvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJ2fX%2FbtrIdDB48sf%2FEGcjMQ08gEWs2ig19vwtvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1255&quot; height=&quot;639&quot; data-origin-width=&quot;1255&quot; data-origin-height=&quot;639&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) ArgoCD CLI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 ArgoCD CLI에서는 배포된 앱의 정보를 다음과 같이 확인할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아르고 CD 서버에 로그인하기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;$ argocd login --insecure ac...32-882088216.ap-northeast-2.elb.amazonaws.com:80
Username: admin
Password:
'admin:login' logged in successfully
Context 'ac...16.ap-northeast-2.elb.amazonaws.com:80' updated&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱 정보 보기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;$ argocd app get mini-project-cicd
Name:               mini-project-cicd
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://ac...16.ap-northeast-2.elb.amazonaws.com:80/applications/mini-project-cicd
Repo:               https://github.com/xxxx/xxxx.git
Target:             HEAD
Path:               deploy/
SyncWindow:         Sync Allowed
Sync Policy:        Automated
Sync Status:        Synced to HEAD (ee6b1a4)
Health Status:      Healthy

GROUP              KIND        NAMESPACE  NAME               STATUS  HEALTH   HOOK  MESSAGE
                   Service     default    production-svc-lb  Synced  Healthy        service/production-svc-lb unchanged
apps               Deployment  default    production-deploy  Synced  Healthy        deployment.apps/production-deploy configured
networking.k8s.io  Ingress     default    myweb-ing          Synced  Healthy        ingress.networking.k8s.io/myweb-ing unchanged&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sync Policy가 Automated 가 아닌 경우, 싱크를 실행하는 명령어는 다음과 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;argocd app sync &amp;lt;application&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl로도 애플리케이션 Ingress 및 내부 Load Balancer의 DNS 주소를 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;jenkins@jenkins:~$ kubectl get ing,svc -n default
NAME                                  CLASS    HOSTS   ADDRESSPORTS                                                                     AGE
ingress.networking.k8s.io/myweb-ing   &amp;lt;none&amp;gt;   *       k8s-default-mywebing-c51c82d215-597039061.ap-northeast-2.elb.amazonaws.com:80    42m

NAME                        TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/kubernetes          ClusterIP      10.100.0.1      &amp;lt;none&amp;gt;        443/TCP        13h
service/production-svc-lb   LoadBalancer   10.100.165.79   k8s-default-producti-7b54e179b8-1cecdb688046eb5a.elb.ap-northeast-2.amazonaws.com  80:30458/TCP   42m&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. CI/CD 연동 확인&amp;mdash;Jenkins &amp;amp; ArgoCD&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins와 ArgoCD 동기화 확인을 위해 jenkins에서 build를 실행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb0ldY/btrIeMy3Uya/lPr3qwy8m6hL0ATRNtX451/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb0ldY/btrIeMy3Uya/lPr3qwy8m6hL0ATRNtX451/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb0ldY/btrIeMy3Uya/lPr3qwy8m6hL0ATRNtX451/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb0ldY%2FbtrIeMy3Uya%2FlPr3qwy8m6hL0ATRNtX451%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;650&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;449&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uYbxL/btrH7RagXZO/v5d8dJ7cqd2OYs4AkRpCfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uYbxL/btrH7RagXZO/v5d8dJ7cqd2OYs4AkRpCfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uYbxL/btrH7RagXZO/v5d8dJ7cqd2OYs4AkRpCfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuYbxL%2FbtrH7RagXZO%2Fv5d8dJ7cqd2OYs4AkRpCfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;915&quot; height=&quot;449&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;449&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 build가 완료되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci5myF/btrIiEfGfPn/dqhnEkoXjzB0JW793yfSm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci5myF/btrIiEfGfPn/dqhnEkoXjzB0JW793yfSm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci5myF/btrIiEfGfPn/dqhnEkoXjzB0JW793yfSm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci5myF%2FbtrIiEfGfPn%2FdqhnEkoXjzB0JW793yfSm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;582&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD에서 Refresh를 해주거나 약간 기다리면 변경 사항을 반영하여 자동으로 Synchronized가 진행되는 것을 볼 수 있다. 아래는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Sync Status&lt;/b&gt;를 통해 Jenkins로 인한 변동 사항이 ArgoCD에 정상적으로 동기화되었음을 확인하는 화면이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w0M6c/btrIhiRJ7h4/QyvH5IPBGEhALUKeqoV4j1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w0M6c/btrIhiRJ7h4/QyvH5IPBGEhALUKeqoV4j1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w0M6c/btrIhiRJ7h4/QyvH5IPBGEhALUKeqoV4j1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw0M6c%2FbtrIhiRJ7h4%2FQyvH5IPBGEhALUKeqoV4j1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;834&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;History and Rollback 을 통해서 아래치럼 배포 이력을 확인하고, 우측 상단의 버튼을 통해 Re-deploy 또는 Rollback 시킬 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m73SL/btrIhkaYq41/HpWxTok9cukuG3Lb1rh8b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m73SL/btrIhkaYq41/HpWxTok9cukuG3Lb1rh8b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m73SL/btrIhkaYq41/HpWxTok9cukuG3Lb1rh8b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm73SL%2FbtrIhkaYq41%2FHpWxTok9cukuG3Lb1rh8b1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;654&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sync Status를 통해 Jenkins로 인한 변동 사항이 ArgoCD에 정상적으로 동기화되었음을 확인할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. 추가 구성&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-1. metrics-server&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/metrics-server.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/metrics-server.html&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-2. Cluster Autoscaler&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 수동 스케일링&lt;/h3&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;eksctl scale nodegroup --name myeks-ng1 --cluster project-myeks --help&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;eksctl scale nodegroup --name myeks-ng1 --cluster project-myeks --nodes 2&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 자동 스케일링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/autoscaling.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/autoscaling.html&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl -o cluster-autoscaler-autodiscover.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YAML 파일을 수정하고 **을 클러스터 이름으로 바꾼다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;                        - --expander=least-waste
            - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/project-myeks
          volumeMounts:
            - name: ssl-certs&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;kubectl patch deployment cluster-autoscaler \
  -n kube-system \
  -p '{&quot;spec&quot;:{&quot;template&quot;:{&quot;metadata&quot;:{&quot;annotations&quot;:{&quot;cluster-autoscaler.kubernetes.io/safe-to-evict&quot;: &quot;false&quot;}}}}}'&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;kubectl -n kube-system edit deployment.apps/cluster-autoscaler

48         - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/***project-myeks***
49         - ***--balance-similar-node-groups***
50         - ***--skip-nodes-with-system-pods=false***&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 노드의 kubernetes 버전과 image 버전을 맞춰야 한다. 현재 kubernetes 버전은 1.22.6 이지만 오토스케일러 이미지는 1.22.2 가 제일 최신 버전이기 때문에 1.22.2 로 설정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl get nodes
NAME                                                 STATUS   ROLES    AGE   VERSION
ip-192-168-117-21.ap-northeast-2.compute.internal    Ready    &amp;lt;none&amp;gt;   50m   v1.22.6-eks-7d68063
ip-192-168-149-72.ap-northeast-2.compute.internal    Ready    &amp;lt;none&amp;gt;   50m   v1.22.6-eks-7d68063
ip-192-168-180-249.ap-northeast-2.compute.internal   Ready    &amp;lt;none&amp;gt;   50m   v1.22.6-eks-7d68063&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl set image deployment cluster-autoscaler \
  -n kube-system \
  cluster-autoscaler=k8s.gcr.io/autoscaling/cluster-autoscaler:v1.22.2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령어로 Cluster Autoscaler 로그를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;kubectl -n kube-system logs -f deployment.apps/cluster-autoscaler&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-3. CloudWatch Container Insight&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-EKS-quickstart.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-EKS-quickstart.html&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;ClusterName='project-myeks'
LogRegion='ap-northeast-2'
FluentBitHttpPort='2020'
FluentBitReadFromHead='Off'
[[ ${FluentBitReadFromHead} = 'On' ]] &amp;amp;&amp;amp; FluentBitReadFromTail='Off'|| FluentBitReadFromTail='On'
[[ -z ${FluentBitHttpPort} ]] &amp;amp;&amp;amp; FluentBitHttpServer='Off' || FluentBitHttpServer='On'
curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/quickstart/cwagent-fluent-bit-quickstart.yaml | sed 's/{{cluster_name}}/'${ClusterName}'/;s/{{region_name}}/'${LogRegion}'/;s/{{http_server_toggle}}/&quot;'${FluentBitHttpServer}'&quot;/;s/{{http_server_port}}/&quot;'${FluentBitHttpPort}'&quot;/;s/{{read_from_head}}/&quot;'${FluentBitReadFromHead}'&quot;/;s/{{read_from_tail}}/&quot;'${FluentBitReadFromTail}'&quot;/' | kubectl apply -f -&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우는 쉘 스크립트를 실행하지 못해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://git-scm.com/downloads&quot;&gt;git bash&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 다운받아서 실행해야 한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;❯ kubectl get po -A
NAMESPACE           NAME                                            READY   STATUS    RESTARTS   AGE
amazon-cloudwatch   cloudwatch-agent-8khfv                          1/1     Running   0          74s
amazon-cloudwatch   cloudwatch-agent-l9w7j                          1/1     Running   0          74s
amazon-cloudwatch   fluent-bit-lrzx2                                1/1     Running   0          74s
amazon-cloudwatch   fluent-bit-vlc9x                                1/1     Running   0          74s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fluent-bit 가 로그를 수집하고 cloudwatch-agent 에게 로그를 보내주면 cloudwatch 에 로그가 쌓인다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl get nodes --show-labels
NAME                                                 STATUS   ROLES    AGE   VERSION               LABELS
ip-192-168-149-72.ap-northeast-2.compute.internal    Ready    &amp;lt;none&amp;gt;   77m   v1.22.6-eks-7d68063   alpha.eksctl.io/cluster-name=project-myeks,alpha.eksctl.io/nodegroup-name=myeks-ng1,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t3.medium,beta.kubernetes.io/os=linux,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup-image=ami-06512ccb913a9d11d,eks.amazonaws.com/nodegroup=myeks-ng1,eks.amazonaws.com/sourceLaunchTemplateId=lt-018ac7a7f7853aa04,eks.amazonaws.com/sourceLaunchTemplateVersion=1,failure-domain.beta.kubernetes.io/region=ap-northeast-2,failure-domain.beta.kubernetes.io/zone=ap-northeast-2b,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-192-168-149-72.ap-northeast-2.compute.internal,kubernetes.io/os=linux,node.kubernetes.io/instance-type=t3.medium,topology.ebs.csi.aws.com/zone=ap-northeast-2b,topology.kubernetes.io/region=ap-northeast-2,topology.kubernetes.io/zone=ap-northeast-2b&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;alpha.eksctl.io/cluster-name=project-myeks
alpha.eksctl.io/nodegroup-name=myeks-ng1
beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t3.medium,beta.kubernetes.io/os=linux
eks.amazonaws.com/capacityType=ON_DEMAND
eks.amazonaws.com/nodegroup-image=ami-06512ccb913a9d11d
eks.amazonaws.com/nodegroup=myeks-ng1
eks.amazonaws.com/sourceLaunchTemplateId=lt-018ac7a7f7853aa04
eks.amazonaws.com/sourceLaunchTemplateVersion=1
failure-domain.beta.kubernetes.io/region=ap-northeast-2
failure-domain.beta.kubernetes.io/zone=ap-northeast-2b
kubernetes.io/arch=amd64
kubernetes.io/hostname=ip-192-168-149-72.ap-northeast-2.compute.internal
kubernetes.io/os=linux,node.kubernetes.io/instance-type=t3.medium
topology.ebs.csi.aws.com/zone=ap-northeast-2b
topology.kubernetes.io/region=ap-northeast-2
topology.kubernetes.io/zone=ap-northeast-2b&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Troubleshooting&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테라폼으로 VPC를 직접 생성하고 클러스터 생성에 활용 시 오류 발생&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: 테라폼으로 VPC를 생성한 후 해당 VPC와 서브넷 주소를 할당해서 클러스터를 생성했을 때, LoadBalancer 가 VPC 에 할당되지 않았다.&lt;/li&gt;
&lt;li&gt;해결: eksctl 로 VPC 가 자동으로 생성되도록 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jenkins Helm chart 생성시 오류 발생&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: 헬름 차트로 젠킨스를 생성했을 시, ALB, NLB 가 제대로 연결이 되지 않는다.&lt;/li&gt;
&lt;li&gt;추정 이유: LB가 타겟 그룹에 제대로 연결되지 않아 생기는 것으로 짐작된다.&lt;/li&gt;
&lt;li&gt;해결: ALB를 하나만 생성했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도커의 사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: EKS 클러스터에서 도커를 사용할 수 없기 때문에 Jenkins 에서 도커를 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;해결: 마스터 슬레이브 구조로 구축하는 것이 최선이나 시간적인 여유가 없어 EC2 인스턴스에 젠킨스를 설치하는 방식으로 우회하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS Load Balancer Controller 설치 오류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: AWS Load Balancer Controller 설치를 시도했을 때 다음과 같은 메시지가 출력되며 설치가 진행되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Error: INSTALLATION FAILED: Kubernetes cluster unreachable: exec plugin: invalid apiVersion &quot;client.authentication.k8s.io/v1alpha1&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해결: 해결하지 못함. EC2를 새로 만들고 처음부터 재설치 진행.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EKS 클러스터의 완전 삭제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: 실습을 반복하면서 EKSCTL 로 깨끗하게 지워지지 않은 클러스터가 남아 새 클러스터가 정상적으로 생성되지 않는다.&lt;/li&gt;
&lt;li&gt;해결: AWS 웹콘솔의 CloudFormation에서 직접 스택을 삭제하고 연결된 리소스를 직접 정리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/260</guid>
      <comments>https://ssunw.tistory.com/entry/CICD-EKS-%EC%97%90%EC%84%9C-Jenkins-ArgoCD-%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%84%B1-1#entry260comment</comments>
      <pubDate>Thu, 28 Jul 2022 14:35:36 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] PV, PVC</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-PV-PVC</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;퍼시스턴트 볼륨(PV)와 퍼시스턴트 볼륨 클레임(PVC)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스처럼 파드 내부에서 특정 데이터를 보유해야 하는 상태가 있는(stateful) 애플리케이션의 경우 데이터를 어떻게 관리해야 할까 고민해봐야 한다. 예를 들어, MySQL 디플로이먼트를 통해 파드를 생성하더라도 MySQL 파드 내부에 저장된 데이터는 영속적이지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트를 삭제하면 파드도 함께 삭제되고 그와 동시에 파드의 데이터 또한 함께 삭제되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해서 파드의 데이터를 영속적으로 저장하기 위한 방법이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 워커 노드 중 하나를 선택해 파드를 할당하는데, 특정 노드에서만 데이터를 보관해 저장하면 파드가 다른 노드로 옮겨갔을 때 해당 데이터를 사용할 수 없게 되기 이를 해결하기 위한 일반적인 방법은 어느 노드에서도 접근해 사용할 수 있는 퍼시스턴트 볼륨을 사용하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PV는 워커 노드들이 네트워크 상에서 스토리지를 마운트해 영속적으로 데이터를 저장할 수 있는 볼륨을 의미한다. 따라서 파드에 장애가 생겨 다른 노드로 옮겨가더라도 해당 노드에서 PV에 네트워크로 연결해 데이터를 계속해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크로 연결해 사용할 수 있는 PV의 대표적인 예로는 NFS, AWS의 EBS, Ceph, ClusterFS 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 PV를 사용하기 위한 기능을 자체적으로 제공하고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로컬 볼륨: hostPath, emptyDir&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hostPath는 호스트와 볼륨을 공유하기 위해서 사용하고, emptyDir은 파드의 컨테이너 간에 볼륨을 공유하기 위해서 사용한다. 이 두가지는 자주 사용되는 볼륨 종류는 아니긴 하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;워커 노드의 로컬 디렉토리를 볼륨으로 사용: hostPath&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드의 데이터를 보존할 수 있는 가장 간단한 방법은 호스트의 디렉토리를 파드와 공유해 데이터를 저장하는 방법이다. docker 의 -v 옵션이라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CL7ge/btrIkoMnVx6/CoZRMJysT3aY5m8k43fUa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CL7ge/btrIkoMnVx6/CoZRMJysT3aY5m8k43fUa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CL7ge/btrIkoMnVx6/CoZRMJysT3aY5m8k43fUa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCL7ge%2FbtrIkoMnVx6%2FCoZRMJysT3aY5m8k43fUa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;407&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volumes 항목에 볼륨을 정의한 뒤, 이를 파드를 정의하는 containers 항목에 참조해 사용한다. 위 예시에서는 볼륨에서 hostPath 항목을 정의함으로써 호스트의 /tmp 디렉토리를 파드의 /etc/data 에 마운트했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드를 생성한 뒤 파드의 컨테이너 내부로 들어가 /etc/data 디렉토리에 파일을 생성하면 호스트의 /tmp 디렉토리에 파일이 저장된다. 파드 컨테이너의 /etc/data와 호스트의 /tmp는 동일한 디렉토리로써 사용된다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;$ kubectl exec -it hostpath-pod touch /etc/data/mydata
$ ls /tmp/mydata
/tmp/mydata
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식의 데이터 보존은 바람직하지 않다. 디플로이먼트의 파드에 장애가 생겨 다른 노드로 파드가 옮겨 갔을 경우 이전 노드에 저장된 데이터를 사용할 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hostPath 볼륨을 반드시 사용해야 한다면 노드 스케줄링 기능을 사용하여 특정 노드에만 파드를 배치하는 방법을 생각해봐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hostPath 볼륨은 모든 노드에 배치해야 하는 특수한 파드의 경우에 유용하게 사용할 수 있다. 만약, CAdvisior와 같은 모니터링 툴을 쿠버네티스의 모든 워커 노드에 배포해야 한다면 hostPath 를 사용하는 것이 옳은 선택이다. 이런 경우를 제외하고서는 hostPath 볼륨을 사용하는 것은 보안 및 활용성 측면에서 바람직한 방법이 아니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파드 내의 컨테이너 간 임시 데이터 공유: emptyDir&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emptyDir 볼륨은 파드의 데이터를 영속적으로 보존하기 위해 외부 볼륨을 사용하는 것이 아닌, 파드가 실행되는 도중에만 필요한 휘발성 데이터를 각 컨테이너가 함께 사용할 수 있도록 임시 저장 공간을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emptyDir 이라는 이름이 의미하는 것처럼 emptyDir 디렉토리는 비어있는 상태로 생성되며 파드가 삭제되면 emptyDir에 저장돼 있던 데이터도 함께 삭제된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brf5p5/btrIn0wJcEF/l0HiKe1WdIcMSXEsxyXHxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brf5p5/btrIn0wJcEF/l0HiKe1WdIcMSXEsxyXHxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brf5p5/btrIn0wJcEF/l0HiKe1WdIcMSXEsxyXHxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrf5p5%2FbtrIn0wJcEF%2Fl0HiKe1WdIcMSXEsxyXHxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;797&quot; height=&quot;540&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emptyDir 은 한 컨테이너가 파일을 관리하고 한 컨테이너가 그 파일을 사용하는 경우 유용하게 사용할 수 있다. content-creator 컨테이너 내부로 들어가 /data 디렉토리에 웹 컨텐츠를 생성하면 아파치 웹 서버 컨테이너의 htdocs 디렉토리에도 동일하게 웹 컨텐츠 파일이 생성될 것이고, 웹 서버에 의해 외부로 제공된다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;$ kubectl exec -it emptyDir-pod -c content-creator sh
/ # echo Hello, World! &amp;gt;&amp;gt; /data/test.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방법외에도 깃허브 소스코드를 pull 받아와서 emptyDir을 통해 애플리케이션 컨테이너에 공유해주는 사이드카 컨테이너를 생각할 수도 있고 설정 파일을 동적으로 갱신하는 컨테이너를 파드에 포함시킬 수도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 볼륨&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에서는 별도의 플러그인을 설치하지 않아도 다양한 종류의 네트워크 볼륨을 파드에 마운트할 수 있다. 온프레미스 환경에서도 구축할 수 있는 NFS, iSCSI, GlusterFS, Ceph 와 같은 네트워크 볼륨뿐만 아니라 AWS의 EBS, GCP의 gcePersistentDisk 와 같은 클라우드 플랫폼의 볼륨을 마운트할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 가능한 모든 스토리지 종류는 쿠버네티스 공식 문서에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 볼륨의 위치는 특별하게 정해진 것은 없고, 네트워크로 접근할 수 만 있으면 쿠버네티스 클러스터 내부, 외부 어느 곳에 존재해도 상관없다. 단, AWS의 EBS와 같은 클라우드에 종속적인 볼륨을 사용하려면 AWS에서 쿠버네티스 클러스터를 생성할 때 특정 클라우드를 위한 옵션이 별도로 설정돼 있어야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NFS를 네트워크 볼륨으로 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 클라이언트가 동시에 마운트해서 사용할 수 있지만 여러 개의 스토리지를 클러스터링하는 다른 솔루션에 비해 안정성이 떨어진다. 하지만 하나의 서버만으로 간편하게 사용할 수 있으며, NFS를 마치 로컬 스토리지처럼 사용할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NFS 서버를 사용하려면 NFS 서버와 NFS 클라이언트가 각각 필요하다. NFS 서버는 영속적인 데이터가 실제로 저장되는 네트워크 스토리지 서버이고, NFS 클라이언트는 NFS 서버에 마운트해 스토리지에 파일을 읽고 쓰는 역할이다. NFS 클라이언트는 워커 노드의 기능을 사용할 것이기 때문에 따로 준비할 필요는 없고 NFS 서버만 별도로 구축하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oYWPB/btrIi0kw1WY/hCJi9Bj006JKDxHgdxAlkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oYWPB/btrIi0kw1WY/hCJi9Bj006JKDxHgdxAlkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oYWPB/btrIi0kw1WY/hCJi9Bj006JKDxHgdxAlkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoYWPB%2FbtrIi0kw1WY%2FhCJi9Bj006JKDxHgdxAlkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;805&quot; height=&quot;646&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coMC07/btrIjJpi167/TbueIECaPOli97vccdk4Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coMC07/btrIjJpi167/TbueIECaPOli97vccdk4Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coMC07/btrIjJpi167/TbueIECaPOli97vccdk4Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoMC07%2FbtrIjJpi167%2FTbueIECaPOli97vccdk4Z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;382&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NFS 서버를 위한 디플로이먼트와 서비스를 생성했고, NFS 서버의 볼륨을 파드에서 마운트해 데이터를 영속적으로 저장시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NFS 서버를 컨테이너에서 마운트하는 파드를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vaUdp/btrIjKn71IP/cx8HKOfJ290w7RvKf7OpPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vaUdp/btrIjKn71IP/cx8HKOfJ290w7RvKf7OpPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vaUdp/btrIjKn71IP/cx8HKOfJ290w7RvKf7OpPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvaUdp%2FbtrIjKn71IP%2Fcx8HKOfJ290w7RvKf7OpPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;451&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mountPath를 /mnt로 설정했기 때문에 NFS 서버의 네트워크 볼륨은 파드 컨테이너의 /mnt 디렉토리에 마운트 될 것이다. 즉, 컨테이너 내부에서 /mnt 디렉토리에 파일을 저장하면 NFS 서버에 데이터가 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volumes 항목에서 nfs 라는 항목을 정의함으로써 NFS 서버의 볼륨을 사용한다고 명시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 예제에서 유의해야 할 부분은 server 항목이 nfs-service라는 서비스의 DNS 이름이 아닌 {NFS_SERVICE_IP}로 설정돼 있다는 것이다. NFS 볼륨의 마운트는 컨테이너 내부가 아닌 워커 노드에서 발생하므로 서비스의 DNS 이름으로 NFS 서버에 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드에서는 파드의 IP로 통신은 할 수 있지만 쿠버네티스의 DNS를 사용하도록 설정돼 있지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 예외적으로 NFS 서비스의 Cluster IP 를 직접 얻은 뒤, YAML 파일에서 사용하는 방식으로 파드를 생성한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ export NFS_CLUSTER_IP=$(kubectl get svc nfs-service -o jsonpath='{.spec.clusterIP}')

$ cat nfs-pod.yaml | sed &quot;s/{NFS_SERVICE_IP}/$NFS_CLUSTER_IP/g&quot; | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl get - o jsonpath 명령어를 사용하여 리소스의 특정 정보만 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NFS 서버에 마운트하려면 워커 노드에서 별도의 NFS 패키지를 설치해야 할 수도 있다. 만약 파드가 ContainerCreating 상태에서 더 이상 진행되지 않는다면 kubectl describe pod 명령어로 문제를 확인하고 파드가 할당된 워커 노드에서 $ apt-get install nfs-common 패키지를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 운영환경에서 NFS 서버를 도입하려면 백업 스토리지를 별도로 구축해 NFS의 데이터 손실에 대비하거나 NFS 서버의 설정 튜닝 및 NFS 서버에 접근하기 위한 DNS 이름을 준비해야 할 수도 있다. 이런 설정들은 환경에 맞게 적절히 구성해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PV, PVC를 이용한 볼륨 관리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PV와 PVC를 사용하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC 는 PV의 세부적인 사항을 몰라도 볼륨을 사용할 수 있도록 추상화해주는 역할을 담당한다. 즉, 파드를 생성하는 YAML 입장에서는 네트워크 볼륨이 NFS인지, AWS의 EBS인지 상관없이 볼륨을 사용할 수 있도록 하는 것이 PVC의 핵심 아이디어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PV와 PVC를 사용하는 흐름을 간단하게 나타내면 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 종류의 네트워크 볼륨을 이용해 PV를 미리 생성한다.&lt;/li&gt;
&lt;li&gt;필요한 볼륨 크기, 볼륨의 속성을 명시한 PVC 를 생성한다.&lt;/li&gt;
&lt;li&gt;PVC 요청에 부합하는 PV를 바인드해 컨테이너에 마운트한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정에서 중요한 점은 &amp;lsquo;사용자는 디플로이먼트의 YAML 파일에 볼륨의 상세한 스펙을 정의하지 않아도 된다는 것이다.&amp;rsquo; 사용자는 YAML 파일에서 이 디플로이먼트는 볼륨을 마운트할 수 있어야 한다는 의미의 퍼시스턴트 볼륨 클레임을 명시할 뿐이고, 실제로 마운트되는 볼륨이 무엇인지 알 필요가 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIBIL/btrIiZFWEbr/d011NKnzoKJE6HrxJ7k18K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIBIL/btrIiZFWEbr/d011NKnzoKJE6HrxJ7k18K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIBIL/btrIiZFWEbr/d011NKnzoKJE6HrxJ7k18K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIBIL%2FbtrIiZFWEbr%2Fd011NKnzoKJE6HrxJ7k18K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;366&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS에서 PV, PVC 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 상에서 kops 나 EKS 로 쿠버네티스를 설치했다면 PV를 EBS와 연동해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서 EBS를 쿠버네티스의 PV로 등록하려면 가장 먼저 EBS를 생성해야 한다. AWS 웹사이트 관리 콘솔에서 EBS를 생성해도 상관없지만 여기서는 aws-cli 명령어를 사용해 EBS를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--size 옵션에서는 볼륨의 크기를 기가바이트(GB) 단위로 입력한다. 다음 명령어는 5기가의 EBS 볼륨을 생성하는 예이다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;$ export VOLUME_ID=$(aws ec2 create-volume --size 5
--region ap-northeast-2
--availability-zone ap-northeast-2a
--volum-type gp2
--tag-specifications
'ResourceType=volume, Tags=[{Key=KubernetesCluster,Value=mycluster.k8s.local}]'
| jq '.VolumeId' -r)

$ echo $VOLUME_ID
vol-xxxx...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS의 볼륨 ID를 $VOLUME_ID라는 셸 변수에 저장하고 이 ID를 통해 PV를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS의 가용 영역과 리전은 반드시 쿠버네티스 워커 노드와 동일한 곳에 있어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/psOXk/btrIn1blGqu/XjKU6JdbwkNcu1XQgQ3j31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/psOXk/btrIn1blGqu/XjKU6JdbwkNcu1XQgQ3j31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/psOXk/btrIn1blGqu/XjKU6JdbwkNcu1XQgQ3j31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpsOXk%2FbtrIn1blGqu%2FXjKU6JdbwkNcu1XQgQ3j31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;808&quot; height=&quot;340&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS의 EBS를 마운트하기 위해 awsElasticBlockStore라는 항목을 정의했다. 볼륨의 타입에 따라 하위 항목에 정의하는 옵션이 달라질 수 있으며, EBS의 볼륨 ID를 입력해야 한다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;$ cat ebs-pv.yaml | sed &quot;s/&amp;lt;VOLUME_ID&amp;gt;/$VOLUME_ID/g&quot; | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC 를 생성하지 않았기 때문에 STATUS 항목이 Avaliable 로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션을 배포하려는 사용자 입장에서 PV, PVC 파드를 함께 생성한다. PVC 오브젝트를 먼저 생성한 뒤 이를 파드의 volumes 항목에서 사용함으로써 파드 내부에 EBS 볼륨을 마운트한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEAwZh/btrIq6wqUuj/KV1Eo985S5TazWT6iPfjl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEAwZh/btrIq6wqUuj/KV1Eo985S5TazWT6iPfjl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEAwZh/btrIq6wqUuj/KV1Eo985S5TazWT6iPfjl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEAwZh%2FbtrIq6wqUuj%2FKV1Eo985S5TazWT6iPfjl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;710&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC 의 요구 사항과 일치하는 PV 가 존재하지 않는다면 파드는 계속해서 Pending 상태로 남아있는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PV와 PVC 상태가 bound로 설정됐다면 두 리소스가 성공적으로 연결된 것이다. 파드가 정상적으로 생성되어 Running 상태라면 EBS 볼륨 또한 파드 내부에 정상적으로 마운트됐다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PV는 네임 스페이스에 속하지 않는 오브젝트이지만, PVC는 네임스페이스에 속하는 오브젝트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 다시 순차적으로 나타내면 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파드의 데이터를 영속적으로 저장하기 위해 AWS EBS 볼륨을 생성한다. awscli 나 콘솔에서 직접 생성한 뒤 볼륨의 ID를 기록한다.&lt;/li&gt;
&lt;li&gt;EBS 볼륨을 쿠버네티스 PV로 등록한다. 이 때 YAML 파일에 awsElasticBlockStore 항목에 EBS 볼륨 ID를 명시한다. 또한 볼륨의 읽기 및 쓰기 속성, 볼륨의 크기를 별도로 설정한다.&lt;/li&gt;
&lt;li&gt;PVC 생성하는 YAML 파일에 원하는 볼륨의 조건을 나열한다.&lt;/li&gt;
&lt;li&gt;PV 속성이 PVC의 요구 조건과 일치할 경우 리소스가 연결된다. kubectl get pv,pvc 출력 결과를 확인하면 리소스의 상태가 Bound 즉, 연결 상태로 바뀌는 것을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;파드는 PVC 를 사용하도록 설정돼 있고 최종적으로 EBS 볼륨이 컨테이너 내부에 마운트된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PV 를 선택하기 위한 조건 명시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC 를 사용하면 볼륨이 어떤 스펙을 가졌는지 알 필요는 없지만 사용하려는 볼륨이 애플리케이션에 필요한 최소한의 조건을 맞출 필요는 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨의 크기가 적어도 얼마나 돼야 하는지, 여러 개의 파드에 마운트 될 수 있는지, 읽기 전용으로만 사용할 수 있는지 등이 조건에 해당할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AccessMode나 볼륨의 크기 등이 바로 이런 조건에 해당한다. PV와 PVC의 accessMode 및 볼륨 크기 속성이 부합해야만 쿠버네티스는 두 리소스를 매칭해 바인드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NFS 는 여러 개의 인스턴스에 의해 마운트가 가능하지만(1:N 마운트), AWS의 EBS는 하나의 인스턴스에 의해서만 마운트될 수 있다(1:1 마운트). 또한, NFS 서버의 저장 공간 크기는 일반적으로 호스트의 스토리지 크기와 동일하지만, AWS의 EBS는 생성 당시에 설정했던 크기만큼만 데이터를 저장할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AccessModes와 볼륨 크기, 스토리지 클래스, 라벨 셀렉터를 이용한 PV 선택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accessModes는 PV와 PVC를 생성할 때 설정할 수 있는 속성으로 볼륨에 대해 읽기 및 쓰기 작업이 가능하지, 여러 개의 인스턴스에 의해 마운트 될 수 있는지 등을 의미한다. 사용 가능한 accessModes 의 종류는 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ReadWriteOnce: 1:1 마운트만 가능, 읽기 쓰기 가능, RWO 로 출력&lt;/li&gt;
&lt;li&gt;ReadOnlyMany: 1:N 마운트 가능, 읽기 전용, ROX 로 출력&lt;/li&gt;
&lt;li&gt;ReadWriteMany: 1:N 마운트 가능, 읽기 쓰기 가능, RWX 로 출력&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accesModes 나 볼륨의 크기는 해당 볼륨의 메타데이터일 뿐, 볼륨이 정말로 그러한 속성을 가지도록 강제하지는 않는다. 예를 들어 AWS의 EBS를 통해 PV를 생성하더라도 accessModes를 ReadWriteMany로 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Local, hostPath 등 로컬 볼륨을 PV로 사용할 수 있는데, 그러한 경우 YAML 파일의 capacity.storage 항목에 크기를 지정한다고 해서 해당 크기의 새 디스크 파티션이 생성되는 것도 아니다. 따라서 이런 설정들은 애플리케이션을 배포할 때 적절한 볼륨을 찾아주는 라벨과 같은 역할을 한다고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도 스토리지 클래스나 라벨 셀렉터를 이용해 PV의 선택을 좀 더 세분화할 수 있다. 스토리지 클래스는 볼륨의 대표 속성 등을 나타내는 것으로, PV를 생성할 때 클래스를 설정하면 해당 클래스를 요청하는 PVC와 연결해 바인드한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRTSZZ/btrIi0kw8HE/yKFOwjaqq7Crb9nHqAaCoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRTSZZ/btrIi0kw8HE/yKFOwjaqq7Crb9nHqAaCoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRTSZZ/btrIi0kw8HE/yKFOwjaqq7Crb9nHqAaCoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRTSZZ%2FbtrIi0kw8HE%2FyKFOwjaqq7Crb9nHqAaCoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;819&quot; height=&quot;252&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5KLoh/btrImP3QbEd/iNk3IiWjLAe06pmCyKxKck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5KLoh/btrImP3QbEd/iNk3IiWjLAe06pmCyKxKck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5KLoh/btrImP3QbEd/iNk3IiWjLAe06pmCyKxKck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5KLoh%2FbtrImP3QbEd%2FiNk3IiWjLAe06pmCyKxKck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;239&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 두 개의 YAML에서는 각각 storageClassName 이라는 항목에 my-ebs-volume 이라는 값을 입력했다. 이러한 경우 스토리지 클래스의 이름이 일치하는 PV와 PVC이 서로 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, storageClassName을 별도로 YAML 파일에 명시하지 않으면 클래스가 설정되지 않았다는 뜻 인 &amp;rdquo;&amp;rdquo; 으로 설정되며 똑같이 스토리지 클래스가 설정되지 않은 PV 또는 PVC와 매칭된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 라벨 셀렉터를 사용할 수도 있다. 서비스와 디플로이먼트를 서로 연결할 때 라벨 셀렉터를 사용했던 것처럼 PVC에 라벨 셀렉터인 matchLabels 항목을 정의함으로써 특정 PV와 바인드하는 것이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o5BPN/btrIktT06hL/lXbkDeqKSqg8d4Q86hchaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o5BPN/btrIktT06hL/lXbkDeqKSqg8d4Q86hchaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o5BPN/btrIktT06hL/lXbkDeqKSqg8d4Q86hchaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo5BPN%2FbtrIktT06hL%2FlXbkDeqKSqg8d4Q86hchaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;818&quot; height=&quot;280&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qrFLw/btrIsQ7WMdM/XngDj4n6izPk5soVD8GQN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qrFLw/btrIsQ7WMdM/XngDj4n6izPk5soVD8GQN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qrFLw/btrIsQ7WMdM/XngDj4n6izPk5soVD8GQN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqrFLw%2FbtrIsQ7WMdM%2FXngDj4n6izPk5soVD8GQN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;825&quot; height=&quot;308&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PV의 라이프 사이클과 Reclaim Policy&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PV 를 정상적으로 생성하고 STATUS 부분을 확인하면 Avaliable 로 되어있는 것을 확인할 수 있다. STATUS라는 항목은 PV가 사용 가능한지, PVC와 연결됐는지 등을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC를 새로 생성하여 바인드했을 때는 STATUS 항목이 Bound(연결됨)로 바뀌는 것을 확인할 수 있다. PV가 이미 바인드 됐기 때문에 다른 PVC 와 연결할 수 없는 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PV와 연결된 PVC를 삭제할 경우 직관적으로 생각하면 PVC 가 없어졌으니 연결된 PV를 다시 사용할 수 있을 것처럼 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC 를 삭제하면 PV의 STATUS가 Available 이 아닌 Released 상태로 변경된다. Released 는 해당 PV의 사용이 끝났다는 것을 의미하며, Released 상태에 있는 PV는 다시 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 실제 데이터는 볼륨 안에 고스란히 보존돼 있기 때문에 PV 오브젝트를 삭제한 뒤 다시 생성하면 Avaliable 상태의 볼륨을 다시 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, PVC를 삭제했을 때, PV의 데이터를 어떻게 처리할 것인지 별도로 정의할 수도 있다. PV 사용이 끝났을 때 해당 볼륨을 어떻게 초기화 할 것인지 별도로 설정할 수 있는데 쿠버네티스는 이를 Reclaim Policy라고 부른다. 크게 Retain, Delete, Recycle 방식이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 PV의 용도는 데이터를 영구적으로 저장하기 위한 것이기 때문에 PV 사용이 끝난 뒤에도 원격 스토리지에 저장된 데이터를 계속해서 보존하고 싶을 것이다. 쿠버네티스는 기본적으로 PV 데이터를 보존하는 방식인 Retain 방식이 기본값으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl get pv 명령어에서 출력되는 RECLAIM POLICY 항목의 Retain 이라는 설정값이 이를 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PV 라이프 사이클에 대한 아무런 설정을 하지 않았다면 즉, PV 의 Reclaim Policy가 기본값인 Retain 으로 설정돼 있다면 PV 의 라이프 사이클은 Available &amp;rarr; Bound &amp;rarr; Released 가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reclaim Policy 가 Retain으로 설정된 PV는 연결된 PVC가 삭제된 후 Released 상태로 전환되며, 스토리지에 저장된 데이터는 그대로 보존된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retain 과 반대의 역할을 하는 Delete, Recycle 정책이 있다. PV 의 리클레임 정책을 Delete 로 설정해 생성했다면 PV의 사용이 끝난 뒤에 자동으로 PV가 삭제되며, 가능한 경우에 한해서 연결된 외부 스토리지도 함께 삭제된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgzRfE/btrIn4e3xga/sdQ4GHMTIUHDWvlPttksjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgzRfE/btrIn4e3xga/sdQ4GHMTIUHDWvlPttksjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgzRfE/btrIn4e3xga/sdQ4GHMTIUHDWvlPttksjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgzRfE%2FbtrIn4e3xga%2FsdQ4GHMTIUHDWvlPttksjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;812&quot; height=&quot;360&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;$ cat ebs-pv-delete.yaml | sed &quot;s/&amp;lt;VOLUME_ID&amp;gt;/$VOLUME_ID/g&quot; | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리클레임 정책이 Delete 인 PV를 파드 내부에 마운트한 뒤 PVC 를 삭제함으로써 사용을 종료할 경우, 리클레임 정책이 Delete 이기 때문에 PVC가 삭제됨과 동시에 PV가 삭제된다. 그 뿐만 아니라 외부에 연결돼 있던 EBS 볼륨도 한꺼번에 삭제되기 매누에 볼륨에 저장돼 있던 파일들이 모두 유실된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recycle 정책은 PVC 가 삭제됐을 때 PV 데이터를 모두 삭제한 뒤, Available 상태로 만들어준다. 이 정책은 Delete 와 다르게 PV나 외부 스토리지 자체를 삭제하지는 않는다. Recycle 정책은 쿠버네티스에서 더 이상 사용되지 않을 정책 중 하나이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;storageClass와 Dynamic Provisioning&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PV를 사용하려면 미리 외부 스토리지를 준비해야만 했다. AWS의 EBS를 PV로 사용하려면 EBS를 미리 생성한 뒤, 볼륨 ID를 YAML 파일에 직접 입력하는 방식이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 이렇게 볼륨 스토리지를 직접 수동으로 생성하고 스토리지에 대한 접근 정보를 YAML 파일에 적는 것은 귀찮은 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 쿠버네티스는 동적 프로비저닝이라는 기능을 제공한다. 동적 프로비저닝은 PVC가 요구하는 조건과 일치하는 PV가 존재하지 않는다면 자동으로 PV와 외부 스토리지를 함께 프로비저닝하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 동적 프로비저닝을 사용하면 EBS와 같은 외부 스토리지를 직접 미리 생성할 필요가 없으며 PVC를 생성하면 외부 스토리지가 자동으로 생성되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 특정 PV를 선택하기 위해 스토리지 클래스를 사용했던 것처럼 동적 프로비저닝에서도 사용할 수 있다. 동적 프로비저닝은 스토리지 클래스의 정보를 참고해 외부 스토리지를 생성하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 프로비저닝의 예시를 단계별로 알아보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ssd 라는 스토리지 클래스에는 SSD를 생성하라는 설정을, hdd라는 스토리지 클래스에는 HDD를 생성하라는 설정을 미리 정의했다고 가정한다.&lt;/li&gt;
&lt;li&gt;PVC에 특정 스토리지 클래스를 명시한다. PVC의 AccessModes나 Capacity 등의 조건과 일치하는 PV가 존재하지 않는 상태이다.&lt;/li&gt;
&lt;li&gt;조건에 일치하는 PV를 새롭게 만들기 위해 스토리지 클래스에 정의된 속성에 따라서 외부 스토리지를 생성한다. 만약, PVC 에 storageClassName: ssd 로 설정했고, ssd라는 이름의 스토리지 클래스가 SSD에 대한 설정 정보를 담고 있다면 SSD 스토리지가 동적으로 생성된다.&lt;/li&gt;
&lt;li&gt;새롭게 생성된 외부 스토리지는 쿠버네티스의 PV로 등록되고 PVC와 바인딩된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 프로비저닝을 모든 쿠버네티스 클러스터에서 범용적으로 사용할 수 있는 것은 아니며 동적 프로비저닝 기능이 지원되는 스토리지 프로비저너가 미리 활성화 돼 있어야 한다. 사용 가능한 프로비저닝 목록은 공식 문서에서 확인이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, AWS나 GCP와 같은 클라우드 플랫폼에서 쿠버네티스를 사용하고 있다면 별도로 설정하지 않아도 자동으로 동적 프로비저닝을 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS에서 동적 프로비저닝 사용&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daTWgc/btrIlxPRsEq/cKZJapum1XVVcjTbMG3hCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daTWgc/btrIlxPRsEq/cKZJapum1XVVcjTbMG3hCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daTWgc/btrIlxPRsEq/cKZJapum1XVVcjTbMG3hCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaTWgc%2FbtrIlxPRsEq%2FcKZJapum1XVVcjTbMG3hCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;819&quot; height=&quot;267&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b95qb0/btrIoCil927/YMVkkUsHZZpYLctkklvWuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b95qb0/btrIoCil927/YMVkkUsHZZpYLctkklvWuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b95qb0/btrIoCil927/YMVkkUsHZZpYLctkklvWuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb95qb0%2FbtrIoCil927%2FYMVkkUsHZZpYLctkklvWuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;807&quot; height=&quot;264&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;provisioner와 type을 봐야 한다. provisioner 항목에는 AWS의 쿠버네티스에서 사용할 수 있는 EBS 동적 프로비저너인 &lt;a href=&quot;http://kubernetes.io/aws-ebs%EB%A5%BC&quot;&gt;kubernetes.io/aws-ebs를&lt;/a&gt; 설정했다. type 항목은 EBS가 어떤 종류인지 나타내는 것으로, st1과 gp2, sc1, io1 등을 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LrDs2/btrInJh1cLC/3vX9wwjFp8POzUHEBFpkd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LrDs2/btrInJh1cLC/3vX9wwjFp8POzUHEBFpkd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LrDs2/btrInJh1cLC/3vX9wwjFp8POzUHEBFpkd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLrDs2%2FbtrInJh1cLC%2F3vX9wwjFp8POzUHEBFpkd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;279&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC에 storageClassName 항목을 정의하지 않았을 때, 쿠버네티스에서 기본적으로 사용하도록 설정된 스토리지 클래스가 있다면 해당 스토리지 클래스를 통해 동적 프로비저닝이 수행된다. 따라서 동적 프로비저닝을 아예 사용하지 않을 것이라면 storageClassName: &amp;ldquo;&amp;rdquo; 으로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 프로비저닝을 사용할 때 주의해야 할 점은 PV의 리클레임 정책이 자동으로 Delete로 설정된다는 점이다. 동적으로 생성되는 PV의 리클레임 정책 속성은 스토리지 클래스에 설정된 reclaimPolicy 항목을 상속받는데, 스토리지 클래스의 reclaimPolicy는 기본적으로 Delete 이기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동적 프로비저닝에서 특정 스토리지 클래스를 기본값으로 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 클래스를 생성할 때, 아래와 같은 annotation 을 추가하면 이 스토리지 클래스를 기본값으로 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zKt5X/btrIpuRWSaN/zhj2kT0Ear9jLkXEXpfcxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zKt5X/btrIpuRWSaN/zhj2kT0Ear9jLkXEXpfcxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zKt5X/btrIpuRWSaN/zhj2kT0Ear9jLkXEXpfcxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzKt5X%2FbtrIpuRWSaN%2Fzhj2kT0Ear9jLkXEXpfcxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;172&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;$ kubectl get storageclass
NAME               PROVISIONER             AGE
HDD                kubernetes.io/aws-ebs   3m27s
generic(default)   kubernetes.io/aws-ebs   1s 
SSD                kubernetes.io/aws-ebs   3m42s
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/266</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-PV-PVC#entry266comment</comments>
      <pubDate>Thu, 28 Jul 2022 14:33:57 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Ingress, Nginx Ingress Controller</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-%EC%9D%B8%EA%B7%B8%EB%A0%88%EC%8A%A4</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인그레스(Ingress)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스(Ingress)는 일반적으로 외부에서 내부로 향하는 것을 지칭하는 단어이다. 예를 들어 인그레스 트래픽은 외부에서 서버로 유입되는 트래픽을 의미하며, 인그레스 네트워크는 인그레스 트래픽을 처리하기 위한 네트워크를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 오브젝트가 외부 요청을 받아들이기 위한 것이었다면 &amp;lsquo;인그레스'는 외부 요청을 어떻게 처리할 것인지 네트워크 7계층 레벨에서 정의하는 쿠버네티스 오브젝트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &amp;lsquo;처리한다'라는 문장에는 많은 기능이 내포돼 있는데 인그레스 오브젝트가 담당할 수 있는 기본적인 기능만 간단히 나열해보면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 요청의 라우팅: /apple, /apple/red 등과 같이 특정 경로로 들어온 요청을 어떠한 서비스로 전달할지 정의하는 라우팅 규칙을 설정할 수 있다.&lt;/li&gt;
&lt;li&gt;가상 호스트 기반의 요청 처리: 같은 IP에 대해 다른 도메인 이름으로 요청이 도착했을 때, 어떻게 처리할 것인지 정의할 수 있다.&lt;/li&gt;
&lt;li&gt;SSL/TLS 보안 연결 처리: 여러 개의 서비스로 요청을 라우팅할 때, 보안 연결을 위한 인증서를 쉽게 적용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스의 기능은 위 기능에만 제한되는 것이 아니며, 인그레스를 어떻게 사용하냐(ALB 를 인그레스로 사용하는 등..)에 따라 다양한 기능을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스 자체의 기능은 비교적 정해져 있어도 인그레스의 요청을 처리할 서버로 무엇을 선택하느냐에 따라 기능이 조금씩 달라지기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인그레스를 사용하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에서 사용할 수 있는 NodePort나 LoadBalancer 타입의 서비스를 사용해도 위 기능들을 구현하는 것이 불가능하지는 않기 때문에 인그레스를 왜 사용해야 하는지 알아야 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 애플리케이션이 3개의 디플로이먼트로 생성돼 있다고 가정해 볼 때 각각의 디플로이먼트를 외부에 노출해야 한다면 NodePort 또는 LoadBalancer 타입의 서비스 3개를 생성하는 방법을 먼저 떠올릴 것이다. 각 디플로이먼트에 대응하는 서비스를 하나씩 연결해 준 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식은 잘 작동하는 것 같지만, 서비스마다 세부적인 설정을 할 때 추가적인 복잡성이 발생하게 된다. SSL/TLS 보안 연결, 접근 도메인 및 클라이언트 상태에 기반한 라우팅 등을 구현하려면 각 서비스와 디플로이먼트에 대해 일일이 설정을 해야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 쿠버네티스가 제공하는 인그레스 오브젝트를 사용하면 URL 엔드포인트를 단 하나만 생성함으로써 이러한 번거로움을 쉽게 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 디플로이먼트를 외부로 노출하는 인그레스를 생성하면 3개의 서비스에 대해 3개의 URL이 각각 존재하는 것이 아닌 인그레스에 접근하기 위한 하나의 URL만 존재한다. 따라서 클라이언트는 인그레스의 URL로만 접근하게 되며, 해당 요청은 인그레스에서 정의한 규칙에 따라 처리된 뒤 적절한 디플로이먼트의 파드로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 AWS ELB로 풀어 설명하자면 클라이언트는 하나의 ALB에 접근을 하게 되고 ALB가 3개의 NLB로 부하 분산을 시켜 주는 것이다. 여기서 ALB는 인그레스가 NLB는 서비스 오브젝트가 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인그레스 구조&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MvCSn/btrIjOvNWyx/49ImIeKhnFUlrHVEBQ82WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MvCSn/btrIjOvNWyx/49ImIeKhnFUlrHVEBQ82WK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MvCSn/btrIjOvNWyx/49ImIeKhnFUlrHVEBQ82WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMvCSn%2FbtrIjOvNWyx%2F49ImIeKhnFUlrHVEBQ82WK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1630&quot; height=&quot;902&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[1] host: 해당 도메인 이름으로 접근하는 요청에 대해서 처리 규칙을 적용한다. 위 예시에서는 theotters.net 이라는 도메인으로 접근하는 요청만 처리하지만 여러 개의 host를 정의해 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[2] path: 해당 경로에 들어온 요청을 어느 서비스로 전달할 것인지 정의한다. 위 예시에서는 /login 이라는 경로의 요청을 backend에 정의된 서비스로 전달한다. 여러 개의 path를 정의해 경로를 처리할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[3] serviceName, servicePort: path로 들어온 요청이 전달될 서비스와 포트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 이 파일을 만들고 kubectl apply -f ingress-example.yaml 이라는 이름의 인그레스를 생성해도 이것만으로는 아무 일도 일어나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스는 단지 요청을 처리하는 규칙을 정의하는 선언적인 오브젝트일 뿐, 외부 요청을 받아들일 수 있는 실제 서버가 아니기 때문이다. 인그레스는 인그레스 컨트롤러(Ingress Controller)라고 하는 특수한 서버에 적용해야만 그 규칙을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 실제로 외부의 요청을 받아들이는 것은 인그레스 컨트롤러 서버이며, 이 서버가 인그레스 규칙을 로드해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스 컨트롤러 &amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;gt; 인그레스 규칙 적용 &amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;mdash;&amp;gt; 서비스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 쿠버네티스의 인그레스는 반드시 인그레스 컨트롤러라는 서버와 함께 사용해야 한다. 인그레스 컨트롤러 서버는 여러 종류가 있으며 필요에 따라 하나를 골라서 사용하면 되고 대표적으로 쿠버네티스에서 가장 많이 사용하는 Nginx 웹 서버 인그레스 컨트롤러가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 인그레스 컨트롤러는 쿠버네티스에서 공식적으로 개발되고 있기 때문에 설치를 위한 YAML 파일을 공식 깃허브 저장소에서 내려받을 수 있다. 그래서 명령어를 통해 Nginx 인그레스 컨트롤러와 관련된 모든 리소스를 한 번에 설치할 수 있다.(&lt;a href=&quot;https://kubernetes.github.io/ingress-nginx/deploy/&quot;&gt;https://kubernetes.github.io/ingress-nginx/deploy/&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;kubectl apply -f &amp;lt;https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.3.0/deploy/static/provider/cloud/deploy.yaml&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 인그레스 컨트롤러를 설치하면 자동으로 생성되는 서비스는 LoadBalancer 타입이다. 실제 운영 환경일 경우 LoadBalancer 타입에 DNS 이름을 할당함으로써 Nginx 인그레스 컨트롤러에 접근하는 것이 일반적이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가상 머신처럼 클라우드가 아닌 환경에서 인그레스를 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 머신처럼 클라우드가 아닌 환경에서 인그레스를 테스트하고 싶다면 LoadBalancer 대신 NodePort 타입의 서비스를 생성해 사용해도 된다. 이 경우에는 각 노드의 랜덤한 포트로 Nginx 인그레스 컨트롤러에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 온프레미스에서의 운영 단계를 계획하고 있다면 MetalLB나 오픈스택의 로드 밸런서를 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  인그레스 컨트롤러에 의해 요청이 최종적으로 도착할 디플로이먼트의 서비스는 어떤 타입이든지 상관은 없다. 다만 외부에 서비스를 노출할 필요가 없다면 ClusterIP 타입을 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  인그레스의 기능을 하나씩 사용해보기 위해 YAML 파일에 host, path 항목 등을 모두 명시했지만, 이는 반드시 정의할 필요는 없으며 인그레스 항목을 최소한으로 하여 설정으로 생성이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx 인그레스를 사용하는 방법을 순서대로 정리해보면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공식 깃허브에서 제공되는 YAML 파일로 Nginx 인그레스 컨트롤러를 생성한다.&lt;/li&gt;
&lt;li&gt;Nginx 인그레스 컨트롤러를 외부로 노출하기 위한 서비스를 생성한다.&lt;/li&gt;
&lt;li&gt;요청 처리 규칙을 정의하는 인그레스 오브젝트를 생성한다.&lt;/li&gt;
&lt;li&gt;Nginx 인그레스 컨트롤러로 들어온 요청은 인그레스 규칙에 따라 적절한 서비스로 전달된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정 중 3번에서 인그레스를 생성하면 인그레스 컨트롤러는 자동으로 인그레스를 로드해 Nginx 웹 서버에 적용한다. 이를 위해 Nginx 인그레스 컨트롤러는 항상 인그레스 리소스의 상태를 지켜보고 있으며, 기본적으로 모든 네임스페이스의 인그레스 리소스를 읽어와 규칙을 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 경로와 호스트 이름으로 들어온 요청은 인그레스에 정의된 규칙에 따라 서비스로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 실제로 서비스로 전달되는 것은 아니며, Nginx 인그레스 컨트롤러는 서비스에 의해 생성된 엔드포인트로 요청을 직접 전달한다. 즉 서비스의 ClusterIP 가 아닌 엔드포인트의 실제 종착 지점들로 요청이 전달되는 셈이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어노테이션을 이용한 설정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egBeBF/btrIfte78m7/u7LboBoPwnbbdfhsK6jbhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egBeBF/btrIfte78m7/u7LboBoPwnbbdfhsK6jbhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egBeBF/btrIfte78m7/u7LboBoPwnbbdfhsK6jbhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegBeBF%2FbtrIfte78m7%2Fu7LboBoPwnbbdfhsK6jbhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1588&quot; height=&quot;488&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubernetes.io/ingress.class 는 해당 인그레스 규칙을 어떤 인그레스 컨트롤러에 적용할 것인지를 의미한다. 인그레스 컨트롤러로는 Nginx 말고도 Kong이나 GKE 등 여러 가지 중 하나를 선택해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 클러스터 자체에서 기본적으로 사용하도록 설정된 인그레스 컨트롤러가 존재하는 경우가 있는데, 이 경우에는 어떤 인그레스 컨트롤러를 사용할 것인지 반드시 인그레스에 명시해주는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 GKE에서 쿠버네티스를 사용하고 있다면 GKE에서 제공하는 인그레스 컨트롤러를 기본적으로 사용하도록 설정돼 있다. 따라서 GKE에서 인그레스를 생성할 때 만약 annotation 항목에서 kubernetes.io/ingress.classs 를 사용하지 않으면 GKE의 인그레스 컨트롤러를 자동으로 생성해 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 GKE 나 다른 클라우드 서비스에서 Nginx 인그레스 컨트롤러를 사용하고 싶다면 반드시 kubernetes.io/ingress.class 를 nginx로 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx.ingress.kubernetes.io/rewrite-target 이라는 주석은 Nginx 인그레스 컨트롤러에서만 사용할 수 있는 기능이다. 이 주석은 인그레스에 정의된 경로로 들어오는 모든 요청을 rewrite-target에 설정된 경로로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, Nginx 인그레스 컨트롤러로 /login 으로 접근하면 서비스에는 / 경로로 전달된다. /login 으로 시작하는 모든 요청을 서비스의 / 로 전달한다. 옳은 URL 은 아니지만 /login/success/good 라는 경로로 요청을 보내도 똑같이 / 로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 루트 경로로 접근했을 때 특정 path 로 리다이렉트하는 app-root 주석이나 SSL 리다이렉트를 위한 ssl-redirect 주석 등을 사용할 수 있다. Nginx 인그레스 컨트롤러에서 사용할 수 있는 주석은 공식 사이트에서 확인 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 이런 주석들은 Nginx 인그레스 컨트롤러에서만 사용할 수 있으며, 다른 인그레스 컨트롤러 서버를 사용한다면 해당 인그레스 컨트롤러의 공식 문서를 참고해 적절한 방법으로 기능을 사용해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nginx 인그레스 컨트롤러에 SSL/TLS 보안 연결 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스의 장점 중 하나는 쿠버네티스의 뒤쪽에 있는 디플로이먼트와 서비스가 아닌 앞쪽에 있는 인그레스 컨트롤러에서 편리하게 SSL/TLS 보안 연결을 설정할 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 인그레스 컨트롤러 지점에서 인증서를 적용해 두면 요청이 전달되는 애플리케이션에 대해 모두 인증서 처리를 할 수 있다. 따라서 인그레스 컨트롤러가 보안 연결을 수립하기 위한 일종의 관문(Gateway) 역할을 한다고도 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS와 같은 클라우드 환경에서 LoadBalancer 타입의 서비스를 사용할 계획이라면 클라우드 플랫폼 자체에서 관리해주는 인증서를 인그레스 컨트롤러에 적용할 수도 있다. 예를 들어 AWS의 ACM을 LoadBalancer 타입의 서비스에 제공하는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 인그레스 컨트롤러 또한 인증서를 통한 보안 연결 기능을 제공하기 때문에 어렵지 않게 보안 연결을 설정할 수 있다. 가장 먼저 보안 연결에 사용할 인증서와 비밀키를 생성하자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;openssl req -x509 -nodes -days 365 -newkey rsa:2048
-keyout tls.key -out tls.crt -subj &quot;/CN=dev.theotters.net/0=dev&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/CN 에는 Nginx 인그레스 컨트롤러에 접근하기 위한 Public DNS 이름을 입력해야 한다. 위 예시에서 Nginx 인그레스 컨트롤러와 연결된 서비스에 dev.theotters.net 이라는 도메인으로 접근한다고 가정한 것이며, 인증서 생성 옵션은 환경에 맞게 적절히 변경해 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, AWS에서 생성되어 동적인 도메인 이름을 할당받은 클래식 로드 밸런서라면 /CN=*.ap-northeast-2.elb.amazonaws.com 처럼 사용하는 것도 가능하다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ ls
tls.crt tls.key ...

$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1072&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6uOgr/btrIh40skOS/Ub4EhVqPRKb4f2q6uEuSrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6uOgr/btrIh40skOS/Ub4EhVqPRKb4f2q6uEuSrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6uOgr/btrIh40skOS/Ub4EhVqPRKb4f2q6uEuSrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6uOgr%2FbtrIh40skOS%2FUb4EhVqPRKb4f2q6uEuSrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1608&quot; height=&quot;1072&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1072&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tls 항목이 추가되었고 spec.tls.hosts 항목에서는 보안 연결을 적용할 도메인 이름을, spec.tls.secretName 은 앞서 생성했던 tls 타입의 시크릿 이름을 입력했다. 이는 dev.theotters.net 이라는 도메인 이름으로 접근하는 요청에 대해 tls-secret 시크릿의 인증서로 보안 연결을 수립하겠다는 뜻이다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/265</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-%EC%9D%B8%EA%B7%B8%EB%A0%88%EC%8A%A4#entry265comment</comments>
      <pubDate>Tue, 26 Jul 2022 21:43:04 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Configmap, Secret</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-Configmap-Secret</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;컨피그맵(Configmap), 시크릿(Secret): 설정값을 파드에 전달&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정값이나 설정 파일을 내 애플리케이션에 전달하는 가장 확실한 방법은 도커 이미지 내부에 설정값 또는 설정 파일을 정적으로 저장해 놓는 것이다. 하지만 도커 이미지는 일단 빌드되고 나면 불변의 상태를 가지기 때문에 이 방법은 상황에 따라 설정 옵션을 유연하게 변경할 수 없다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대한 대안으로 파드를 정의하는 YAML 파일에 환경 변수를 직접 적어 놓는 하드 코딩 방식을 사용할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nZcdQ/btrIipDAR8e/LPWBkDZKITZFTMm4P2W7M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nZcdQ/btrIipDAR8e/LPWBkDZKITZFTMm4P2W7M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nZcdQ/btrIipDAR8e/LPWBkDZKITZFTMm4P2W7M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnZcdQ%2FbtrIipDAR8e%2FLPWBkDZKITZFTMm4P2W7M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;387&quot; height=&quot;215&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 환경 변수를 파드 템플릿에 직접 명시하는 방식도 나쁘지는 않지만, 상황에 따라서는 환경 변수의 값만 다른, 동일한 여러 개의 YAML 파일이 존재할 수도 있다. 즉, 운영 환경과 개발 환경이 다른 경우에 각각 디플로이먼트를 생성해야 한다면 환경 변수가 서로 다르게 설정된 두 가지 버전의 YAML 파일이 따로 존재해야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 YAML 파일과 설정값을 분리할 수 있는 컨피그맵과 시크릿이라는 오브젝트를 제공한다. 컨피그맵에는 설정값을 시크릿에는 노출되서는 안되는 비밀값을 저장할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;kubectl create configmap &amp;lt;컨피그맵 이름&amp;gt; &amp;lt;각종 설정값들&amp;gt;
kubectl create cm log-level-configmap --from-literal LOG_LEVEL=DEBUG
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--from-literal 옵션을 여러 번 사용함으로써 여러 개의 키-값을 컨피그맵에서 사용하도록 설정할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;kubectl create cm start-k8s --from-literal k8s=kubernetes --from-literal container=docker
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵에 저장된 설정값은 kubectl describe configmap 명령어나 kubectl get configmap -o yaml 명령어로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵을 파드에서 사용하는 방법은 크게 두 가지가 있다. 내 애플리케이션이 소스코드 내부에서 어떻게 설정값을 읽는지에 따라 적절한 방법을 선택해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨피그맵의 값을 컨테이너의 환경 변수로 사용&lt;/li&gt;
&lt;li&gt;컨피그맵의 값을 파드의 컨테이너 환경 변수로 가져온다. 컨피그맵에 저장된 키-값 데이터가 컨테이너 환경 변수의 키-값으로서 그대로 사용되기 때문에 echo $LOG_LEVEL과 같은 방식으로도 값을 확인할 수 있다. 내 앱이 시스템 환경 변수로부터 설정값을 가져온다면 이 방법을 사용하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;컨피그맵의 값을 파드 내부의 파일로 마운트해 사용&lt;/li&gt;
&lt;li&gt;컨피그맵의 값을 파드 컨테이너 내부의 특정 파일로 마운트한다. 예를 들어 LOG_LEVEL=INFO 라는 값을 가지는 컨피그맵을 /etc/config/log_level 이라는 파일로 마운트하면 log_level 파일에는 INFO 라는 값이 저장된다. 이때 파일이 위치할 경로는 별도로 설정할 수 있다. 내 앱이 nginx.conf, .env 등의 파일을 통해 설정값을 읽어 들인다면 이 방법을 사용하는 것이 좋다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨피그맵의 데이터를 컨테이너의 환경 변수로 가져오기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biL2IW/btrIeMTN2oa/65846Qs5qexkdWaBjr7Pu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biL2IW/btrIeMTN2oa/65846Qs5qexkdWaBjr7Pu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biL2IW/btrIeMTN2oa/65846Qs5qexkdWaBjr7Pu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiL2IW%2FbtrIeMTN2oa%2F65846Qs5qexkdWaBjr7Pu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1370&quot; height=&quot;704&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 컨피그맵을 불러와서 환경 변수를 설정하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;envFrom 항목은 하나의 컨피그맵에 여러 개의 키-값 쌍이 존재하더라도 모두 환경 변수로 가져오도록 설정한다. 따라서 start-k8s 컨피그맵에서 2개의 키-값 데이터가 한꺼번에 포드의 환경 변수로 등록된다. 즉 총 3개의 키-값 쌍을 파드로 넘긴 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;valueFrom과 configMapKeyRef 를 사용하면 여러 개의 키-값 쌍이 들어 있는 컨피그맵에서 특정 데이터만을 선택해 환경 변수로 가져올 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GaEeu/btrIeMM1nK2/RQmekyWdJjSWkyb8GUHmBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GaEeu/btrIeMM1nK2/RQmekyWdJjSWkyb8GUHmBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GaEeu/btrIeMM1nK2/RQmekyWdJjSWkyb8GUHmBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGaEeu%2FbtrIeMM1nK2%2FRQmekyWdJjSWkyb8GUHmBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1452&quot; height=&quot;716&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;$ kubectl exec continer-selective-env-example env | grep ENV
ENV_KEYNAME_2=kubernetes
ENV_KEYNAME_1=DEBUG
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;envFrom: 컨피그맵에 존재하는 모든 키-값 쌍을 가져온다.&lt;/li&gt;
&lt;li&gt;valueFrom: 컨피그맵에 존재하는 키-값 쌍 중에서 원하는 데이터만 선택적으로 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨피그맵의 내용을 파일로 파드 내부에 마운트하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 앱이 nginx.conf, mysql.conf, .env 등과 같은 특정 파일로부터 설정값을 읽어온다면 컨피그맵의 데이터를 파드 내부의 파일로 마운트해 사용할 수 있다. 예를 들어 아래 YAML 파일은 start-k8s 컨피그맵에 존재하는 모든 키-값 쌍을 /etc/config 디렉토리에 위치시킨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zMkgo/btrIeL1CfdO/VSPvvPQJ4ib6totOKpkihk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zMkgo/btrIeL1CfdO/VSPvvPQJ4ib6totOKpkihk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zMkgo/btrIeL1CfdO/VSPvvPQJ4ib6totOKpkihk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzMkgo%2FbtrIeL1CfdO%2FVSPvvPQJ4ib6totOKpkihk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1356&quot; height=&quot;814&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spec.volumes: YAML 파일에서 사용할 볼륨의 목록을 정의한다. 위 예시에서는 start-k8s 라는 이름의 컨피그맵을 통해 configmap-volume 볼륨을 정의했다.&lt;/li&gt;
&lt;li&gt;spec.containers.volumeMounts: volumes 항목에서 정의된 볼륨을 컨테이너 내부의 어떤 디렉토리에 마운트할 것인지 명시한다. 위 예시에서는 /etc/config 디렉토리에 컨피그맵의 값이 담긴 파일이 마운트될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ kubectl exec configmap-volume-pod ls /etc/config
container
k8s

$ kubectl exec configmap-volume-pod ls /etc/config/k8s
kubernetes
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵에 저장돼 있던 두 개의 키-쌍 데이터, 즉, container와 k8s라는 키 이름이 파일로 존재하고 있다. 여기서 알아둬야 할 것은 컨피그맵의 모든 키-값 쌍 데이터가 마운트 됐으며, 파일 이름은 키의 이름과 같다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  컨피그맵과 같은 쿠버네티스 리소스의 데이터를 파드 내부 디렉토리에 위치시키는 것을 쿠버네티스 공식 문서에서는 투사(Projection)라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키-쌍 데이터를 파드에 마운트하는 것이 아닌, 원하는 키-쌍 데이터만 선택해서 파드에 파일로 가져올 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FbmBu/btrIiLGkuGe/TNGJdr2PwGVaXeC0ZyLqkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FbmBu/btrIiLGkuGe/TNGJdr2PwGVaXeC0ZyLqkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FbmBu/btrIiLGkuGe/TNGJdr2PwGVaXeC0ZyLqkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFbmBu%2FbtrIiLGkuGe%2FTNGJdr2PwGVaXeC0ZyLqkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1348&quot; height=&quot;480&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;items 항목: 컨피그맵에서 가져올 키-값의 목록을 의미하며, k8s라는 키만 가져오도록 명시했다.&lt;/li&gt;
&lt;li&gt;path 항목: 최종적으로 디렉토리에 위치할 파일의 이름을 입력하는 항목으로 k8s_fullname 이라는 값을 입력했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, /etc/config/k8s_fullname 경로에 파일이 위치하게 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ kubectl exec selective-cm-volume-pod ls /etc/config
k8s_fullname

$ kubectl exec selective-cm-volume-pod ls /etc/config/k8s_fullname
kubernetes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일로부터 컨피그맵 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵을 볼륨으로 포드에 제공할 때는 대부분 설정 파일 그 자체를 컨피그맵으로 사용하는 경우가 많다. 예를 들어, Nginx의 설정 파일인 nginx.conf 또는 MySQL의 설정 파일인 mysql.conf의 내용을 아예 통째로 컨피그맵에 저장한 뒤 이를 볼륨 파일로 포드 내부에 제공하면 좀 더 효율적인 설정 관리가 가능할 것이다. 이러한 경우를 위해 쿠버네티스는 컨피그맵을 파일로부터 생성하는 기능 또한 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--from-file 옵션을 사용하면 파일로부터 컨피그맵을 생성할 수 있다. --from-file 옵션을 여러 번 사용해 여러 개의 파일을 컨피그맵에 저장할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;$ kubectl create configmap &amp;lt;컨피그맵 이름&amp;gt; --from-file &amp;lt;파일 이름&amp;gt;
$ echo hello, world &amp;gt;&amp;gt; index.html
$ kubectl create configmap index-file --from-file index.html
configmap/index-file created
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--from-file 옵션에서 별도의 키를 지정하지 않으면 파일 이름이 키로, 파일의 내용이 값으로 저장된다. 위의 예시에서는 index.html 이라는 파일로 컨피그맵을 생성했기 때문에 index.html 이라는 키에 hello, world 라는 값이 설정됐다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;YAML 파일로 컨피그맵 정의하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵은 YAML 파일을 사용해서 오브젝트를 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kOd6h/btrIi5R8imN/5WUJg3CjEi9s56aAfYP540/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kOd6h/btrIi5R8imN/5WUJg3CjEi9s56aAfYP540/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kOd6h/btrIi5R8imN/5WUJg3CjEi9s56aAfYP540/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkOd6h%2FbtrIi5R8imN%2F5WUJg3CjEi9s56aAfYP540%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1312&quot; height=&quot;824&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵 &amp;rArr; 환경변수로 줄 수도, 파드에 마운트시켜서 컨피그맵 내부의 값을 주거나, 파일을 컨피그맵으로 만들어서 볼륨 마운트시켜 파드에 넘겨줄 수도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시크릿 사용 방법 익히기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 SSH 키, 비밀번호 등과 같이 민감한 정보를 저장하기 위한 용도로 사용되며, 네임스페이스에 종속되는 쿠버네티스 오브젝트이다. 시크릿과 컨피그맵은 사용 방법이 매우 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 민감한 정보를 저장하기 위해 컨피그맵보다 좀 더 세분화 된 사용법을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령어는 my-password 라는 이름의 시크릿을 생성하며, password=1q2w3e4r 이라는 키-값 쌍을 저장한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ kubectl create secret generic my-password --from-literal password=1q2w3e4r
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 컨피그맵처럼 --from-literal 대신 --from-file 이나 --from-env-file 옵션을 이용해 파일로부터 값을 읽어와 사용해도 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ echo mypassword &amp;gt; pw1 &amp;amp;&amp;amp; echo yourpassword &amp;gt; pw2
$ kubectl create secret generic our-password --from-file pw1 --from-file pw2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 컨피그맵과 달리 데이터의 사용 목적에 따라 몇 가지 종류로 나뉘기 때문에 generic 과 같은 특수 옵션에 대해 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl describe secret 으로 방금 생성한 시크릿을 확인하면 키-값 쌍 에서 값 부분이 이상한 값으로 변형돼 표기되는 것을 볼 수 있고, 이는 시크릿에 값을 저장할 때 쿠버네티스가 기본적으로 base64로 값을 인코딩하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 YAML 파일로부터 시크릿을 생성할 때도 데이터의 값에 base64로 인코딩 된 문자열을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿의 키-값 데이터를 파드의 환경 변수로 설정할 수도 있고, 특정 경로의 파일로 파드 내에 마운트할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시를 확인하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rG07S/btrIfRm01IS/7yWR3zqwZrfP0bDOPaS201/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rG07S/btrIfRm01IS/7yWR3zqwZrfP0bDOPaS201/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rG07S/btrIfRm01IS/7yWR3zqwZrfP0bDOPaS201/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrG07S%2FbtrIfRm01IS%2F7yWR3zqwZrfP0bDOPaS201%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;345&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XlVYU/btrIjNXZfq4/mhfMfBpkt5IhlLFe2an2Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XlVYU/btrIjNXZfq4/mhfMfBpkt5IhlLFe2an2Ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XlVYU/btrIjNXZfq4/mhfMfBpkt5IhlLFe2an2Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXlVYU%2FbtrIjNXZfq4%2FmhfMfBpkt5IhlLFe2an2Ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;412&quot; height=&quot;231&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 시크릿의 키-값 데이터를 파일로 파드의 볼륨에 마운트할 수도 있으며, 여러 개의 키-값 쌍이 존재하는 시크릿에서 선택적으로 사용할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaOzs2/btrIhjQ5AJS/V1fdcaKMRFsKeoWeWQZvk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaOzs2/btrIhjQ5AJS/V1fdcaKMRFsKeoWeWQZvk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaOzs2/btrIhjQ5AJS/V1fdcaKMRFsKeoWeWQZvk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaOzs2%2FbtrIhjQ5AJS%2FV1fdcaKMRFsKeoWeWQZvk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;249&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMzkuj/btrIkpWQMkc/7Jo2VECe38x4SKKeXhk6XK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMzkuj/btrIkpWQMkc/7Jo2VECe38x4SKKeXhk6XK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMzkuj/btrIkpWQMkc/7Jo2VECe38x4SKKeXhk6XK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMzkuj%2FbtrIkpWQMkc%2F7Jo2VECe38x4SKKeXhk6XK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;761&quot; height=&quot;299&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이미지 레지스트리 접근을 위한 docker-registry 타입의 시크릿 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 컨피그맵처럼 단순 문자열이나 설정 파일 등을 저장할 때 사용할 수도 있지만 사용 목적에 따라 여러 종류의 시크릿을 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿의 기본 타입은 Opaque 로 설정돼 있고, Opaque 타입은 별도로 시크릿의 종류를 명시하지 않으면 자동으로 설정되는 타입이며, 사용자가 정의하는 데이터를 저장할 수 있는 일반적인 목적의 시크릿이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl create 명령어로 시크릿을 생성할 때 generic 이라고 명시했던 것이 바로 Opaque 타입에 해당하는 종류이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사설 레지스트리에 접근하기 위해서는 cli 상에서 로그인이 필요로 한데 도커의 경우 docker login 을 사용하여 사설 레지스트리에 인증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에서는 docker login 명령어 대신 레지스트리의 인증 정보를 저장하는 별도의 시크릿을 생성해 사용한다. 레지스트리 인증을 위해 시크릿을 생성하는 방법은 두 가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째는 docker login 명령어로 로그인에 성공했을 때 도커 엔진이 자동으로 생성하는 ~/.docker/config.json 파일을 사용하는 것이다. config.json 파일에는 인증을 위한 정보가 담겨 있기 때문에 이를 그대로 시크릿으로 가져오면 된다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ kubectl create secret generic registry-auth \
--from-file=.dockerconfigjson=/root/.docker/config.json \
--type=kubernetes.io/dockerconfigjson&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 시크릿을 생성하는 명령어에서 직접 로그인 인증 정보를 명시할 수도 있다. 각 옵션에 적절한 인자를 입력하면 되며, --docker-username과 --docker-password 옵션은 로그인 이름과 비밀번호를 입력하는 필수 옵션이다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ kubectl create secret docker-registry registry-auth-bt-cmd \
--docker-username=how0326 \
--docker-password=12345&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--docker-server 는 필수 옵션이 아니며, 필요에 따라 사용하면 된다. --docker-server 옵션을 사용하지 않으면 기본적으로 도커 허브(&lt;a href=&quot;http://docker.io&quot;&gt;docker.io&lt;/a&gt;)를 사용하도록 설정되지만, 다른 사설 레지스트리를 사용하려면 --docker-server 옵션에 해당 서버의 주소 또는 도메인 이름을 입력하면 된다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ kubectl create secret docker-registry registry-auth-bt-cmd \
--docker-username=how0326 \
--docker-password=12345 \
--docker-server=ecs.registry.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어로 생성된 시크릿은 &lt;a href=&quot;http://kubernetes.io/dockerconfigjson&quot;&gt;kubernetes.io/dockerconfigjson&lt;/a&gt; 이라는 타입으로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시크릿은 디플로이먼트 또는 포드 등에서 사설 레지스트리로부터 이미지를 받아올 때 사용할 수 있다. 예를 들어 도커 허브의 프라이빗 저장소에 저장된 이미지를 통해 파드를 생성하려면 다음과 같이 YAML 파일에서 imagePullSecret 항목을 정의한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l6gq5/btrIfSTKM0T/dWKaXbnD5cXu9bhhvckAT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l6gq5/btrIfSTKM0T/dWKaXbnD5cXu9bhhvckAT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l6gq5/btrIfSTKM0T/dWKaXbnD5cXu9bhhvckAT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl6gq5%2FbtrIfSTKM0T%2FdWKaXbnD5cXu9bhhvckAT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;268&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  기본적으로는 YAML 파일에 명시된 도커 이미지가 워커 서버에 존재하지 않을 때만 이미지를 받아오도록 설정돼 있지만, imagePullPolicy 항목을 통해 이미지를 받아오는 설정을 변경할 수 있다. imagePullPolicy의 자세한 사용 방법은 쿠버네티스 공식 문서를 참고할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TLS 키를 저장할 수 있는 tls 타입의 시크릿 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 TLS 연결에 사용되는 공개키, 비밀키 등을 쿠버네티스에 자체적으로 저장할 수 있도록 tls 타입을 지원한다. 따라서 파드 내부의 애플리케이션이 보안 연결을 위해 인증서나 비밀키 등을 가져와야 할 때 시크릿의 값을 파드에 제공하는 방식으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tls 타입의 시크릿을 사용하는 방법은 매우 간단하다. 보안 연결에 사용되는 키 페어가 미리 준비돼 있다면 kubectl create secret tls 명령어로 쉽게 생성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj &quot;/CN=example.com&quot; -keyout cert.key -out cert.crt

$ ls
cert.crt cert.key ...

$ kubectl create secret tls my-tls-secret --cert cert.crt --key cert.key
secret/my-tls-sercret created
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 시크릿의 정보를 확인해보면 cert.crt 와 cert.key 파일의 내용이 tls.crt 와 tls.key 라는 키로 저장돼 있음을 알 수 있다. 각 데이터는 모두 base64로 인코딩되어 저장된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좀 더 쉽게 컨피그맵과 시크릿 리소스 배포하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLI 상에서 시크릿을 만드는 것보다 YAML 을 통해 시크릿 오브젝트를 배포할 수 있다. 그러나 시크릿의 데이터가 많아질수록 YAML 파일에 직접 시크릿의 데이터를 저장하는 것은 바람직한 방법이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 단점을 해결하면서 시크릿이나 컨피그맵을 배포하기 위해 YAML 파일을 작성할 때, 데이터를 YAML 파일로부터 분리할 수 있는 kustomize 기능을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kustomize는 자주 사용되는 YAML 파일의 속성을 별도로 정의해 재사용하거나 여러 YAML 파일을 하나로 묶는 등 다양한 용도로 사용할 수 있는 기능이다. 그렇지만 지금은 시크릿과 컨피그맵을 좀 더 쉽게 쓰기 위한 용도로 kustomize를 간단하게 사용해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq3jGw/btrIiKOgce1/VdNObAJYhUNYTt6imPuKA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq3jGw/btrIiKOgce1/VdNObAJYhUNYTt6imPuKA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq3jGw/btrIiKOgce1/VdNObAJYhUNYTt6imPuKA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq3jGw%2FbtrIiKOgce1%2FVdNObAJYhUNYTt6imPuKA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1426&quot; height=&quot;394&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kustomize 로부터 생성될 시크릿의 정보를 미리 확인하려면 kubectl kustomize 명령어를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfH5Ti/btrIfRAz6yK/yMIfR6Qi0cAwONeS2glY5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfH5Ti/btrIfRAz6yK/yMIfR6Qi0cAwONeS2glY5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfH5Ti/btrIfRAz6yK/yMIfR6Qi0cAwONeS2glY5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfH5Ti%2FbtrIfRAz6yK%2FyMIfR6Qi0cAwONeS2glY5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1564&quot; height=&quot;490&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kustomization.yaml 파일로부터 시크릿을 생성하려면 해당 파일이 위치한 디렉토리에서 kubectl apply -k ./ 명령어를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 컨피그맵을 kustomize 로부터 생성하고 싶다면 kustomization.yaml 파일에서 secretGenerator 대신 configmapGenerator 를 사용하면 된다. 컨피그맵은 시크릿과 달리 종류가 존재하지 않으므로 type 항목은 제거한다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/264</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-Configmap-Secret#entry264comment</comments>
      <pubDate>Tue, 26 Jul 2022 20:34:07 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Namespace</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-Namespace-%EC%9E%AC%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Namespace: 리소스를 논리적으로 구분하는 장벽&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에서는 리소스를 논리적으로 구분하기 위해 네임스페이스라는 오브젝트를 제공한다. 간단히 생각해서 네임스페이스는 포드, 레플리카셋, 디플로이먼트, 서비스 등과 같은 리소스들이 묶여 있는 하나의 가상 공간 또는 그룹이라고 이해하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 모니터링을 위한 모든 리소스들은 monitoring 이라는 이름의 네임스페이스에서 생성할 수 있고, 테스트를 위한 리소스들은 testbed 라는 네임스페이스에서 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는, 여러 개발 조직이 하나의 쿠버네티스 클러스터를 공유해 사용해야 한다면 조직별로 네임스페이스를 사용하도록 구성할 수 있다. 이렇게 여러 개의 네임스페이스를 사용하면 마치 하나의 클러스터에서 여러 개의 가상 클러스터를 사용하는 것처럼 느껴질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스를 설치하면 자동으로 default, kube-system 라는 네임스페이스가 생성되고 kubectl 명령어로 쿠버네티스 리소스를 사용할 때는 기본적으로 default 네임스페이스를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 kube-system 네임스페이스는 쿠버네티스에 대한 충분한 이해 없이는 건드리지 않는 것이 좋다. 예상치 못하게 쿠버네티스 클러스터가 동작하지 않을 수도 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스를 사용하는 경우는 대부분 모니터링, 로드 밸런싱 인그레스 등의 특정 목적을 위한 용도가 대부분이다. 각 네임스페이스의 리소스들은 논리적으로만 구분된 것일 뿐, 물리적으로 격리된 것이 아니기 때문에 서로 다른 네임스페이스에서 생성된 파드가 같은 노드에 존재할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Label vs NameSpace&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스와 라벨의 차이점이 궁금할 수 있다. 서비스와 파드를 매칭시키기 위해 사용하는 라벨 또한 리소스를 분류하고 구분하기 위한 방법 중 하나이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스는 라벨보다 더욱 넓은 용도로 사용될 수 있다. ResourceQuota 오브젝트를 이용해 특정 네임스페이스에서 생성되는 포드의 자원 사용량을 제한하거나, 애드미션 컨트롤러라는 기능을 이용해 특정 네임스페이스에 생성되는 파드에는 항상 사이드카 컨테이너를 붙이도록 설정할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 쿠버네티스의 사용 목적에 따라 파드, 서비스 등의 리소스를 격리함으로써 편리하게 구분할 수 있다는 특징도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네임스페이스에 종속되는 쿠버네티스 오브젝트와 독립적인 오브젝트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A라는 네임스페이스에서 파드를 만들면 A 네임스페이스에서만 보이고 B 네임스페이스에서는 보이지 않는다. 이런 경우를 쿠버네티스에서는 &amp;lsquo;오브젝트가 네임스페이스에 속한다'라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 네임스페이스 속하지 않는 쿠버네티스 오브젝트는 nodes이다. nodes는 쿠버네티스 클러스터에서 사용되는 저수준의 오브젝트이며, 네임스페이스에 의해 구분되지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드처럼 네임스페이스에 속하지 않는 오브젝트들은 보통 네임 스페이스에서 관리되지 않아야 하는 클러스터 전반에 걸쳐 사용되는 경우가 많다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/263</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-Namespace-%EC%9E%AC%EC%A0%95%EB%A6%AC#entry263comment</comments>
      <pubDate>Tue, 26 Jul 2022 20:33:19 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Service</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-Service-%EC%9E%AC%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Service: 포드를 연결하고 외부에 노출&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 디플로이먼트의 포드들이 내부적으로 접근하려면 서비스라고 부르는 별도의 쿠버네티스 오브젝트를 생성해야 한다. 서비스는 포드에 접근하기 위한 규칙을 정의하기 때문에 쿠버네티스에서 애플리케이션을 배포하기 위해서는 반드시 알아야 할 오브젝트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 기능은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개의 포드에 쉽게 접근할 수 있도록 고유한 도메인 이름을 부여한다.&lt;/li&gt;
&lt;li&gt;여러 개의 포드에 접근할 때, 요청을 분산하는 로드 밸런서 기능을 한다.&lt;/li&gt;
&lt;li&gt;클라우드 플랫폼의 로드 밸런서, 클러스터 노드의 포트 등을 통해 포드를 외부로 노출한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서비스의 종류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ClusterIP 타입: 쿠버네티스 내부에서만 포드들에 접근할 때 사용한다. 외부로 포드를 노출하지 않기 때문에 쿠버네티스 클러스터 내부에서만 사용되는 포드에 적합하다.&lt;/li&gt;
&lt;li&gt;NodePort 타입: 포드에 접근할 수 있는 포트를 클러스터의 모든 노드에 동일하게 개방한다. 따라서 외부에서 포드에 접근할 수 있는 서비스 타입이다. 접근할 수 있는 포트는 랜덤으로 정해지지만, 특정 포트로 접근하도록 설정할 수도 있다.&lt;/li&gt;
&lt;li&gt;LoadBalancer 타입: 클라우드 플랫폼에서 제공하는 로드 밸런서를 동적으로 프로비저닝해 포드에 연결한다. NodePort 타입과 마찬가지로 외부에서 포드에 접근할 수 있는 서비스 타입이다. 그렇지만 일반적으로 AWS, GCP 등과 같은 클라우드 플랫폼 환경에서만 사용할 수 있다. nginx 를 사용하여 로드 밸런서 역할을 하게 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ClusterIP 타입의 서비스 - 쿠버네티스 내부에서만 포드에 접근하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec.selector: 이 서비스에서 어떠한 라벨을 가지는 포드에 접근할 수 있게 만들 것인지 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec.ports.port: 서비스는 쿠버네티스 내부에서만 사용할 수 있는 고유한 IP를 할당받는다. 이 때 port 항목에는 서비스의 IP에 접근할 때 사용할 포트를 설정하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec.ports.targetPort: 은 selector 항목에서 정의한 라벨에 의해 접근 대상이 된 포드들이 내부적으로 사용하고 있는 포트를 입력한다. 즉, 포드 템플릿에 정의된 containerPort 와 같은 값으로 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ClusterIP 타입의 서비스 IP와 포트를 통해 파드에 접근을 할 수 있고 서비스와 연결된 여러 개의 포드에 자동으로 요청이 분산된다. 서비스를 생성할 때 별도의 설정을 하지 않아도 서비스는 연결된 파드에 대해 로드 밸런싱을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에는 IP뿐만 아니라 서비스 이름 그 자체로도 접근할 수 있다. 쿠버네티스는 애플리케이션이 서비스나 파드를 쉽게 찾을 수 있도록 내부 DNS를 구동하고 있고 파드들은 자동으로 이 DNS를 사용하도록 설정되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 여러 파드가 클러스터 내부에서 서로를 찾아 연결해야 할 때는 서비스의 이름과 같은 도메인 이름을 사용하는 것이 일반적이다. 즉, 파드가 서로 상호 작용할 때는 파드의 IP를 알 필요 없고 파드와 연결된 서비스 이름을 사용함으로써 간단하게 파드에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  서비스의 라벨 셀렉터와 파드의 라벨이 매칭돼 연결되면 쿠버네티스는 자동으로 엔드포인트라고 부르는 오브젝트를 별도로 생성한다. 예를 들어, 서비스와 관련된 엔드포인트는 서비스와 동일한 이름으로 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔드 포인트라는 이름이 의미하는 것처럼 엔드포인트 오브젝트는 서비스가 가리키고 있는 도착점을 나타낸다. 서비스를 이용해 파드를 연결한다면 엔드포인트는 자동으로 생성되므로 엔드포인트를 지나치게 알 필요는 없지만, 엔드포인트 자체도 독립된 쿠버네티스의 리소스이기 때문에 이론상으로 서비스와 엔드포인트를 따로 따로 만들 수도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NodePort 타입의 서비스 - 서비스를 이용해 포드를 외부에 노출하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodePort 타입의 서비스는 클러스터 외부에서도 접근을 할 수 있다. NodePort 타입의 서비스는 모든 노드의 특정 포트를 개방해 서비스에 접근하는 방식이다. 도커에서 컨테이너를 외부로 노출하는 방식과 비슷하다고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 노드에서 개방되는 포트는 기본적으로 30000~32768 포트 주에 랜덤으로 선택되지만 YAML 파일에 nodePort 항목을 정의하면 원하는 포트를 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciE50O/btrIjObzdRU/khurs5E25VN7Yy7XijmaWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciE50O/btrIjObzdRU/khurs5E25VN7Yy7XijmaWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciE50O/btrIjObzdRU/khurs5E25VN7Yy7XijmaWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciE50O%2FbtrIjObzdRU%2Fkhurs5E25VN7Yy7XijmaWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1544&quot; height=&quot;388&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodePort 타입의 서비스는 ClusterIP의 기능을 포함하고 있다. NodePort 타입의 서비스는 내부 네트워크와 외부 네트워크 양쪽에서 접근을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  기본적으로 NodePort 가 사용할 수 있는 포트 범위는 30000~32768 이지만 API 서버 컴포넌트의 실행 옵션을 변경하면 원하는 포트 범위를 설정할 수 있다. 포트 범위를 직접 지정하려면 API 서버의 옵션을 다음과 같이 추가하거나 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--service-node-port-range=30000-35000&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 낮은 포트 번호는 시스템의 의해 예약된 포트일 수 있기 때문에 30000번 이상부터 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 운영 환경에서 NodePort로 서비스를 외부에 제공하는 경우는 많지 않다. NodePort에서 포트 번호를 80 이나 443으로 설정하기에는 적절하지 않으며, SSL 인증서 적용, 라우팅 등과 같은 복잡한 설정을 서비스에 적용하기가 어렵기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 NodePort 의 경우는 이 자체를 통해 서비스를 외부로 제공하기보다는 인그레스(Ingress)라고 부르는 쿠버네티스의 오브젝트에서 간접적으로 사용되는 경우가 많다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LoadBalancer 타입의 서비스 - 클라우드 플랫폼의 로드 밸런서와 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서 타입의 서비스는 서비스 생성과 동시에 로드 밸런서를 새롭게 생성해 포드와 연결한다. 로드 밸런서 타입의 서비스는 클라우드 플랫폼으로부터 도메인 이름과 IP를 할당받기 때문에 NodePort 보다 더욱 쉽게 파드에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  로드밸런서 타입의 서비스는 로드 밸런서를 동적으로 생성하는 기능을 제공하는 환경에서만 사용할 수 있다는 점을 알아야 한다. 일반적인 가상 머신이나, 온프레미스 환경에서는 사용하기 어려울 수 있지만 MetalLB 를 사용하여 온프레미스 환경에서도 돌릴 수 있기는 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서 타입의 서비스 또한 NodePort나 ClusterIP와 동일하게 서비스의 IP(CLUSTER-IP)가 할당되며, 파드에서는 서비스의 IP 또는 서비스의 이름으로 서비스에 접근할 수 있다. 눈여겨 볼 부분은 EXTERNER-IP 항목이다. 이 주소는 클라우드 플랫폼인 AWS, GCP 등으로부터 자동으로 할당된 것이며, 이 주소와 서비스의 포트를 통해 포드에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 서비스 타입과 마찬가지로 요청이 여러 개의 파드로 분산되는 로드 밸런싱 기능을 자동으로 사용할 수 있다. 또한 각 노드에서 동일하게 접근할 수 있는 PORT(S) 가 부여되어 NodePort 의 간접적인 기능 또한 자동으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 NLB나 ALB를 설정하고 싶다면 metadata.annotations 항목을 수정해주면 된다. 자세한 것은 AWS 공식 문서에 잘 나와 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  쿠버네티스에서 annotations는 라벨처럼 해당 리소스의 추가적인 정보를 나타내기 위한 키-값 쌍으로 이뤄져 있다. 그렇지만 리소스의 종류에 따라 특정 용도로 사용할 수 있게 쿠버네티스에 미리 정의된 몇 가지 주석이 있다. 이 주석들은 리소스에 특별한 설정값을 부여하기 위해 사용된다. 미리 정의된 주석은 라벨과 함께 익숙해지는 것이 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LoadBalancer 타입의 서비스 - 온프레미스 환경에서 LoadBalancer 타입의 서비스 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서 타입의 서비스는 일반적으로 AWS와 같은 클라우드 플랫폼에서 사용되지만, 필요하다면 직접 보유하고 있는 온프레미스 환경에서도 MetalLB 나 오픈 스택과 같은 특수한 환경을 직접 구축하여 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서도 MetalLB 라는 서드파티 제품을 사용하면 로드 밸런서 타입의 서비스를 사용할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요청을 외부로 리다이렉트하는 서비스: ExternalName&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스를 외부 시스템과 연동해야 할 때는 ExternalName 타입의 서비스를 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExternalName 타입을 사용해 서비스를 생성하면 서비스가 외부 도메인을 가리키도록 설정할 수 있다. 예를 들어 쿠버네티스 내부의 파드들이 externalname-svc 라는 이름으로 요청을 보낼 경우, 쿠버네티스의 DNS 는 my.database.com으로 접근할 수 있도록 CNAME 레코드를 반환한다. 즉, externalname-svc로 요청을 보내면 my.database.com에 접근하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExternalName 타입의 서비스는 쿠버네티스와 별개로 존재하는 레거시 시스템에 연동해야 하는 상황에 유용하게 사용할 수 있다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/262</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-Service-%EC%9E%AC%EC%A0%95%EB%A6%AC#entry262comment</comments>
      <pubDate>Tue, 26 Jul 2022 20:32:49 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] POD, ReplicaSet, Deployment</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-POD-ReplicaSet-Deployment-%EC%9E%AC%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;POD vs Container&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드는 같은 리눅스 네임스페이스를 공유하는 여러 컨테이너들을 추상호된 집합으로 사용하기 위해서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포드 내의 컨테이너들이 네트워크 네임스페이스 등과 같은 리눅스 네임스페이스를 공유해 사용하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 도커에서 컨테이너들끼리 네트워크 네임 스페이스를 공유하여 동일한 네트워크 환경을 사용한 것 처럼 POD 내부의 컨테이너들도 같은 리눅스 네임스페이스를 공유해 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;완전한 앱으로써의 POD&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 포드는 하나의 완전환 애플리케이션이다. 만약 하나의 애플리케이션에 로그를 수집하는 기능이나, 변경사항을 갱신해야 하는 경우 포드의 주 컨테이너와 기능 확장을 위한 추가 컨테이너를 함께 포드에 포함시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 포드에 정의된 부가적인 컨테이너를 사이드카 컨테이너라고 부르며 사이드카 컨테이너는 포드 내의 다른 컨테이너와 네트워크 환경 등을 공유하게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RepilcaSet&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POD 의 라이프 싸이클을 관리하기 위해 여러 개의 파드를 생성하거나 삭제할 때 한 번에 관리하기 편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드와 RS 는 느슨한 연결을 유지하고 있으며 이런 느슨한 연결은 포드와 RS 의 정의 중 라벨 셀렉터를 이용해 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라벨은 포드 등의 쿠버네티스 리소스를 분류할 때 유용하게 사용할 수 있는 메타데이터이다. 라벨은 쿠버네티스 리소스의 부가적인 정보를 표현할 수 있을 뿐만 아니라 서로 다른 오브젝트가 서로를 찾아야 할 때 사용되기도 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정해진 수의 동일한 포드가 항상 실행되도록 관리한다.&lt;/li&gt;
&lt;li&gt;노드 장애 등의 이유로 포드를 사용할 수 없을 때 다른 노드에서 포드를 다시 생성한다.&lt;/li&gt;
&lt;li&gt;레플리카셋의 목적은 &amp;lsquo;포드를 생성하는 것'이 아닌, &amp;lsquo;일정 개수의 포드를 유지하는 것&amp;rsquo;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Deployment&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 쿠버네티스 운영 환경에서 레플리카셋을 YAML 파일에서 사용하는 경우는 거의 없다. 대부분은 레플리카셋과 포드의 정보를 정의하는 디플로이먼트라는 오브젝트를 YAML 파일에 정의해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트는 레플리카셋의 상위 오브젝트이기 때문에 디플로이먼트를 생성하면 해당 디플로이먼트에 대응하는 레플리카셋도 함께 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  디플로이먼트로부터 생성된 레플리카셋과 포드는 특이한 해시값을 포함한 이름으로 생성된다. 이 해시값은 포드를 정의하는 템플릿으로부터 생성된 것 이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Deployment 를 사용하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트를 사용하는 이유 중 하나는 애플리케이션의 업데이트와 배포를 더욱 편하게 만들기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 애플리케이션을 업데이트할 때 레플리카셋의 변경 사항을 저장하는 리비전(revision)을 남겨 롤백을 가능하게 해주고, 무중단 서비스를 위해 포드의 롤링 업데이트의 전략을 지정할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트는 포드의 정보를 업데이트함으로써 새로운 레플리카셋과 포드를 생성하더라도 이전 버전의 레플리카셋을 삭제하지 않고 남겨두고 있다. 즉, 디플로이먼트는 포드의 정보가 변경되어 업데이트가 발생했을 때 이전의 정보를 리비전으로서 보존한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리비전에 대한 자세한 정보를 알고 싶으면 아래 명령어로 확인이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;kubectl rollout history deployment &amp;lt;디플로이먼트_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--record=true 옵션으로 디플로이먼트를 변경하면 변경 사항을 위와 같이 디플로이먼트에 기록함으로써 해당 버전의 레플리카셋을 보존한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 이전 버전의 레플리카셋으로 되돌리는 롤백을 하고 싶을 경우 아래와 같은 명령어를 사용할 수 있다. --to-revision 에는 되돌리려는 리비전의 번호를 입력하면 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl rollout undo deployment &amp;lt;디플로이먼트_이름&amp;gt; --to-revision=1
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/261</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-POD-ReplicaSet-Deployment-%EC%9E%AC%EC%A0%95%EB%A6%AC#entry261comment</comments>
      <pubDate>Tue, 26 Jul 2022 20:32:18 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] AWS EKS : EBS CSI(Container Storage Interface) Driver 설치하기</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-AWS-EKS-EBS-CSIContainer-Storage-Interface-Driver-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;CSI(Container Storage Interface)란, Kubernetes와 같은 CO(컨테이너 오케스트레이션 시스템)의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;컨테이너화된 워크로드에, 임의의 블록 및 파일 스토리지 시스템을 노출하기 위한 표준 인터페이스&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Amazon EBS CSI Driver를 사용하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;표준 Kubernetes 인터페이스를 사용&lt;/b&gt;하여,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AWS에서 EKS 및 자체 관리형 Kubernetes 클러스터 모두에서 실행되는 애플리케이션에 대해&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;블록 스토리지를 간단하게 구성하고 사용&lt;/b&gt;할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Amazon EBS CSI Driver에서는Amazon EKS 클러스터가 영구 볼륨을 위해&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;u&gt;Amazon EBS 볼륨의 수명 주기(LifeCycle)를 관리&lt;/u&gt;할 수 있게 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;클러스터를 처음 생성할 때는 Amazon EBS CSI Driver가 설치되지 않으며,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;드라이버를 사용하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;Amazon EKS 추가 기능&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는 자체 관리형 추가 기능으로 드라이버를 추가해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아래 그림은 일반적인 CSI Driver의 구조이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AWS EBS CSI driver 또한 다음과 같은 구조를 가지며, 우측 StatefulSet/Deployment로 배포된 controller Pod가 AWS API를 사용하여 실제 EBS volume을 생성하는 역할을 한다. 좌측의 DaemonSet으로 배포된 node Pod은 AWS API를 사용하여 Kubernetes node (EC2 instance)에 EBS volume을 attach 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p31K9/btrEND6rPGM/vyqkXhb3dWYHzSBtmXV030/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p31K9/btrEND6rPGM/vyqkXhb3dWYHzSBtmXV030/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p31K9/btrEND6rPGM/vyqkXhb3dWYHzSBtmXV030/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp31K9%2FbtrEND6rPGM%2FvyqkXhb3dWYHzSBtmXV030%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;673&quot; height=&quot;379&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyu3ss/btrD1gKSOsI/WJthxKQRKWkkWieEnMxD3K/img.png&quot; data-lightbox=&quot;lightbox&quot; data-alt=&quot;https://hyperconnect.github.io/2021/07/05/ebs-csi-gp3-support.html&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://hyperconnect.github.io/2021/07/05/ebs-csi-gp3-support.html&quot;&gt;https://hyperconnect.github.io/2021/07/05/ebs-csi-gp3-support.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;더 자세한 구조와 동작에 대한 설명은&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;Container Storage Interface의&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/container-storage-interface.md&quot;&gt;Design Document&lt;/a&gt;&lt;span&gt;에서 확인할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;EBS%--CSI%--Driver%--%EC%--%A-%EC%B-%--%ED%--%--%EA%B-%B-&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;EBS CSI Driver 설치하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;managing-ebs-csi에 따라 설치해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;NLB, ALB로 EKS 배포가 된 상태여야 한다.&amp;nbsp;현재 IAM 계정 설정까지 완료된 상태(yaml 파일에 정의)이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;addon 설치부터 진행하며, 클러스터 yaml파일은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;myeks.yaml&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;apiVersion: eksctl.io/v1alpha5 
kind: ClusterConfig 

metadata:
  name: myeks-custom #cluster 이름
  region: ap-northeast-2 
  version: &quot;1.22&quot;

# 가용영역 지정
availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;,  &quot;ap-northeast-2c&quot;]

#IAM 계정을 만들어 연결
iam:
  withOIDC: true #OIDC(OpenID Connect) : eks입장에서 AWS IAM은 외부인증서버이기 때문에 OIDC를 true로 하지 않으면 
                 #                       AWS 계정과 eks 계정은 완전히 별개이다.
  serviceAccounts: #SA 계정 생성 -&amp;gt; 이후 addon 추가할 때 필요
    - metadata:
        name: aws-load-balancer-controller #SA 계정 이름
        namespace: kube-system
      wellKnownPolicies:
        awsLoadBalancerController: true #계정에 권한 부여 : ingress를 만들 때 사용
    - metadata:
        name: ebs-csi-controller-sa #SA 계정 이름
        namespace: kube-system
      wellKnownPolicies:
        ebsCSIController: true #계정에 권한 부여 : ebs
    - metadata: 
        name: cluster-autoscaler #SA 계정 이름
        namespace: kube-system
      wellKnownPolicies:
        autoScaler: true #계정에 권한 부여

# Managed Node Groups : worker node 그룹
managedNodeGroups: #여러개 세팅 가능
  # On-Demand Instance
  - name: myeks-ng1
    instanceType: t3.medium
    minSize: 2
    desiredCapacity: 3 #cluster 오토스케일링된다
    maxSize: 4
    privateNetworking: true #기본적으로 EC2는 public에 배치된다(앞서 EXTERNAL-IP 부여된 것 확인했었다)
                            #외부에 노출되는 것은 위험하기 때문에 private 배치애햐한다
    ssh:
      allow: true 
      publicKeyPath: ./keypair/myeks.pub  #접속할 ssh 키
    availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;, &quot;ap-northeast-2c&quot;]
    iam:
      withAddonPolicies: #IAM 계정 정책
        autoScaler: true
        albIngress: true
        cloudWatch: true #로그를 남기기 위해
        ebs: true

# Fargate Profiles
fargateProfiles:
  - name: fg-1
    selectors:
    - namespace: dev
      labels:
        env: fargate
        
        
# CloudWatch Logging
cloudWatch:
  clusterLogging:
    enableTypes: [&quot;*&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;클러스터 생성이 되어있지 않다면, 다음 명령을 통해 클러스터를 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ eksctl create cluster -f myeks.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;생성 후, 다음과 같이 role을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ eksctl get iamserviceaccount --cluster myeks-custom
NAMESPACE       NAME                            ROLE ARN
kube-system     aws-load-balancer-controller    arn:aws:iam::xxxxxxxxxxxx:role/eksctl-myeks-custom-addon-iamserviceaccount-Role1-8AMSGCDRD4JC
kube-system     aws-node                        arn:aws:iam::xxxxxxxxxxxx:role/eksctl-myeks-custom-addon-iamserviceaccount-Role1-1SIOBJ3B0WDDL
kube-system     cluster-autoscaler              arn:aws:iam::xxxxxxxxxxxx:role/eksctl-myeks-custom-addon-iamserviceaccount-Role1-86B8C7A4ZBUF
kube-system     ebs-csi-controller-sa           arn:aws:iam::xxxxxxxxxxxx:role/eksctl-myeks-custom-addon-iamserviceaccount-Role1-1NNQO9W67BBCY&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음으로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;aws-ebs-csi-driver&lt;span&gt;&amp;nbsp;&lt;/span&gt;애드온을 설치한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;elixir&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ eksctl create addon --name aws-ebs-csi-driver --cluster myeks-custom --service-account-role-arn  arn:aws:iam::[account]:role/eksctl-myeks-custom-addon-iamserviceaccount-Role1-15HLE8HBOD9CN --force&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;ebs-csi-*&lt;span&gt;&amp;nbsp;&lt;/span&gt;파드가 생성된 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ kubectl get po -A
NAMESPACE     NAME                                  READY   STATUS    RESTARTS   AGE
...
kube-system   ebs-csi-controller-84bcf56778-5mbk7   6/6     Running   0          25m
kube-system   ebs-csi-controller-84bcf56778-p7snc   6/6     Running   0          25m
kube-system   ebs-csi-node-fzl2v                    3/3     Running   0          25m
kube-system   ebs-csi-node-qgw25                    3/3     Running   0          25m
kube-system   ebs-csi-node-sd45p                    3/3     Running   0          25m
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;EBS 스냅샷 및 EBS 사이즈 조정&lt;/b&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;등의 기능을 사용할 수 있게 된 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;eksctl&lt;b&gt;을 사용하여 Amazon EBS CSI 추가 기능을 제거하려면,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;클러스터 이름을 수정하고 다음 명령을 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ eksctl delete addon --cluster [클러스터 이름] --name aws-ebs-csi-driver --preserve&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/259</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-AWS-EKS-EBS-CSIContainer-Storage-Interface-Driver-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0#entry259comment</comments>
      <pubDate>Wed, 15 Jun 2022 12:56:03 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] k8s Logging : EFK 개요 및 설치</title>
      <link>https://ssunw.tistory.com/entry/kubernetes-k8s-Logging-EFK-%EA%B0%9C%EC%9A%94-%EB%B0%8F-%EC%84%A4%EC%B9%98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;EFK stack은&lt;span&gt;&amp;nbsp;&lt;/span&gt;Elasticsearch,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Fluent bit(Fluentd),&lt;span&gt;&amp;nbsp;&lt;/span&gt;Kibana&lt;span&gt;&amp;nbsp;&lt;/span&gt;3개의 플랫폼 조합&lt;/b&gt;을 뜻하며,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;클러스터 환경에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;로그의 수집, 검색, 시각화&lt;/b&gt;&lt;/u&gt;를 가능하게 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5b1GG/btrEPOGj7S6/9JxGlts3SEpVmkctZL2gxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5b1GG/btrEPOGj7S6/9JxGlts3SEpVmkctZL2gxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5b1GG/btrEPOGj7S6/9JxGlts3SEpVmkctZL2gxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5b1GG%2FbtrEPOGj7S6%2F9JxGlts3SEpVmkctZL2gxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;403&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그림을 보면 알 수 있듯이,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;각 클러스터에 fluent bit가 daemonset으로 log를 수집&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;elasticsearch는 fluent bit가 수집한 로그를 저장&lt;/b&gt;하며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;요청에 따라 검색&lt;/b&gt;을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;마지막으로 유저가 용이하게 사용할 수 있도록&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;kibana로 시각화 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;FluentBit&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;FluentBit&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Log는&lt;span&gt;&amp;nbsp;&lt;/span&gt;/var/log(시스템 로그)또는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/var/log/container&lt;span&gt;(파드 로그) 또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;/var/log/pods&lt;span&gt;(파드 로그)에 저장된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;(이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;/var/log/container에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;/var/log/pods로 심볼릭 링크가 연결되어있다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Fluent Bit는 이러한 로그 파일들을 수집&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉 Fluent Bit는 로그 수집기이며, 로그 컬렉터, 로그 스트리머 등으로 불린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Fluent Bit는 이렇게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;수집된 로그들을 Elastic Search로 전송&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;Elastic%--Search&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Elastic Search&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;elastic search는 로그 저장소 및&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;검색 엔진&lt;/b&gt;으로,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;데이터를 저장하는 저장소가 있고, 이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;저장소에서 로그를 검색&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;Kibana&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Kibana&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Elastic Search를 Kibana를 통해 시각적으로 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;수집한 로그를 시각화 하는 대시 보드&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;단,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;무인증&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형태이기 때문에 실무에서 사용할 때는 유의해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;☁️ 참고&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;ELK Stack: Elasticsearch + Logstash + Kibana&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;EFK Stack&lt;/b&gt;: Elasticsearch + Fluentd + Kibana&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Elasticsearch + Fluent Bit + Kibana&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Elastic Stack: Elasticsearch + Beat + Kibana&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;메모리 사용량 : Fluent Bit &amp;lt; Fluentd &amp;lt; Logstash&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기능 : Logstash &amp;gt; Fluentd &amp;gt; Fluent Bit&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;fluentd vs fluentbit&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;fluent bit는 자바 런타임이 필요한 JRuby&lt;span&gt;&amp;nbsp;&lt;/span&gt;logstash&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;가벼워진 자바 런타임이 필요하지 않는 CRuby&lt;span&gt;&amp;nbsp;&lt;/span&gt;fluentd에서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;더욱 경량화 된 C로 만들어진 전송에 특화된 로그 수집기이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;fluent bit 공식 사이트에 들어가면 이 둘의 차이를 디테일하게 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2N1vv/btrEObbfJvL/yluQvKgRv31u192IWV3MeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2N1vv/btrEObbfJvL/yluQvKgRv31u192IWV3MeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2N1vv/btrEObbfJvL/yluQvKgRv31u192IWV3MeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2N1vv%2FbtrEObbfJvL%2FyluQvKgRv31u192IWV3MeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;278&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;결론적으로 요약하자면 fluentd는 플러그인 등 확장성이 좋고, fluent bit는 리소스를 적게 차지 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EFK Logging&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kibana: 데이터 시각화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fluent Bit: 로그 컬렉터(수집기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch: 검색 엔진&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fluent Bit 가 노드에서 로그를 수집한 후 Elasticsearch 로 전송, 로그는 Elasticsearch 에 저장되고 Kibana 가 수집한 로그들을 시각화 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Elasticsearch&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://artifacthub.io/packages/helm/elastic/elasticsearch&quot;&gt;https://artifacthub.io/packages/helm/elastic/elasticsearch&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655264875979&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;elasticsearch 7.17.3 &amp;middot; elastic/elastic&quot; data-og-description=&quot;Official Elastic helm chart for Elasticsearch&quot; data-og-host=&quot;artifacthub.io&quot; data-og-source-url=&quot;https://artifacthub.io/packages/helm/elastic/elasticsearch&quot; data-og-url=&quot;https://artifacthub.io/packages/helm/elastic/elasticsearch&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jLoyk/hyOMDfVcza/OL8oNkK7xeWOh2uARtnGlk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://artifacthub.io/packages/helm/elastic/elasticsearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://artifacthub.io/packages/helm/elastic/elasticsearch&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jLoyk/hyOMDfVcza/OL8oNkK7xeWOh2uARtnGlk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;elasticsearch 7.17.3 &amp;middot; elastic/elastic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Official Elastic helm chart for Elasticsearch&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;artifacthub.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;먼저 helm으로 레포지토리(저장소)를 추가하고, 업데이트한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;vim&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;helm repo add elastic https://helm.elastic.co
helm repo update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;해당 레포지토리에서 elasticsearch와 kibana를 설치할 예정이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;다음으로 몇가지를 수정(사용자화)하기 위해 values를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;es-value.yaml&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;로 받아온다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;helm show values elastic/elasticsearch &amp;gt; es-value.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;테스트를 위한 것이기 때문에, 복제본의 개수와 리소스를 줄여서 다음과 같이 수정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;es-value.yml&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;...
18 replicas: 1
19 minimumMasterNodes: 1
...
80 resources:
81   requests:
82     cpu: &quot;500m&quot;
83     memory: &quot;1Gi&quot;
84   limits:
85     cpu: &quot;500m&quot;
86     memory: &quot;1Gi&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logging&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;namespace를 생성하고, 해당 ns에 elasticsearch를 설치한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl create ns logging
helm install elastic elastic/elasticsearch -f es-value.yml -n logging
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fluent Bit&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/fluent/fluent-bit-kubernetes-logging&quot;&gt;https://github.com/fluent/fluent-bit-kubernetes-logging&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655264937730&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - fluent/fluent-bit-kubernetes-logging: Fluent Bit Kubernetes Daemonset&quot; data-og-description=&quot;Fluent Bit Kubernetes Daemonset. Contribute to fluent/fluent-bit-kubernetes-logging development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/fluent/fluent-bit-kubernetes-logging&quot; data-og-url=&quot;https://github.com/fluent/fluent-bit-kubernetes-logging&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cIpFqn/hyOMGjpn0U/VgQEa8xAprkN1kyTGO2dpK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/fluent/fluent-bit-kubernetes-logging&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/fluent/fluent-bit-kubernetes-logging&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cIpFqn/hyOMGjpn0U/VgQEa8xAprkN1kyTGO2dpK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - fluent/fluent-bit-kubernetes-logging: Fluent Bit Kubernetes Daemonset&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Fluent Bit Kubernetes Daemonset. Contribute to fluent/fluent-bit-kubernetes-logging development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;Fluent Bit는 Helm차트가 있긴 하지만 버전이 낮으므로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;GitHub&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;에서 clone 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;git clone https://github.com/fluent/fluent-bit-kubernetes-logging.git&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;Service Account와 Role, RoleBinding을 설치한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;cd fluent-bit-kubernetes-logging

kubectl create -f fluent-bit-service-account.yaml -n logging
kubectl create -f fluent-bit-role-1.22.yaml -n logging
kubectl create -f fluent-bit-role-binding-1.22.yaml -n logging&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;fluent-bit-configmap.yaml는 공통적으로 적용되는 설정파일이고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;fluent-bit-ds-minikube.yaml는&lt;span&gt;&amp;nbsp;&lt;/span&gt;minikube에서 사용하는 것이고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일반은&lt;span&gt;&amp;nbsp;&lt;/span&gt;fluent-bit-ds.yaml파일(데몬셋)이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;vi fluent-bit-kubernetes-logging/output/elasticsearch/fluent-bit-ds.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Fluent가 Elastic Search로 로그를 보낼 수 있도록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음과 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;fluent-bit-ds.yaml을 Elastic Search 서비스 이름에 맞춰서 수정하고, 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;32         - name: FLUENT_ELASTICSEARCH_HOST
33           value: &quot;elasticsearch-master&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;cd fluent-bit-kubernetes-logging/output/elasticsearch

kubectl create -f fluent-bit-configmap.yaml -n logging
kubectl create -f fluent-bit-ds.yaml -n logging&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;minikube 를 사용할 경우 fluent-bit-ds.yaml 대신 fluent-bit-ds-minikube.yaml 을 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kibana&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;kibana는 앞서 설치한 elasticsearch와 동일한 레포지토리에서 설치한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;먼저 다음과 같이 values를&lt;span&gt;&amp;nbsp;&lt;/span&gt;kibana-value.yaml로 받아오고, 리소스를 수정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또한, 외부에 노출시키기 위해 서비스타입을 LoadBalancer로 수정한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;helm show values elastic/kibana &amp;gt; kibana-value.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kibana-value.yml&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;...
49 resources:
50   requests:
51     cpu: &quot;500m&quot;
52     memory: &quot;0.7Gi&quot;
53   limits:
54     cpu: &quot;500m&quot;
55     memory: &quot;0.7Gi&quot;
...
119 service:
120   type: LoadBalancer
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;helm install kibana elastic/kibana -f kibana-value.yml -n logging
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%EC%-E%--%EB%-F%--%--%ED%--%--%EC%-D%B-&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;작동 확인&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;설치한 것들이 잘 작동되는지 테스트를 진행해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ kubectl port-forward -n logging elasticsearch-master-0 9200:9200&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;터미널을 하나 더 열어서&lt;span&gt;&amp;nbsp;&lt;/span&gt;curl명령어로 확인했을 때,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음과 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;tagline이&lt;span&gt;&amp;nbsp;&lt;/span&gt;You Know, for Search라고 출력되면 정상적으로 작동되고 있다는 뜻이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;elixir&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ curl localhost:9200
{
  &quot;name&quot; : &quot;elasticsearch-master-0&quot;,
  &quot;cluster_name&quot; : &quot;elasticsearch&quot;,
  &quot;cluster_uuid&quot; : &quot;XXXXXXXXXXXXXXXXXX&quot;,
  &quot;version&quot; : {
    &quot;number&quot; : &quot;7.17.3&quot;,
    &quot;build_flavor&quot; : &quot;default&quot;,
    &quot;build_type&quot; : &quot;docker&quot;,
    &quot;build_hash&quot; : &quot;XXXXXXXXXXXXXXXXXX&quot;,
    &quot;build_date&quot; : &quot;XXXXXXXXXXXXXXXXXX&quot;,
    &quot;build_snapshot&quot; : false,
    &quot;lucene_version&quot; : &quot;8.11.1&quot;,
    &quot;minimum_wire_compatibility_version&quot; : &quot;6.8.0&quot;,
    &quot;minimum_index_compatibility_version&quot; : &quot;6.0.0-beta1&quot;
  },
  &quot;tagline&quot; : &quot;You Know, for Search&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ curl localhost:9200/_cat/indices
green  open .kibana_task_manager_7.17.3_001 XXXXXXXXXXXXXXXXXX 1 0   17 3804 538.4kb 538.4kb
green  open .apm-custom-link                XXXXXXXXXXXXXXXXXX 1 0    0    0    226b    226b
green  open .apm-agent-configuration        XXXXXXXXXXXXXXXXXX 1 0    0    0    226b    226b
yellow open logstash-2022.06.01             XXXXXXXXXXXXXXXXXX 1 1 4676    0  15.8mb  15.8mb
green  open .kibana_7.17.3_001              XXXXXXXXXXXXXXXXXX 1 0   22    4   4.7mb   4.7mb&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음으로 kibana에 접속한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ kubectl get svc -n logging
NAME                            TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)             AGE
elasticsearch-master            ClusterIP      10.233.21.152   &amp;lt;none&amp;gt;            9200/TCP,9300/TCP   27m
elasticsearch-master-headless   ClusterIP      None            &amp;lt;none&amp;gt;            9200/TCP,9300/TCP   27m
kibana-kibana                   LoadBalancer   10.233.19.27    192.168.100.240   5601:31535/TCP      18m&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;kibana의 EXTERNAL-IP와 PORT&lt;span&gt;&amp;nbsp;&lt;/span&gt;192.168.100.240:5601&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 접속을 시도하면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음과 같이 정상적으로 접속되는 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buXiuq/btrER2cP2Nu/GiNhcQnWKhAe1DkCjGCKj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buXiuq/btrER2cP2Nu/GiNhcQnWKhAe1DkCjGCKj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buXiuq/btrER2cP2Nu/GiNhcQnWKhAe1DkCjGCKj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuXiuq%2FbtrER2cP2Nu%2FGiNhcQnWKhAe1DkCjGCKj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;383&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;%E-%-C%--%EF%B-%-F%--EFK%--%EC%--%B-%ED%-C%--%ED%--%--%EA%B-%B-&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;EFK&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;세팅하기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Explore on my own을 클릭하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;[Management] - [Stack Management]를 클릭한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p21Pe/btrEPb2XiKo/77Okls44kv7xb0FaQs7CwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p21Pe/btrEPb2XiKo/77Okls44kv7xb0FaQs7CwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p21Pe/btrEPb2XiKo/77Okls44kv7xb0FaQs7CwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp21Pe%2FbtrEPb2XiKo%2F77Okls44kv7xb0FaQs7CwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;380&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;[Kibana] - [Index Patterns] - [Create index pattern]을 클릭한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Elastic Search에서 index는 database이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc6ngK/btrEOH1ZC4Y/4vNT408E1kWey0k8RyK1y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc6ngK/btrEOH1ZC4Y/4vNT408E1kWey0k8RyK1y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc6ngK/btrEOH1ZC4Y/4vNT408E1kWey0k8RyK1y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc6ngK%2FbtrEOH1ZC4Y%2F4vNT408E1kWey0k8RyK1y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;380&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;logstash-xxxx.xx.xx&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일을 확인할 수 있는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이것은 Fluent Bit가 Elastic Search에 전송한 로그파일로, 하루에 1개씩 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Name에 모든 로그 파일을 수집하도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;logstash-*를 입력하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Timestamp field는&lt;span&gt;&amp;nbsp;&lt;/span&gt;@timestamp를 선택한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2ECw1/btrEOtoHK2Z/vakFNpvyHqgJuFwTkxFf81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2ECw1/btrEOtoHK2Z/vakFNpvyHqgJuFwTkxFf81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2ECw1/btrEOtoHK2Z/vakFNpvyHqgJuFwTkxFf81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2ECw1%2FbtrEOtoHK2Z%2FvakFNpvyHqgJuFwTkxFf81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;380&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;[Analytics] - [Discover]를 클릭한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zbUfu/btrEOHVf4XA/vGlOPGgpPf6M9qzb7EycM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zbUfu/btrEOHVf4XA/vGlOPGgpPf6M9qzb7EycM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zbUfu/btrEOHVf4XA/vGlOPGgpPf6M9qzb7EycM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzbUfu%2FbtrEOHVf4XA%2FvGlOPGgpPf6M9qzb7EycM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;379&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그러면 다음과 같이 로그들을 확인할 수 있다. 여기서 KQP는 Kibana Query Language이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oFdIg/btrER6lZ0vo/Y0woOGqYoXfq0VqyEe82m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oFdIg/btrER6lZ0vo/Y0woOGqYoXfq0VqyEe82m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oFdIg/btrER6lZ0vo/Y0woOGqYoXfq0VqyEe82m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoFdIg%2FbtrER6lZ0vo%2FY0woOGqYoXfq0VqyEe82m0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;380&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Search 창에 원하는 키워드를 입력하여 검색이 가능하다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/258</guid>
      <comments>https://ssunw.tistory.com/entry/kubernetes-k8s-Logging-EFK-%EA%B0%9C%EC%9A%94-%EB%B0%8F-%EC%84%A4%EC%B9%98#entry258comment</comments>
      <pubDate>Wed, 15 Jun 2022 12:51:56 +0900</pubDate>
    </item>
    <item>
      <title>aws 로드밸랜서 등록 후 503 Service Temporarily Unavailable 발생</title>
      <link>https://ssunw.tistory.com/entry/aws-%EB%A1%9C%EB%93%9C%EB%B0%B8%EB%9E%9C%EC%84%9C-%EB%93%B1%EB%A1%9D-%ED%9B%84-503-Service-Temporarily-Unavailable-%EB%B0%9C%EC%83%9D</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;aws 로드밸랜서 등록후 나오는 503 에러 해결방법&lt;br&gt;&amp;nbsp;&lt;br&gt;보통은 로드밸랜서의 타겟 그룹에 등록된 타겟이 없을때 혹은 타겟 그룹이 unhealthy 상태일 때 발생한다고 생각하면 편하거. &lt;br&gt;&amp;nbsp;&lt;br&gt;인스턴스 기반일 경우 타겟 그룹에서 사용할 ec2 인스턴스를 등록하거나 헬스체크를 통과했는지 확인한다.&lt;br&gt;&lt;br&gt;쿠버네티스 환경에서도 상동하다.&lt;br&gt;&amp;nbsp;&lt;br&gt;참조: &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html#http-503-issues&quot; target=&quot;_self&quot;&gt;&lt;span&gt;docs.aws.amazon.com/ko_kr/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html#http-503-issues&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>AWS/Basic</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/257</guid>
      <comments>https://ssunw.tistory.com/entry/aws-%EB%A1%9C%EB%93%9C%EB%B0%B8%EB%9E%9C%EC%84%9C-%EB%93%B1%EB%A1%9D-%ED%9B%84-503-Service-Temporarily-Unavailable-%EB%B0%9C%EC%83%9D#entry257comment</comments>
      <pubDate>Wed, 15 Jun 2022 12:30:50 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Helm Prometheus Stack 설치</title>
      <link>https://ssunw.tistory.com/entry/Helm</link>
      <description>&lt;h1&gt;HELM&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://helm.sh/docs/&quot;&gt;https://helm.sh/docs/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 패키지 매니저&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Helm Charts: 차트, 패키지&lt;/li&gt;
&lt;li&gt;Repository: 차트 저장소&lt;/li&gt;
&lt;li&gt;Release: 쿠버네티스 오브젝트 리소스 (패키지 &amp;rarr; 클러스터에 생성한 인스턴스)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Helm 저장소는 다양하게 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC 로 클라이언트와 서버가 통신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tiller 에 의해 다운받은 패키지를 API server 를 통해 설치를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v3 에서 Tiller 를 없앴다. 보안상 문제가 생길 수도 있기 때문, 그래서 v3 에서는 클라이언트가 바론 API server 에 직접 접근하는 방식을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;helm 설치(debian/ubuntu 계열)&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg &amp;gt; /dev/null
sudo apt-get install apt-transport-https --yes
echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all
sudo apt-get update
sudo apt-get install helm main&quot; | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Helm Chart 검색 사이트: &lt;a href=&quot;https://artifacthub.io/&quot;&gt;https://artifacthub.io/&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차트 구조&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;&amp;lt;Chart Name&amp;gt;/
	chart.yml
	values.yml
	templates/
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;chart.yml: 차트의 메타데이터&lt;/li&gt;
&lt;li&gt;values.yml: 패키지를 커스터마이즈/사용자화(value)&lt;/li&gt;
&lt;li&gt;templates: YAML 오브젝트 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;helm 사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;artifacthub 검색&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;helm search hub &amp;lt;PATTERN&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소 추가&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;helm repo add &amp;lt;저장소&amp;gt; &amp;lt;레포지토리_주소&amp;gt;

helm repo add wordpress https://charts.bitnami.com/bitnami&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소 검색&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;helm search repo &amp;lt;저장소&amp;gt;/&amp;lt;PATTERN&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차트 설치&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;helm install &amp;lt;릴리즈_이름&amp;gt; &amp;lt;저장소&amp;gt;/&amp;lt;차트_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈 확인&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;helm list
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈 제거&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;helm uninstall &amp;lt;릴리즈_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차트 정보 확인&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;helm show readme &amp;lt;저장소&amp;gt;/&amp;lt;차트&amp;gt;
helm show chart &amp;lt;저장소&amp;gt;/&amp;lt;차트&amp;gt;
helm show values &amp;lt;저장소&amp;gt;/&amp;lt;차트&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차트 사용자화&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;helm install mywp bitnami/wordpress --set replicaCount=2
helm install mywp bitnami/wordpress --set replicaCount=2 --set service.type=NodePort
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈 업그레이드&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;helm show value &amp;lt;저장소&amp;gt;/&amp;lt;차트&amp;gt; &amp;gt; wp-value.yml
vi wp-value.yml
helm upgrade &amp;lt;릴리즈_이름&amp;gt; &amp;lt;저장소&amp;gt;/&amp;lt;차트&amp;gt; -f wp-value.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈 업그레이드 히스토리 확인&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;helm history &amp;lt;릴리즈_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈 롤백&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;helm rollback &amp;lt;릴리즈_이름&amp;gt; &amp;lt;REVISION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Monitoring &amp;amp; Logging&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Prometheus Monitoring&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prometheus.io/docs/introduction/overview/&quot;&gt;https://prometheus.io/docs/introduction/overview/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로메테우스 아키텍쳐&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lXvMO/btrDnp9NNvg/BZja89jRgBMnqtCttTS4t1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lXvMO/btrDnp9NNvg/BZja89jRgBMnqtCttTS4t1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lXvMO/btrDnp9NNvg/BZja89jRgBMnqtCttTS4t1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlXvMO%2FbtrDnp9NNvg%2FBZja89jRgBMnqtCttTS4t1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;768&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus targets(Jobs/exporters): 앱, 프로세스 등 모니터링하려는 대상의 데이터를 exporters 파드에서 수집한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pushgateway: 생명주기가 짧은 녀석들의 데이터들은 여기서 보관한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;discover targets: target 을 찾기 위해 프로메테우스 서버에서 대상을 특정할 수 있어야 한다.(api-server 에 질의 한다.-pod 목록, node 목록, svc 목록 등&amp;hellip;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSDB(Time Series DB): 시계열 DB, 관계형 데이터베이스가 아니라 시간대별로 데이터를 저장하기 좋게 만든 DB 를 시계열 DB 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP server: 대시보드. UI, UX 가 엄청 끔찍해서 거의 사용하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PromQL: SQL 같은 전용 언어를 만들었다. TSDB 에서 데이터를 검색할 수 있는 전용 언어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AlertManager: 리소스를 모니터링 하다가 특정 임계값을 넘으면 슬랙, 이메일 등으로 알림을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana: TSDB 의 시계열 데이터 PromQL 을 통해 쿼리하여 프로덕션 레벨에서 모니터링하는 도구&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Helm kube-prometheus-stack 차트 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브: &lt;a href=&quot;https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack&quot;&gt;https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prom-values.yml&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;grafana:
  service:
    type: LoadBalancer
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;kubectl create ns monitor
helm install prom prometheus-community/kube-prometheus-stack -f prom-values.yaml -n monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;helm list -n monitor
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
prom    monitor         1               2022-05-27 05:49:29.781975505 +0000 UTC deployed        prometheus-15.9.0       2.34.0
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;kubectl get all -n monitor
NAME                                                READY   STATUS    RESTARTS   AGE
pod/prom-kube-state-metrics-7b78c9f88-mf84w         1/1     Running   0          84s
pod/prom-prometheus-alertmanager-55b97bcd64-rwpct   2/2     Running   0          84s
pod/prom-prometheus-node-exporter-28t5x             1/1     Running   0          85s
pod/prom-prometheus-node-exporter-72sbt             1/1     Running   0          85s
pod/prom-prometheus-node-exporter-rkcx5             1/1     Running   0          85s
pod/prom-prometheus-pushgateway-67f89c46f7-4psh9    1/1     Running   0          85s
pod/prom-prometheus-server-c5cbb7c66-x76b6          2/2     Running   0          84s

NAME                                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/prom-kube-state-metrics         ClusterIP   10.233.17.58    &amp;lt;none&amp;gt;        8080/TCP   85s
service/prom-prometheus-alertmanager    ClusterIP   10.233.35.47    &amp;lt;none&amp;gt;        80/TCP     85s
service/prom-prometheus-node-exporter   ClusterIP   10.233.3.31     &amp;lt;none&amp;gt;        9100/TCP   85s
service/prom-prometheus-pushgateway     ClusterIP   10.233.47.169   &amp;lt;none&amp;gt;        9091/TCP   85s
service/prom-prometheus-server          ClusterIP   10.233.55.155   &amp;lt;none&amp;gt;        80/TCP     85s

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

NAME                                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/prom-kube-state-metrics        1/1     1            1           85s
deployment.apps/prom-prometheus-alertmanager   1/1     1            1           85s
deployment.apps/prom-prometheus-pushgateway    1/1     1            1           85s
deployment.apps/prom-prometheus-server         1/1     1            1           85s

NAME                                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/prom-kube-state-metrics-7b78c9f88         1         1         1       85s
replicaset.apps/prom-prometheus-alertmanager-55b97bcd64   1         1         1       85s
replicaset.apps/prom-prometheus-pushgateway-67f89c46f7    1         1         1       85s
replicaset.apps/prom-prometheus-server-c5cbb7c66          1         1         1       85s
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;prom-grafana&lt;span&gt;&amp;nbsp;&lt;/span&gt;로드밸런서의 EXTERNAL-IP로 접속하면 다음과 같은 화면을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6RwaA/btrEPb9FLFt/jkiv3jlj0gUo6Z5jBUOTPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6RwaA/btrEPb9FLFt/jkiv3jlj0gUo6Z5jBUOTPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6RwaA/btrEPb9FLFt/jkiv3jlj0gUo6Z5jBUOTPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6RwaA%2FbtrEPb9FLFt%2Fjkiv3jlj0gUo6Z5jBUOTPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;430&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;패스워드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/prometheus-community/helm-charts/blob/main/charts/kube-prometheus-stack/values.yaml&quot;&gt;values.yaml&lt;/a&gt;에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;ID: admin&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;PWD: prom-operator&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;로그인 후 다음과 같이 클러스터의 CPU 사용량 등을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WIqxE/btrENgYCPkO/JP9HdDKpzYOmKqxGEo5a9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WIqxE/btrENgYCPkO/JP9HdDKpzYOmKqxGEo5a9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WIqxE/btrENgYCPkO/JP9HdDKpzYOmKqxGEo5a9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWIqxE%2FbtrENgYCPkO%2FJP9HdDKpzYOmKqxGEo5a9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;435&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또한 다음과 같이 네임스페이스를 선택해서 확인하는 것도 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vLyfa/btrEOaDmZHo/DSB9t0nQTl2m0qwaWtqg7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vLyfa/btrEOaDmZHo/DSB9t0nQTl2m0qwaWtqg7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vLyfa/btrEOaDmZHo/DSB9t0nQTl2m0qwaWtqg7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvLyfa%2FbtrEOaDmZHo%2FDSB9t0nQTl2m0qwaWtqg7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;434&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/255</guid>
      <comments>https://ssunw.tistory.com/entry/Helm#entry255comment</comments>
      <pubDate>Sun, 29 May 2022 15:30:34 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] 인증</title>
      <link>https://ssunw.tistory.com/entry/%EC%9D%B8%EC%A6%9D</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인증&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/docs/reference/access-authn-authz/authentication/&quot;&gt;https://kubernetes.io/docs/reference/access-authn-authz/authentication/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스의 사용자&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Service Account(SA): 쿠버네티스가 관리하는 SA 사용자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;Pod 가 사용하는 계정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Normal User: 일반 사용자(쿠버네티스가 관리하지 않는다.)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 사용하는 계정이다.&lt;/li&gt;
&lt;li&gt;Pod 는 사용하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 방법:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;x509 인증서&lt;/li&gt;
&lt;li&gt;static token
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bearer Token
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http 헤더에 인증 정보가 있는 토큰을 담아서 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Service Account Token
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSON Web Token: JWT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OpenID Connect(OIDC)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 인증 표준화 인터페이스&lt;/li&gt;
&lt;li&gt;okta 인증 서버 서비스가 있다.&lt;/li&gt;
&lt;li&gt;OAuth Provider: EKS 에서 IAM 계정을 사용하는 방식이 바로 OIDC 를 사용한 방식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod 는 SA 계정을 사용하고 SA 계정에 할당받은 Token 을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자(나)는 x509 를 사용하여 kubectl 을 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RBAC: Role Based Access Control&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/docs/reference/access-authn-authz/rbac/&quot;&gt;https://kubernetes.io/docs/reference/access-authn-authz/rbac/&lt;/a&gt;, &lt;a href=&quot;https://kubernetes.io/ko/docs/reference/access-authn-authz/authorization/&quot;&gt;https://kubernetes.io/ko/docs/reference/access-authn-authz/authorization/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 기반 접근 제어(RBAC, Role-based access control)는 클러스터 내 개별 사용자의 역할을 기반으로 컴퓨터나 네트워크 리소스에 대한 접근을 규제하는 방식이다. 이 맥락에서 접근은 개별 사용자가 파일을 보거나 만들거나 수정하는 것과 같은 특정 작업을 수행할 수 있는 능력이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Role: 특정 Name Space 내에서만 권한 부여 가능&lt;/li&gt;
&lt;li&gt;ClusterRole: 글로벌하게 권한 부여 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;view: 읽을 수 있는 권한&lt;/li&gt;
&lt;li&gt;edit: 생성/삭제/변경할 수 있는 권한&lt;/li&gt;
&lt;li&gt;admin: 모든 것 관리(RBAC의 ClusterRole 제외)&lt;/li&gt;
&lt;li&gt;cluster-admin: 모든 것 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RoleBinding
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 Name Space 내에서 Role 과 SA/User 사용자를 연결시켜 주는 것이 RoleBinding 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ClusterRoleBinding
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ClusterRole 과 SA/User 사용자를 연결시켜주는 것이 ClusterRoleBinding 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diUjGN/btrDqVNoEm5/11ocbkFEKlhrVCC9FetOnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diUjGN/btrDqVNoEm5/11ocbkFEKlhrVCC9FetOnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diUjGN/btrDqVNoEm5/11ocbkFEKlhrVCC9FetOnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiUjGN%2FbtrDqVNoEm5%2F11ocbkFEKlhrVCC9FetOnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;311&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ kubectl get po -v=7
I0526 06:31:24.674874  163632 loader.go:372] Config loaded from file:  /home/vagrant/.kube/config
I0526 06:31:24.695368  163632 round_trippers.go:432] GET &amp;lt;https://127.0.0.1:6443/api/v1/namespaces/default/pods?limit=500&amp;gt;
I0526 06:31:24.695939  163632 round_trippers.go:438] Request Headers:
I0526 06:31:24.696880  163632 round_trippers.go:442]     Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json
I0526 06:31:24.697384  163632 round_trippers.go:442]     User-Agent: kubectl/v1.22.8 (linux/amd64) kubernetes/7061dbb
I0526 06:31:24.713208  163632 round_trippers.go:457] Response Status: 200 OK in 15 milliseconds
NAME                                      READY   STATUS    RESTARTS        AGE
nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   5 (4h25m ago)   2d1h
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-v 옵션을 사용해서 Request, Response 를 자세하게 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl create 커맨드 &amp;rarr; api-server 에 HTTP POST 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl get, list, watch 커맨드 &amp;rarr; api-server 에 HTTP GET 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl update 커맨드 &amp;rarr; api-server 에 HTTP PUT 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl patch 커맨드 &amp;rarr; api-server 에 HTTP PATCH 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl delete, deletecollection 커맨드 &amp;rarr; api-server 에 HTTP DELTE 요청&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Role 예제&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [&quot;&quot;] # &quot;&quot; indicates the core API group
  resources: [&quot;pods&quot;]
  verbs: [&quot;get&quot;, &quot;watch&quot;, &quot;list&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cluster Role 예제&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # &quot;namespace&quot; omitted since ClusterRoles are not namespaced
  name: secret-reader
rules:
- apiGroups: [&quot;&quot;]
  #
  # at the HTTP level, the name of the resource for accessing Secret
  # objects is &quot;secrets&quot;
  resources: [&quot;secrets&quot;]
  verbs: [&quot;get&quot;, &quot;watch&quot;, &quot;list&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RoleBinding 예제&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows &quot;jane&quot; to read pods in the &quot;default&quot; namespace.
# You need to already have a Role named &quot;pod-reader&quot; in that namespace.
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
# You can specify more than one &quot;subject&quot;
- kind: User 
  name: jane # .kube/config 에 users.user.name 에 jane 이 있어야 한다.
  apiGroup: rbac.authorization.k8s.io
roleRef:
  # &quot;roleRef&quot; specifies the binding to a Role / ClusterRole
  kind: Role #this must be Role or ClusterRole
  name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ClusterRoleBinding 예제&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
# This cluster role binding allows anyone in the &quot;manager&quot; group to read secrets in any namespace.
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name: manager # Name is case sensitive
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Service Account(SA)&lt;/h2&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;kubectl craete sa &amp;lt;SA_NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ kubectl create sa myuser1
serviceaccount/myuser1 created

$ kubectl describe sa myuser1
Name:                myuser1
Namespace:           default
Labels:              &amp;lt;none&amp;gt;
Annotations:         &amp;lt;none&amp;gt;
Image pull secrets:  &amp;lt;none&amp;gt;
Mountable secrets:   myuser1-token-lgpsj
Tokens:              myuser1-token-lgpsj
Events:              &amp;lt;none&amp;gt;

$ kubectl get secret
NAME                                 TYPE                                  DATA   AGE
default-token-jw8gl                  kubernetes.io/service-account-token   3      2d4h
myuser1-token-lgpsj                  kubernetes.io/service-account-token   3      14s
nfs-client-provisioner-token-bgdtf   kubernetes.io/service-account-token   3      2d1h
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용자 생성을 위한 x509 인증서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Private Key 생성&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;openssl genrsa -out myuser.key 2048
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x509 인증서 요청 생성&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;openssl req -new -key myuser.key -out myuser.csr -subj &quot;/CN=myuser&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/CN=myuser 을 작성한 이유는 인증서의 Common Name 과 ~/.kube/config 의 &lt;a href=&quot;http://users.name&quot;&gt;users.name&lt;/a&gt; 과 똑같아야 한다. 그 때문에 /CN=myuser 를 하면 간단하게 Common Name 을 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서 base64 인코딩&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;cat myuser.crt | base64 | tr -d &quot;\\n&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;csr.yml&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: myuser-csr
spec:
  usages:
  - client auth
  signerName: kubernetes.io/kube-apiserver-client
  request: LS0tLS1CRUdJTiB
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;kubectl create -f csr.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get csr
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태: Pending&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;kubectl certificate approve myuser-csr
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get csr
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태: Apporved, Issued&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get csr myuser-csr -o yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec.status.certificate 확인&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get csr myuser-csr -o jsonpath='{.status.certificate}' | base64 -d &amp;gt; myuser.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;openssl x509 -in myuser.crt --text
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubeconfig 사용자 생성&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl config set-credentials myuser --client-certificate=myuser.crt --client-key=myuser.key --embed-certs=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubeconfig 컨텍스트 생성&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl config set-context myuser@cluster.local --cluster=cluster.local --user=myuser --namespace
=default
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;kubectl config get-users
kubectl config get-clusters
kubectl config get-contexts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl config use-context myuser@cluster.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ClusterRole view 권한을 myuser 사용자에게 부여&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: myuser-view-crb
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: myuser
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;kubectl create -f myuser-view-crb.yml

kubectl describe clusterrolebindings.rbac.authorization.k8s.io myuser-view-crb
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl config use-context myuser@cluster.local
Switched to context &quot;myuser@cluster.local&quot;.

kubectl config get-contexts
CURRENT   NAME                             CLUSTER         AUTHINFO           NAMESPACE
          kubernetes-admin@cluster.local   cluster.local   kubernetes-admin
*         myuser@cluster.local             cluster.local   myuser             default

kubectl get pods
NAME                                      READY   STATUS    RESTARTS      AGE
nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   6 (23m ago)   2d19h
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl auth can-i get pod
yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 새로운 ClusterRole 만들고 싶을 때는 아래처럼 하면 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  namespace: default
  name: myuser-role
rules:
- apiGroups: [&quot;*&quot;] # &quot;&quot; indicates the core API group
  resources: [&quot;secrets&quot;]
  verbs: [&quot;get&quot;, &quot;watch&quot;, &quot;list&quot;]
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/254</guid>
      <comments>https://ssunw.tistory.com/entry/%EC%9D%B8%EC%A6%9D#entry254comment</comments>
      <pubDate>Sun, 29 May 2022 15:28:49 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Shedulling</title>
      <link>https://ssunw.tistory.com/entry/Shedulling</link>
      <description>&lt;h1&gt;Pod Shedulling&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/scheduling-eviction/assign-pod-node/&quot;&gt;https://kubernetes.io/ko/docs/concepts/scheduling-eviction/assign-pod-node/&lt;/a&gt;, &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/scheduling-eviction/kube-scheduler/&quot;&gt;https://kubernetes.io/ko/docs/concepts/scheduling-eviction/kube-scheduler/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에서&amp;nbsp;스케줄링&amp;nbsp;은&amp;nbsp;Kubelet이 파드를 실행할 수 있도록&amp;nbsp;파드가&amp;nbsp;노드에 적합한지 확인하는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄러는 노드가 할당되지 않은 새로 생성된 파드를 감시한다. 스케줄러가 발견한 모든 파드에 대해 스케줄러는 해당 파드가 실행될 최상의 노드를 찾는 책임을 진다. 스케줄러는 아래 설명된 스케줄링 원칙을 고려하여 이 배치 결정을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드가 특정 노드에 배치되는 이유를 이해하려고 하거나 사용자 정의된 스케줄러를 직접 구현하려는 경우 이 페이지를 통해서 스케줄링에 대해 배울 수 있을 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;nodeName&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodeName&amp;nbsp;은 가장 간단한 형태의 노트 선택 제약 조건이지만, 한계로 인해 일반적으로는 사용하지 않는다.&amp;nbsp;nodeName&amp;nbsp;은 PodSpec의 필드이다. 만약 비어있지 않으면, 스케줄러는 파드를 무시하고 명명된 노드에서 실행 중인 kubelet이 파드를 실행하려고 한다. 따라서 만약 PodSpec에&amp;nbsp;nodeName&amp;nbsp;가 제공된 경우, 노드 선택을 위해 위의 방법보다 우선한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodeName&amp;nbsp;을 사용해서 노드를 선택할 때의 몇 가지 제한은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 명명된 노드가 없으면, 파드가 실행되지 않고 따라서 자동으로 삭제될 수 있다.&lt;/li&gt;
&lt;li&gt;만약 명명된 노드에 파드를 수용할 수 있는 리소스가 없는 경우 파드가 실패하고, 그 이유는 다음과 같이 표시된다. 예: OutOfmemory 또는 OutOfcpu.&lt;/li&gt;
&lt;li&gt;클라우드 환경의 노드 이름은 항상 예측 가능하거나 안정적인 것은 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에&amp;nbsp;nodeName&amp;nbsp;필드를 사용하는 파드 설정 파일 예시가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-rs-nn.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-nn
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      nodeName: node2
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;nodeSelector&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodeSelector&amp;nbsp;는 가장 간단하고 권장되는 노드 선택 제약 조건의 형태이다.&amp;nbsp;nodeSelector&amp;nbsp;는 PodSpec의 필드이다. 이는 키-값 쌍의 매핑으로 지정한다. 파드가 노드에서 동작할 수 있으려면, 노드는 키-값의 쌍으로 표시되는 레이블을 각자 가지고 있어야 한다(이는 추가 레이블을 가지고 있을 수 있다). 일반적으로 하나의 키-값 쌍이 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;0 단계: 사전 준비&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시는 쿠버네티스 파드에 대한 기본적인 이해를 하고 있고&amp;nbsp;&lt;a href=&quot;https://kubernetes.io/ko/docs/setup/&quot;&gt;쿠버네티스 클러스터가 설정&lt;/a&gt;되어 있다고 가정한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1 단계: 노드에 레이블 붙이기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl get nodes&amp;nbsp;를 실행해서 클러스터 노드 이름을 가져온다. 이 중에 레이블을 추가하기 원하는 것 하나를 선택한 다음에&amp;nbsp;kubectl label nodes &amp;lt;노드 이름&amp;gt; &amp;lt;레이블 키&amp;gt;=&amp;lt;레이블 값&amp;gt;&amp;nbsp;을 실행해서 선택한 노드에 레이블을 추가한다. 예를 들어 노드의 이름이 'kubernetes-foo-node-1.c.a-robinson.internal' 이고, 원하는 레이블이 'disktype=ssd' 라면,&amp;nbsp;kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssd&amp;nbsp;를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl get nodes --show-labels&amp;nbsp;를 다시 실행해서 노드가 현재 가진 레이블을 확인하여, 이 작업을 검증할 수 있다. 또한&amp;nbsp;kubectl describe node &quot;노드 이름&quot;&amp;nbsp;을 사용해서 노드에 주어진 레이블의 전체 목록을 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2 단계: 파드 설정에 nodeSelector 필드 추가하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하고자 하는 파드의 설정 파일을 가져오고, 이처럼 nodeSelector 섹션을 추가한다. 예를 들어 이것이 파드 설정이라면,&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get nodes --show-labels
kubectl label nodes k8s-node1 gpu=highend
kubectl label nodes k8s-node2 gpu=lowend
kubectl label nodes k8s-node3 gpu=lowend
kubectl get nodes -L gpu
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-ns
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      nodeSelector:
        gpu: lowend
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get po -o wide
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Affinity(선호도)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조: &lt;a href=&quot;https://waspro.tistory.com/582&quot;&gt;https://waspro.tistory.com/582&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;affinity
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pod&lt;/li&gt;
&lt;li&gt;node&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;anti-affinity
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pod&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;affinity는 뜻 그대로 유연한 Scheduling이 가능하도록 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 살펴본 nodeSelector의 경우 label naming을 기준으로 Pod를 특정 노드 또는 특정 노드 그룹에 배포하는 방식을 사용하였다면, affinity는 다양한 조건을 제시할 수 있고 유연하게 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;affinity는 크게 노드를 기준으로 하는 node affinity와 pod 레벨에서 label을 관리할 수 있는 pod affinity으로 구분할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node affinity가 node level에서 배치를 관리하는다는점에서 nodeSelector와는 비슷하지만 다양한 조건을 명시할 수 있다는 유연성에서 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node affinity는 크게 아래와 같이 4가지로 분류할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/li&gt;
&lt;li&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/li&gt;
&lt;li&gt;requiredDuringSchedulingRequiredDuringExecution&lt;/li&gt;
&lt;li&gt;preferredDuringSchedulingRequiredDuringExecution&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서두의 빨간색 required(hard affinity) &amp;amp; preferred(soft affinity)는 반드시 포함해야 하는지 또는 우선시하되 필수는 아닌지를 결정하는 조건이며, 중간의 파란색 Ignored &amp;amp; Required는 운영 중(Runtime) Node의 Label이 변경되면 무시할 것인지(Ignore) 또는 즉시 Eviction 처리(Required)하여 재기동을 수행할 것인지를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 노드 어피니티를 사용하는 파드 예시가 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;requiredDuringSchedulingIgnoredDuringExecution의 경우 아래 요구조건이 일치하는 node에 적용하겠다는 의미로 matchExpressions로 정의 된 key 값이 label의 [key], values 값이 label의 [value]와 일치해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 처음 정의되는 operator라는 항목이 있는데 nodeAffinity에서 사용할 수 있는 operator에는 In 외에도 NotIn, Exists, DoesNotExist, Gt, Lt 등을 활용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pod Affinity&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pod affinity는 node affinity가 node의 label을 기준으로 기동했던것 처럼 pod의 label을 기준으로 조건을 만족할 경우 pod를 기동하는 방식으로 작성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드가 3개 있다. RS, STS 이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RS 에는 Web Pod 3개가 있다. STS 에는 DB Pod 가 3개 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;베스트 시나리오는 각각의 노드에 Web, DB 파드가 한 쌍 씩 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최악의 시나리오는 노드 하나에 모든 Web Pod 가 있고 다른 노드에 DB Pod 가 나눠져서 존재하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;베스트 시나리오 처럼 만들기 위해 Pod 와 Pod 간에 선호도를 설정해줘야 한다. 이 때 Pod Affinity 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 최악의 시나리오로 하나의 노드에 모든 파드들이 몰려있을 수 있다. 이를 방지하기 위해 Pod anti-affinity 를 사용해야 한다. RS, STS 에서 레플리카로 Pod 가 생성되기 때문에 RS, STS 에 anti-affinity 를 적용하여 서로 같은 파드들은 배척하도록 하여 다른 노드에 배치되도록 해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8tnuD/btrDlZcFzzi/kBEmXVeZhL4bznKSUFRFrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8tnuD/btrDlZcFzzi/kBEmXVeZhL4bznKSUFRFrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8tnuD/btrDlZcFzzi/kBEmXVeZhL4bznKSUFRFrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8tnuD%2FbtrDlZcFzzi%2FkBEmXVeZhL4bznKSUFRFrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;468&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같이 만들어지도록 시나리오를 작성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;topologyKey: Pod 간에 같이 있어도 되는지, 같이 있으면 안되는지에 대한 기준이다. 즉 어떤 노드에 파드들을 어떻게 배치할지에 대해 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-rs-a.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-a
spec:
  replicas: 3
  selector:
    matchLabels:
      app: a
  template:
    metadata:
      labels:
        app: a
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 10
              preference:
                matchExpressions:
                  - key: gpu
                    operator: Exists
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: a
              topologyKey: kubernetes.io/hostname
      containers:
        - name: myweb
          image: nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-rs-b.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-b
spec:
  replicas: 2
  selector:
    matchLabels:
      app: b
  template:
    metadata:
      labels:
        app: b
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 10
              preference:
                matchExpressions:
                  - key: gpu
                    operator: Exists
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                 matchLabels:
                   app: b
              topologyKey: &quot;kubernetes.io/hostname&quot;
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                 matchLabels:
                   app: a
              topologyKey: &quot;kubernetes.io/hostname&quot;
      containers:
        - name: myweb
					image: ghcr.io/c1t1d0s7/go-myweb
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                 matchLabels:
                   app: a
              topologyKey: &quot;kubernetes.io/hostname&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;labelSelector에 의해 pod의 라벨 정보가 app=a 인 pods가 기동 된 노드를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;topologyKey에 정의 된 key 값인 &lt;a href=&quot;http://kubernetes.io/hostname%EC%9D%98&quot;&gt;kubernetes.io/hostname의&lt;/a&gt; value가 같은 모든 노드에 Pod를 배치한다. 이때 hostname은 모든 서버가 서로 다르므로 app=a 인 파드가 배포된 노드에만 배포가 가능하다는 결론이 나온다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cordon &amp;amp; Drain&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cordon&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄링 금지&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;kubectl cordon &amp;lt;노드_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄링 허용&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;kubectl uncordon &amp;lt;노드_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;drain: 노드의 Pod 들을 삭제 후 해당 노드 cordon. 데몬셋이 셋팅되어 있을 경우 ignore 옵션을 사용해야 한다. 주로 패치, 커널 업데이트를 할 때 사용한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;kubectl drain &amp;lt;노드_이름&amp;gt;
kubectl drain &amp;lt;노드_이름&amp;gt; --ignore-daemonsets
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;kubectl uncordon &amp;lt;노드_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Taint &amp;amp; Toleration&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/scheduling-eviction/taint-and-toleration/&quot;&gt;https://kubernetes.io/ko/docs/concepts/scheduling-eviction/taint-and-toleration/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Control Plane&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Taint: &amp;ldquo;&lt;a href=&quot;http://node-role.kubernetes.io/master:NoSchedule%E2%80%9D&quot;&gt;node-role.kubernetes.io/master:NoSchedule&amp;rdquo;&lt;/a&gt; &amp;rarr; key:value == master:NoSchedule, 키밸류 형태로 master 에 스케줄링을 하지 않도록 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Taint: 특정 노드에 역할을 부여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Toleration: Taint 노드에 스케줄링 허용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;command 를 사용하여 Taint 걸기&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;kubectl taint node k8s-node1 node-role.kubernetes.io/master:NoSchedule
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;command 를 사용하여 Taint 해제&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;kubectl taint node k8s-node1 node-role.kubernetes.io/master:NoSchedule-
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-a.yml&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-a
spec:
  replicas: 3
  selector:
    matchLabels:
      app: a
  template:
    metadata:
      labels:
        app: a
    spec:
      tolerations:
        - key: &quot;node-role.kubernetes.io/masters&quot;
          operator: &quot;Exists&quot;
          effect: &quot;NoSchedule&quot;
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 10
              preference:
                matchExpressions:
                  - key: gpu
                    operator: Exists
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: a
              topologyKey: kubernetes.io/hostname
      containers:
        - name: myweb
          image: nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toleration 을 사용하여 taint 된 노드에 스케줄링을 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;kubectl describe node k8s-node1 | grep -i taint
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 노드에 taint 를 거는 것은 역할을 부여하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toleration 을 사용해서 특정 파드들을 taint 에 의해 역할이 부여된 노드에 파드를 배치할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;taint 를 만든 목적은 노드에 역할을 부여하기 위해서이다. taint 된 노드들은 반드시 toleration 을 사용해야 파드가 배치될 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kubeconfig&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/.kube/config&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Config
preferences: {}

clusters:
- name: cluster.local
  cluster:
    certificate-authority-data: LS0tLS... # CA 인증서
    server: https://127.0.0.1:6443 # 실제 api-sever 의 주소
  
- name: mycluster
  cluster: # 새로운 클러스터를 여기에 추가할 수 있다.
    server: https://x.x.x.x:6443
  
users:
- name: kubernetes-admin
  user:
    # 인증서와 키를 사용해서 서버에 사용자 인증을 한다.
    client-certificate-data: LS0tLS1CRUdJTi...  # 클라이언트 인증서
    client-key-data: LS0tLS1CRU... # 클라이언트 개인키

contexts:
- name: kubernetes-admin@cluster.local 
  context: # 사용자와 클러스터를 연결
    cluster: cluster.local
    user: kubernetes-admin
  

# 현재 사용할 컨텍스트 결정
current-context: kubernetes-admin@cluster.local&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl config view
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;kubectl config get-clusters
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;kubectl config get-contexts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl config use-context myadmin@mycluster
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/253</guid>
      <comments>https://ssunw.tistory.com/entry/Shedulling#entry253comment</comments>
      <pubDate>Sun, 29 May 2022 15:27:28 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] AutoScaling</title>
      <link>https://ssunw.tistory.com/entry/AutoScaling</link>
      <description>&lt;h1&gt;Auto Sacling&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Resource Request &amp;amp; Limit&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/configuration/manage-resources-containers/&quot;&gt;https://kubernetes.io/ko/docs/concepts/configuration/manage-resources-containers/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청: request&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한: limit&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 &amp;le; 제한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QoS(서비스 품질) Class:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BestEffort: 품질이 가장 안좋음, 리소스를 아예 할당받지 못할 수도 있다. 리소스가 없는 상태에서 파드가 생성되야 하는 경우 BestEffort Pod 가 먼저 삭제된다.&lt;/li&gt;
&lt;li&gt;Burstable: 중간&lt;/li&gt;
&lt;li&gt;Guranteed: 품질이 가장 좋음, 가장 베스트&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청/제한이 설정되어 있지 않으면: QoS&lt;/li&gt;
&lt;li&gt;요청 &amp;lt; 제한: Burstable&lt;/li&gt;
&lt;li&gt;요청 == 제한: Guranteed&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pod.spec.containers.resources&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;requests
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cpu&lt;/li&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;limits
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cpu&lt;/li&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 요청 &amp;amp; 제한: milicore 단위를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 1500m &amp;rarr; CPU 코어 1.5개, 1000m &amp;rarr; CPU 코어 1개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 1.5, 0.5&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 요청 &amp;amp; 제한: M, G, T, Mi, Gi, Ti&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드를 지정할 때,&amp;nbsp;컨테이너에 필요한 각 리소스의 양을 선택적으로 지정할 수 있다. 지정할 가장 일반적인 리소스는 CPU와 메모리(RAM) 그리고 다른 것들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드에서 컨테이너에 대한 리소스&amp;nbsp;요청(request)&amp;nbsp;을 지정하면,&amp;nbsp;kube-scheduler는 이 정보를 사용하여 파드가 배치될 노드를 결정한다. 컨테이너에 대한 리소스&amp;nbsp;제한(limit)&amp;nbsp;을 지정하면, kubelet은 실행 중인 컨테이너가 설정한 제한보다 많은 리소스를 사용할 수 없도록 해당 제한을 적용한다. 또한 kubelet은 컨테이너가 사용할 수 있도록 해당 시스템 리소스의 최소&amp;nbsp;요청&amp;nbsp;량을 예약한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드가 실행 중인 노드에 사용 가능한 리소스가 충분하면, 컨테이너가 해당 리소스에 지정한&amp;nbsp;request&amp;nbsp;보다 더 많은 리소스를 사용할 수 있도록 허용된다. 그러나, 컨테이너는 리소스&amp;nbsp;limit&amp;nbsp;보다 더 많은 리소스를 사용할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 컨테이너에 대해 256MiB의&amp;nbsp;memory&amp;nbsp;요청을 설정하고, 해당 컨테이너가 8GiB의 메모리를 가진 노드로 스케줄된 파드에 있고 다른 파드는 없는 경우, 컨테이너는 더 많은 RAM을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 컨테이너에 대해 4GiB의&amp;nbsp;memory&amp;nbsp;제한을 설정하면, kubelet(그리고&amp;nbsp;컨테이너 런타임)이 제한을 적용한다. 런타임은 컨테이너가 구성된 리소스 제한을 초과하여 사용하지 못하게 한다. 예를 들어, 컨테이너의 프로세스가 허용된 양보다 많은 메모리를 사용하려고 하면, 시스템 커널은 메모리 부족(out of memory, OOM) 오류와 함께 할당을 시도한 프로세스를 종료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한은 반응적(시스템이 위반을 감지한 후에 개입)으로 또는 강제적(시스템이 컨테이너가 제한을 초과하지 않도록 방지)으로 구현할 수 있다. 런타임마다 다른 방식으로 동일한 제약을 구현할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;로컬 임시(ephemeral) 스토리지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드에는 로컬에 연결된 쓰기 가능 장치 또는, 때로는 RAM에 의해 지원되는 로컬 임시 스토리지가 있다. &quot;임시&quot;는 내구성에 대한 장기간의 보증이 없음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드는 스크래치 공간, 캐싱 및 로그에 대해 임시 로컬 스토리지를 사용한다. kubelet은 로컬 임시 스토리지를 사용하여 컨테이너에&amp;nbsp;emptyDir&amp;nbsp;볼륨을 마운트하기 위해 파드에 스크래치 공간을 제공할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubelet은 이러한 종류의 스토리지를 사용하여&amp;nbsp;노드-레벨 컨테이너 로그, 컨테이너 이미지 및 실행 중인 컨테이너의 쓰기 가능 계층을 보유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-reqlim.yml&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-reqlim
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb
      resources:
        requests:
          cpu: 200m
          memory: 200M
        limits:
          cpu: 200m
          memory: 200M
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;kubectl craete -f .
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-reqlim
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb
      resources:
        requests:
          cpu: 200m
          memory: 200M
        limits:
          cpu: 300m
          memory: 300M
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;kubectl replace -f myweb-reqlim.yml --force
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드별 CPU &amp;amp; Memory 사용량 확인&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;kubectl top node
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 요청/제한 확인&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;kubectl describe nodes &amp;lt;노드_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드별 CPU &amp;amp; Memory 사용량 확인&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;kubectl top pods
kubectl top pods -A
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스 모니터링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heapster 프로젝트(retire)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metric-server: 실시간 CPU/Memory 모니터링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prometheus: 실시간/이전 CPU/Memory/Network/Disk 모니터링&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HPA: Horisontal Pod AutoScaler&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/tasks/run-application/horizontal-pod-autoscale/&quot;&gt;https://kubernetes.io/ko/docs/tasks/run-application/horizontal-pod-autoscale/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AutoScaling&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HPA: Scale OUT&lt;/li&gt;
&lt;li&gt;VPA(Vertical Pod AutoScaler): Scale UP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Node
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ClusterScaler&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HPA: Deployment, ReplicaSet, StatefulSet 의 복제본 개수를 조정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에서,&amp;nbsp;HorizontalPodAutoscaler&amp;nbsp;는 워크로드 리소스(예:&amp;nbsp;디플로이먼트&amp;nbsp;또는&amp;nbsp;스테이트풀셋)를 자동으로 업데이트하며, 워크로드의 크기를 수요에 맞게 자동으로 스케일링하는 것을 목표로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수평 스케일링은 부하 증가에 대해&amp;nbsp;파드를 더 배치하는 것을 뜻한다. 이는&amp;nbsp;수직&amp;nbsp;스케일링(쿠버네티스에서는, 해당 워크로드를 위해 이미 실행 중인 파드에 더 많은 자원(예: 메모리 또는 CPU)를 할당하는 것)과는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하량이 줄어들고, 파드의 수가 최소 설정값 이상인 경우, HorizontalPodAutoscaler는 워크로드 리소스(디플로이먼트, 스테이트풀셋, 또는 다른 비슷한 리소스)에게 스케일 다운을 지시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Horizontal Pod Autoscaling은 크기 조절이 불가능한 오브젝트(예:&amp;nbsp;데몬셋)에는 적용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HorizontalPodAutoscaler는 쿠버네티스 API 자원 및&amp;nbsp;컨트롤러&amp;nbsp;형태로 구현되어 있다. HorizontalPodAutoscaler API 자원은 컨트롤러의 행동을 결정한다. 쿠버네티스&amp;nbsp;컨트롤 플레인&amp;nbsp;내에서 실행되는 HPA 컨트롤러는 평균 CPU 사용률, 평균 메모리 사용률, 또는 다른 커스텀 메트릭 등의 관측된 메트릭을 목표에 맞추기 위해 목표물(예: 디플로이먼트)의 적정 크기를 주기적으로 조정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HorizontalPodAutoscaler는 디플로이먼트 및 디플로이먼트의 레플리카셋의 크기를 조정한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;알고리즘 세부 정보&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 관점에서, HorizontalPodAutoscaler 컨트롤러는 원하는(desired) 메트릭 값과 현재(current) 메트릭 값 사이의 비율로 작동한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;원하는 레플리카 수 = ceil[현재 레플리카 수 * ( 현재 메트릭 값 / 원하는 메트릭 값 )]

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 현재 메트릭 값이&amp;nbsp;200m이고 원하는 값이&amp;nbsp;100m인 경우&amp;nbsp;200.0 / 100.0 == 2.0이므로 복제본 수가 두 배가 된다. 만약 현재 값이&amp;nbsp;50m&amp;nbsp;이면,&amp;nbsp;50.0 / 100.0 == 0.5&amp;nbsp;이므로 복제본 수를 반으로 줄일 것이다. 컨트롤 플레인은 비율이 1.0(기본값이 0.1인&amp;nbsp;-horizontal-pod-autoscaler-tolerance&amp;nbsp;플래그를 사용하여 전역적으로 구성 가능한 허용 오차 내)에 충분히 가깝다면 스케일링을 건너 뛸 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;targetAverageValue&amp;nbsp;또는&amp;nbsp;targetAverageUtilization가 지정되면,&amp;nbsp;currentMetricValue는 HorizontalPodAutoscaler의 스케일 목표 안에 있는 모든 파드에서 주어진 메트릭의 평균을 취하여 계산된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허용치를 확인하고 최종 값을 결정하기 전에, 컨트롤 플레인은 누락된 메트릭이 있는지, 그리고 몇 개의 파드가&amp;nbsp;Ready인지도 고려한다. 삭제 타임스탬프가 설정된 모든 파드(파드에 삭제 타임스탬프가 있으면 셧다운/삭제 중임을 뜻한다)는 무시되며, 모든 실패한 파드는 버려진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;워크로드 스케일링의 안정성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HorizontalPodAutoscaler를 사용하여 레플리카 그룹의 크기를 관리할 때, 측정하는 메트릭의 동적 특성 때문에 레플리카 수가 계속 자주 요동칠 수 있다. 이는 종종&amp;nbsp;thrashing&amp;nbsp;또는&amp;nbsp;flapping이라고 불린다. 이는 사이버네틱스 분야의&amp;nbsp;이력 현상(hysteresis)&amp;nbsp;개념과 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 자주 스케일 인, 스케일 아웃이 계속해서 발생할 경우 성능이 더 떨어지기 때문에 metric monitoring 이 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;안정화 윈도우&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정화 윈도우는 스케일링에 사용되는 메트릭이 계속 변동할 때 레플리카 수의&amp;nbsp;흔들림을 제한하기 위해 사용된다. 오토스케일링 알고리즘은 이전의 목표 상태를 추론하고 워크로드 수의 원치 않는 변화를 방지하기 위해 이 안정화 윈도우를 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 다음 예제 스니펫에서,&amp;nbsp;scaleDown에 대해 안정화 윈도우가 설정되었다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;behavior:
  scaleDown:
    stabilizationWindowSeconds: 300&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭 관측 결과 스케일링 목적물이 스케일 다운 되어야 하는 경우, 알고리즘은 이전에 계산된 목표 상태를 확인하고, 해당 구간에서 계산된 값 중 가장 높은 값을 사용한다. 위의 예시에서, 이전 5분 동안의 모든 목표 상태가 고려 대상이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 동적 최대값(rolling maximum)을 근사화하여, 스케일링 알고리즘이 빠른 시간 간격으로 파드를 제거하고 바로 다시 동일한 파드를 재생성하는 현상을 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 5분 동안 스케일링을 하지 않고 기다린 후에 스케일링이 이뤄진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 requests, limits 를 설정해줘야 HPA 가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-deploy.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: myweb-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 200m
            limits:
              cpu: 200m
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-hpa.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: myweb-hpa
spec:
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myweb-deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하 걸기&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;kubectl exec &amp;lt;POD&amp;gt; -- sha256sum /dev/zero
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v2beta2 로 HPA 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-hpa-v2beta2.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;$ cat myweb-hpa-v2beta2.yml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: myweb-hpa
spec:
  minReplicas: 1
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 40
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myweb-deploy
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/252</guid>
      <comments>https://ssunw.tistory.com/entry/AutoScaling#entry252comment</comments>
      <pubDate>Sun, 29 May 2022 15:25:32 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] statefulSet</title>
      <link>https://ssunw.tistory.com/entry/statefulSet</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Headless Service&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;headless 서비스는 Cluster IP 가 none 이다. 즉, 서비스의 IP 가 없는 서비스를 Headless 서비스라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-rs.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      env: dev
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-svc.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: myweb-svc
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-svc-headless.yml&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-headless
spec:
  type: ClusterIP
  clusterIP: None # &amp;lt;-- Headless Service
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;gt; host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.233.58.177

&amp;gt; host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.81.32
myweb-svc-headless.default.svc.cluster.local has address 10.233.76.27
myweb-svc-headless.default.svc.cluster.local has address 10.233.111.28
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;host myweb-svc-headless 를 실행하면 myweb-svc-headless 의 서비스 주소가 none 이기 때문에 연결된 Pod 의 IP 주소가 나온다.&lt;/p&gt;
&lt;h1&gt;스테이트풀셋&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/workloads/controllers/statefulset/&quot;&gt;https://kubernetes.io/ko/docs/concepts/workloads/controllers/statefulset/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pet vs cattle: &lt;a href=&quot;http://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/&quot;&gt;http://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pet 은 고유성, 상태를 가지고 있고 교체 하는 것이 상당히 어렵다. 그에 반해 cattle 고유성이랄게 없고 교체가 아주 쉽고 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 pet 은 RDBMS 가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이트풀셋은 애플리케이션의 스테이트풀을 관리하는데 사용하는 워크로드 API 오브젝트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드&amp;nbsp;집합의 디플로이먼트와 스케일링을 관리하며, 파드들의&amp;nbsp;순서 및 고유성을 보장한다&amp;nbsp;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트와 유사하게, 스테이트풀셋은 동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다. 디플로이먼트와는 다르게, 스테이트풀셋은 각 파드의 독자성을 유지한다. 이 파드들은 동일한 스팩으로 생성되었지만, 서로 교체는 불가능하다. 다시 말해, 각각은 재스케줄링 간에도 지속적으로 유지되는 식별자를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 볼륨을 사용해서 워크로드에 지속성을 제공하려는 경우, 솔루션의 일부로 스테이트풀셋을 사용할 수 있다. 스테이트풀셋의 개별 파드는 장애에 취약하지만, 퍼시스턴트 파드 식별자는 기존 볼륨을 실패한 볼륨을 대체하는 새 파드에 더 쉽게 일치시킬 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스테이트풀셋 사용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이트풀셋은 다음 중 하나 또는 이상이 필요한 애플리케이션에 유용하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안정된, &lt;b&gt;고유한 네트워크 식별자&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;안정된, &lt;b&gt;지속성을 갖는 스토리지&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;순차적인, 정상 배포(graceful deployment)와 스케일링.&lt;/li&gt;
&lt;li&gt;순차적인, 자동 롤링 업데이트.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 안정은 파드의 (재)스케줄링 전반에 걸친 지속성과 같은 의미이다. 만약 애플리케이션이 안정적인 식별자 또는 순차적인 배포, 삭제 또는 스케일링이 필요하지 않으면, 스테이트리스 레플리카셋(ReplicaSet)을 제공하는 워크로드 오브젝트를 사용해서 애플리케이션을 배포해야 한다.&amp;nbsp;디플로이먼트&amp;nbsp;또는&amp;nbsp;레플리카셋과 같은 컨트롤러가 스테이트리스 요구에 더 적합할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;제한사항&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파드에 지정된 스토리지는 관리자에 의해&amp;nbsp;**퍼시스턴트 볼륨 프로비저너(PVC)**를 기반으로 하는&amp;nbsp;storage class&amp;nbsp;를 요청해서 프로비전하거나 사전에 프로비전이 되어야 한다. STS 의 파드들은 각자가 고유성을 가져야 하기 때문에 각각의 파드들은 자신만의 볼륨을 가져야 한다. (.spec.volumeClaimTemplates 사용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스테이트풀셋을 삭제 또는 스케일 다운해도 스테이트풀셋과 연관된 볼륨이&amp;nbsp;삭제되지 않는다.&lt;/b&gt; 이는 일반적으로 스테이트풀셋과 연관된 모든 리소스를 자동으로 제거하는 것보다 더 중요한 &lt;b&gt;데이터의 안전을 보장&lt;/b&gt;하기 위함이다.&lt;/li&gt;
&lt;li&gt;스테이트풀셋은 현재 파드의 네트워크 신원을 책임지고 있는&amp;nbsp;&lt;b&gt;헤드리스 서비스가 필요&lt;/b&gt;하다. 사용자가 이 서비스를 생성할 책임이 있다.&lt;/li&gt;
&lt;li&gt;스테이트풀셋은 스테이트풀셋의 삭제 시 파드의 종료에 대해 어떠한 보증을 제공하지 않는다. 스테이트풀셋에서는 파드가 순차적이고 정상적으로 종료(graceful termination)되도록 하려면, 삭제 전 스테이트풀셋의 스케일을 0으로 축소할 수 있다.&lt;/li&gt;
&lt;li&gt;롤링 업데이트와 기본&amp;nbsp;파드 매니지먼트 폴리시&amp;nbsp;(OrderedReady)를 함께 사용시&amp;nbsp;복구를 위한 수동 개입이 필요한 파손 상태로 빠질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구성요소&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # .spec.template.metadata.labels 와 일치해야 한다
  serviceName: &quot;nginx&quot;
  replicas: 3 # 기본값은 1
  minReadySeconds: 10 # 기본값은 0
  template:
    metadata:
      labels:
        app: nginx # .spec.selector.matchLabels 와 일치해야 한다
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ &quot;ReadWriteOnce&quot; ]
      storageClassName: &quot;my-storage-class&quot;
      resources:
        requests:
          storage: 1Gi
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;안정적인 네트워크 신원&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이트풀셋의 각 파드는 스테이트풀셋의 이름과 파드의 순번에서 호스트 이름을 얻는다. 호스트 이름을 구성하는 패턴은&amp;nbsp;$(statefulset name)-$(ordinal)&amp;nbsp;이다. 위의 예시에서 생성된 3개 파드의 이름은&amp;nbsp;web-0,web-1,web-2&amp;nbsp;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이트풀셋은 스테이트풀셋에 있는 파드의 도메인을 제어하기위해&amp;nbsp;헤드리스 서비스를 사용할 수 있다. 이 서비스가 관리하는 도메인은&amp;nbsp;$(service name).$(namespace).svc.cluster.local&amp;nbsp;의 형식을 가지며, 여기서 &quot;cluster.local&quot;은 클러스터 도메인이다. 각 파드는 생성되면&amp;nbsp;$(podname).$(governing service domain)&amp;nbsp;형식을 가지고 일치되는 DNS 서브도메인을 가지며, 여기서 거버닝 서비스(governing service)는 스테이트풀셋의&amp;nbsp;serviceName&amp;nbsp;필드에 의해 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터에서 DNS가 구성된 방식에 따라, 새로 실행된 파드의 DNS 이름을 즉시 찾지 못할 수 있다. 이 동작은 클러스터의 다른 클라이언트가 파드가 생성되기 전에 파드의 호스트 이름에 대한 쿼리를 이미 보낸 경우에 발생할 수 있다. 네거티브 캐싱(DNS에서 일반적)은 이전에 실패한 조회 결과가 파드가 실행된 후에도 적어도 몇 초 동안 기억되고 재사용됨을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드를 생성한 후 즉시 파드를 검색해야 하는 경우, 몇 가지 옵션이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DNS 조회에 의존하지 않고 쿠버네티스 API를 직접(예를 들어 watch 사용) 쿼리한다.&lt;/li&gt;
&lt;li&gt;쿠버네티스 DNS 공급자의 캐싱 시간(일반적으로 CoreDNS의 컨피그맵을 편집하는 것을 의미하며, 현재 30초 동안 캐시함)을 줄인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항&amp;nbsp;섹션에서 언급한 것처럼 사용자는 파드의 네트워크 신원을 책임지는&amp;nbsp;헤드리스 서비스를 생성할 책임이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 클러스터 도메인, 서비스 이름, 스테이트풀셋 이름을 선택을 하고, 그 선택이 스테이트풀셋 파드의 DNS이름에 어떻게 영향을 주는지에 대한 약간의 예시가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWAkCe/btrDmVA2LaW/7kddmP9yrtdTikWLQwH4DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWAkCe/btrDmVA2LaW/7kddmP9yrtdTikWLQwH4DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWAkCe/btrDmVA2LaW/7kddmP9yrtdTikWLQwH4DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWAkCe%2FbtrDmVA2LaW%2F7kddmP9yrtdTikWLQwH4DK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1159&quot; height=&quot;222&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 1&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-sts.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myweb-sts
spec:
  replicas: 3
  serviceName: myweb-svc-headless
  selector:
    matchLabels:
      app: web
      env: dev
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-svc-headless&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-headless
spec:
  type: ClusterIP
  clusterIP: None # &amp;lt;-- Headless Service
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ kubectl get sts,po                              
NAME                         READY   AGE
statefulset.apps/myweb-sts   3/3     7m

NAME                                          READY   STATUS    RESTARTS       AGE
pod/myweb-sts-0                               1/1     Running   0              7m
pod/myweb-sts-1                               1/1     Running   0              6m57s
pod/myweb-sts-2                               1/1     Running   0              3m50s
pod/nettool                                   1/1     Running   0              2m2s
pod/nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (141m ago)   21h
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드를 특정해서 해당 파드의 IP 주소를 가져올 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm

&amp;gt; host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.111.30
myweb-svc-headless.default.svc.cluster.local has address 10.233.76.32
myweb-svc-headless.default.svc.cluster.local has address 10.233.81.35

&amp;gt; host myweb-sts-0.myweb-svc-headless
myweb-sts-0.myweb-svc-headless.default.svc.cluster.local has address 10.233.76.32

&amp;gt; host myweb-sts-1.myweb-svc-headless
myweb-sts-1.myweb-svc-headless.default.svc.cluster.local has address 10.233.111.30

&amp;gt; host myweb-sts-2.myweb-svc-headless
myweb-sts-1.myweb-svc-headless.default.svc.cluster.local has address 10.233.111.35
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 2: PVC 템플릿 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-svc-headless.yml&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-headless
spec:
  type: ClusterIP
  clusterIP: None # &amp;lt;-- Headless Service
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-sts-vol.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myweb-sts-vol
spec:
  replicas: 3
  serviceName: myweb-svc-headless
  selector:
    matchLabels:
      app: web
      env: dev
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb:alpine
          ports:
            - containerPort: 8080
              protocol: TCP
          volumeMounts:
            - name: myweb-pvc
              mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: myweb-pvc
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1G
        storageClassName: nfs-client
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;kubectl create -f .
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;$ kubectl get sts,po,pv,pvc
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   3/3     25s

NAME                                          READY   STATUS    RESTARTS       AGE
pod/myweb-sts-vol-0                           1/1     Running   0              25s
pod/myweb-sts-vol-1                           1/1     Running   0              22s
pod/myweb-sts-vol-2                           1/1     Running   0              19s
pod/nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (164m ago)   21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              22s
persistentvolume/pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              25s
persistentvolume/pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              19s

NAME                                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            nfs-client     25s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            nfs-client     22s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            nfs-client     19s
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;kubectl scale sts myweb-sts-vol --replicas=2
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;$ kubectl get sts,po,pv,pvc
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   2/2     82s

NAME                                          READY   STATUS    RESTARTS       AGE
pod/myweb-sts-vol-0                           1/1     Running   0              82s
pod/myweb-sts-vol-1                           1/1     Running   0              79s
pod/nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (165m ago)   21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              79s
persistentvolume/pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              82s
persistentvolume/pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              76s

NAME                                              STATUS   VOLUME
      CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            nfs-client     82s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            nfs-client     79s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            nfs-client     76s
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;$ kubectl scale sts myweb-sts-vol --replicas=4
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;$ kubectl get sts,po,pv,pvc
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   4/4     2m27s

NAME                                          READY   STATUS    RESTARTS       AGE
pod/myweb-sts-vol-0                           1/1     Running   0              2m27s
pod/myweb-sts-vol-1                           1/1     Running   0              2m24s
pod/myweb-sts-vol-2                           1/1     Running   0              26s
pod/myweb-sts-vol-3                           1/1     Running   0              24s
pod/nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (166m ago)   21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              2m24s
persistentvolume/pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              2m27s
persistentvolume/pvc-542ab02f-acaa-4de3-a28f-fdfeb2b6c82d   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-3   nfs-client              24s
persistentvolume/pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              2m21s

NAME                                              STATUS   VOLUME
      CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            nfs-client     2m27s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            nfs-client     2m24s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            nfs-client     2m21s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-3   Bound    pvc-542ab02f-acaa-4de3-a28f-fdfeb2b6c82d   1G         RWO            nfs-client     24s
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 파드들은 고유한 volume 을 가지기 때문에 볼륨의 타입이 nfs 이어도 상관 없이 데이터를 공유하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;$ kubectl exec -it myweb-sts-vol-0 -- sh
&amp;gt; cd data/
/data &amp;gt; ls
/data &amp;gt; touch a b c
/data &amp;gt; ls
a  b  c
/data &amp;gt; exit
$ kubectl exec -it myweb-sts-vol-1 -- sh
&amp;gt; cd data/
/data &amp;gt; ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pod 가 삭제되더라도 volume 은 반영구적으로 남아있다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;$ kubectl delete pod myweb-sts-vol-0
pod &quot;myweb-sts-vol-1&quot; deleted

$ kubectl get po
NAME                                      READY   STATUS    RESTARTS       AGE
myweb-sts-vol-0                           1/1     Running   0              2s
myweb-sts-vol-1                           1/1     Running   0              6m52s
myweb-sts-vol-2                           1/1     Running   0              6m58s
myweb-sts-vol-3                           1/1     Running   0              6m56s
nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (173m ago)   21h

$ kubectl exec -it myweb-sts-vol-0 -- sh
/ &amp;gt; cd data/
/data &amp;gt; ls
a  b  c
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 3&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/&quot;&gt;https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;statefulse 으로 mysql 파드를 만든다. Master(RW) 와 Read Replicas 들로 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConfigMap&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  labels:
    app: mysql
data:
  primary.cnf: |
    # Apply this config only on the primary.
    [mysqld]
    log-bin
  replica.cnf: |
    # Apply this config only on replicas.
    [mysqld]
    super-read-only
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None # Headless 서비스
  selector:
    app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the primary: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;statefulSet&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;command 필드를 사용해서 ENTRYPOINT 를 변경한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7
        command:
        - bash
        - &quot;-c&quot;
        - |
          set -ex
          # Generate mysql server-id from pod ordinal index.
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] &amp;gt; /mnt/conf.d/server-id.cnf
          # Add an offset to avoid reserved server-id=0 value.
          echo server-id=$((100 + $ordinal)) &amp;gt;&amp;gt; /mnt/conf.d/server-id.cnf
          # Copy appropriate conf.d files from config-map to emptyDir.
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/primary.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/replica.cnf /mnt/conf.d/
          fi          
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      - name: clone-mysql
        image: gcr.io/google-samples/xtrabackup:1.0
        command:
        - bash
        - &quot;-c&quot;
        - |
          set -ex
          # Skip the clone if data already exists.
          [[ -d /var/lib/mysql/mysql ]] &amp;amp;&amp;amp; exit 0
          # Skip the clone on primary (ordinal index 0).
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] &amp;amp;&amp;amp; exit 0
          # Clone data from previous peer.
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # Prepare the backup.
          xtrabackup --prepare --target-dir=/var/lib/mysql          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: &quot;1&quot;
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
        livenessProbe:
          exec:
            command: [&quot;mysqladmin&quot;, &quot;ping&quot;]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            # Check we can execute queries over TCP (skip-networking is off).
            command: [&quot;mysql&quot;, &quot;-h&quot;, &quot;127.0.0.1&quot;, &quot;-e&quot;, &quot;SELECT 1&quot;]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: gcr.io/google-samples/xtrabackup:1.0
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - &quot;-c&quot;
        - |
          set -ex
          cd /var/lib/mysql

          # Determine binlog position of cloned data, if any.
          if [[ -f xtrabackup_slave_info &amp;amp;&amp;amp; &quot;x$(&amp;lt;xtrabackup_slave_info)&quot; != &quot;x&quot; ]]; then
            # XtraBackup already generated a partial &quot;CHANGE MASTER TO&quot; query
            # because we're cloning from an existing replica. (Need to remove the tailing semicolon!)
            cat xtrabackup_slave_info | sed -E 's/;$//g' &amp;gt; change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f xtrabackup_slave_info xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # We're cloning directly from primary. Parse binlog position.
            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm -f xtrabackup_binlog_info xtrabackup_slave_info
            echo &quot;CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\\
                  MASTER_LOG_POS=${BASH_REMATCH[2]}&quot; &amp;gt; change_master_to.sql.in
          fi

          # Check if we need to complete a clone by starting replication.
          if [[ -f change_master_to.sql.in ]]; then
            echo &quot;Waiting for mysqld to be ready (accepting connections)&quot;
            until mysql -h 127.0.0.1 -e &quot;SELECT 1&quot;; do sleep 1; done

            echo &quot;Initializing replication from clone position&quot;
            mysql -h 127.0.0.1 \\
                  -e &quot;$(&amp;lt;change_master_to.sql.in), \\
                          MASTER_HOST='mysql-0.mysql', \\
                          MASTER_USER='root', \\
                          MASTER_PASSWORD='', \\
                          MASTER_CONNECT_RETRY=10; \\
                        START SLAVE;&quot; || exit 1
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
          fi

          # Start a server to send backups when requested by peers.
          exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \\
            &quot;xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root&quot;          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [&quot;ReadWriteOnce&quot;]
      resources:
        requests:
          storage: 10Gi
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mysql-0 은 마스터로써 읽기와 쓰기가 모두 가능하고 mysql-1 은 read replica 이다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;/ &amp;gt; mysql -h mysql-0.mysql -u root

MySQL [(none)]&amp;gt; create database test;
Query OK, 1 row affected (0.008 sec)

MySQL [(none)]&amp;gt; show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sys                    |
| test                   |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.003 sec)

MySQL [(none)]&amp;gt; use test;

MySQL [test]&amp;gt; create table message (message VARCHAR(50));
Query OK, 0 rows affected (0.032 sec)

MySQL [test]&amp;gt; show tables;
+----------------+
| Tables_in_test |
+----------------+
| message        |
+----------------+
1 row in set (0.004 sec)

MySQL [test]&amp;gt; insert into message values (&quot;hello mysql&quot;);
Query OK, 1 row affected (0.017 sec)

MySQL [test]&amp;gt; select * from message;
+-------------+
| message     |
+-------------+
| hello mysql |
+-------------+
1 row in set (0.001 sec)

MySQL [(none)]&amp;gt; exit
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;/ &amp;gt; mysql -h mysql-1.mysql -u root

MySQL [(none)]&amp;gt; show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sys                    |
| test                   |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.003 sec)

MySQL [(none)]&amp;gt; drop database test;
ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option so it cannot execute this statement

MySQL [(none)]&amp;gt; use test;

MySQL [test]&amp;gt; show tables;
+----------------+
| Tables_in_test |
+----------------+
| message        |
+----------------+
1 row in set (0.004 sec)

MySQL [test]&amp;gt; select * from message;
+-------------+
| message     |
+-------------+
| hello mysql |
+-------------+
1 row in set (0.001 sec)

MySQL [(test)]&amp;gt; exit
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/251</guid>
      <comments>https://ssunw.tistory.com/entry/statefulSet#entry251comment</comments>
      <pubDate>Sun, 29 May 2022 15:24:35 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] TLS Termination</title>
      <link>https://ssunw.tistory.com/entry/TLS-Termination</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;TLS/SSL Termination with Ingress&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위키피디아: &lt;a href=&quot;https://en.wikipedia.org/wiki/TLS_termination_proxy&quot;&gt;https://en.wikipedia.org/wiki/TLS_termination_proxy&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSL 프로토콜은 3.X 버전까지 나왔으며 현재 더 이상 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TLS 는 SSL 프로토콜을 변형해서 만든 프로토콜이다. 현재 1.3 버전까지 나왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LH7cw/btrDpO8PdTd/1wRFt7vpJKadK8rFFF5Kkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LH7cw/btrDpO8PdTd/1wRFt7vpJKadK8rFFF5Kkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LH7cw/btrDpO8PdTd/1wRFt7vpJKadK8rFFF5Kkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLH7cw%2FbtrDpO8PdTd%2F1wRFt7vpJKadK8rFFF5Kkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;148&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 Proxy(LB) 사이에 TLS 를 사용하고 Proxy 와 웹서비스 사이에서는 HTTP 로 통신을 하는 방식이 TLS Termination 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 외부로 노출된 LB 에 클라이언트는 HTTPS 로 통신을 하고 LB 는 뒷단에 연결된 백엔드들과 HTTP 로 통신을 하는 방식을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 구성을 사용하는 이유는 첫번째 LB 뒷단에 연결되는 백엔드들과 HTTPS 로 통신을 할 경우 각각의 백엔드마다 인증서를 관리해줘야 하는데 이가 매우 불편하기 때문에 TLS Termination 구조를 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 수학적인 연산을 하는 작업들 즉, 암복호화 하는데 CPU 를 굉장히 많이 사용하기 때문에 TLS Termination 을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Proxy 와 백엔드도 HTTPS 로 통신하는 End To End 방식일 경우 보안이 좀 더 좋을 것이라고 생각할 수 있지만 꼭 그런 것도 아니다. 사용자가 해커라고 가정할 경우 로드 밸런서로 공격을 보낼 경우 해당 공격도 암호화가 되기 때문에 LB 에서 확인할 방법이 없다. 그래서 백엔드에서 복호화를 한 이후에 WAF, IDS, IPS, Anti DDoS, Anti Vius 등을 사용해서 확인을 해야한다. 이는 매우 좋지 않고 복잡하며 관리적인 측면에서 어려움이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비암호화 되는 구간이 있어야 백엔드로 도착하기 전에 보안 장치들로 확인을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안적인 관점에서 E to E 가 정말 힘든 작업이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ingress-secret.yml&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: ingress-secret
type: kubernetes.io/tls
data:
  tls.crt: |
    LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ1VENDQXMyZ0F3SUJBZ0lVQ2NFOTM4WEZFSnBLYjU2NTlJZE...
	tls.key: |
    LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMms4YTFBYS96NDRWcGZ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx-deploy.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx-svc.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
    - name: http
      port: 80
      targetPort: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx-ing.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
    - hosts:
        - &quot;*.nip.io&quot;
      secretName: ingress-secret
  rules:
    - host: &quot;*.nip.io&quot;
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-svc
                port:
                  number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ingress 에서 tls 를 셋팅했기 때문에 따로 443 포트를 열지 않고 80 포트를 사용해도 자동으로 443 포트를 열어서 HTTPS 로 리다이렉션 해준다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/250</guid>
      <comments>https://ssunw.tistory.com/entry/TLS-Termination#entry250comment</comments>
      <pubDate>Sun, 29 May 2022 15:23:23 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] ConfigMap &amp;amp; Secret</title>
      <link>https://ssunw.tistory.com/entry/ConfigMap-Secret</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;ConfigMap &amp;amp; Secret&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/configuration/configmap/&quot;&gt;https://kubernetes.io/ko/docs/concepts/configuration/configmap/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵은 키-값 쌍으로 기밀이 아닌 데이터를 저장하는 데 사용하는 API 오브젝트이다.&amp;nbsp;파드는&amp;nbsp;볼륨에서 &lt;b&gt;환경 변수&lt;/b&gt;, &lt;b&gt;커맨드-라인 인수&lt;/b&gt; 또는 **구성 파일(key=파일명, value=내용)**로 컨피그맵을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주로 설정파일, 인증서, 암호화 키를 제공할 때 ConfigMap 과 Secret 을 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵을 사용하면&amp;nbsp;컨테이너 이미지에서 환경별 구성을 분리하여, 애플리케이션을 쉽게 이식할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # 속성과 비슷한 키; 각 키는 간단한 값으로 매핑됨
  player_initial_lives: &quot;3&quot;
  ui_properties_file_name: &quot;user-interface.properties&quot;

  # 파일과 비슷한 키
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 변수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;my-config.yml&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: mymessage
data:
  MESSAGE: Customized Hello ConfigMap
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mymessage 컨피그맵의 모든 키를 환경 변수로 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      envFrom:
        - configMapRef:
            name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;valueFrom 필드 사용, mymessage 컨피그맵의 특정 키를 뽑아서 환경 변수로 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
	    env:  
				valueFrom:
					configMapKeyRef:
						key: MESSAGE
						name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;kubectl exec -it myweb-env -- sh
/ # env
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.233.0.1:443
HOSTNAME=myweb-env
SHLVL=1
HOME=/root
TERM=xterm
KUBERNETES_PORT_443_TCP_ADDR=10.233.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP=tcp://10.233.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
MESSAGE=Customized Hello ConfigMap
KUBERNETES_SERVICE_HOST=10.233.0.1
PWD=/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-cm-vol.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-cm-vol
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      volumeMounts:
        - name: cmvol
          mountPath: /myvol
  volumes:
    - name: cmvol
      configMap:
        name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;my-config.yml&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: mymessage
data:
  MESSAGE: Customized Hello ConfigMap
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Secret&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/configuration/secret/&quot;&gt;https://kubernetes.io/ko/docs/concepts/configuration/secret/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value 를 base64 로 인코딩하여 encoded data 를 만든다. 암호화라고 하지만 전혀 안전한 방식은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 AWS KMS, Hashicorp Vault 와 연동하여 암호화를 하여 안전하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 암호, 토큰 또는 키와 같은 소량의 중요한 데이터를 포함하는 오브젝트이다. 이를 사용하지 않으면 중요한 정보가&amp;nbsp;파드&amp;nbsp;spec 이나&amp;nbsp;컨테이너 이미지에 포함될 수 있다. 시크릿을 사용한다는 것은 사용자의 기밀 데이터를 애플리케이션 코드에 넣을 필요가 없음을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 시크릿을 사용하는 파드와 독립적으로 생성될 수 있기 때문에, 파드를 생성하고, 확인하고, 수정하는 워크플로우 동안 시크릿(그리고 데이터)이 노출되는 것에 대한 위험을 경감시킬 수 있다. 쿠버네티스 및 클러스터에서 실행되는 애플리케이션은 기밀 데이터를 비휘발성 저장소에 쓰는 것을 피하는 것과 같이, 시크릿에 대해 추가 예방 조치를 취할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은&amp;nbsp;컨피그맵과 유사하지만 특별히 기밀 데이터를 보관하기 위한 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;시크릿 타입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿을 생성할 때,&amp;nbsp;Secret&amp;nbsp;리소스의&amp;nbsp;type&amp;nbsp;필드를 사용하거나, (활용 가능하다면)&amp;nbsp;kubectl&amp;nbsp;의 유사한 특정 커맨드라인 플래그를 사용하여 시크릿의 타입을 명시할 수 있다. 시크릿 타입은 여러 종류의 기밀 데이터를 프로그래밍 방식으로 용이하게 처리하기 위해 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 일반적인 사용 시나리오를 위해 몇 가지 빌트인 타입을 제공한다. 이 타입은 쿠버네티스가 부과하여 수행되는 검증 및 제약에 따라 달라진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwgthA/btrDnrfqF5k/hDcCFhrRsl52oRgdjrzWK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwgthA/btrDnrfqF5k/hDcCFhrRsl52oRgdjrzWK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwgthA/btrDnrfqF5k/hDcCFhrRsl52oRgdjrzWK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwgthA%2FbtrDnrfqF5k%2FhDcCFhrRsl52oRgdjrzWK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;456&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Opaque, service-account-token, dockercfg, dockerconfigjson, tls 정도가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mydata.yml&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: mydata
type: Opaque
data:
  id: YWRtaW4K
  pwd: MTIzNAo=
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      envFrom:
        - configMapRef:
            name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mydata 시크릿의 모든 키를 환경 변수로 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      envFrom:
        - secretRef:
            name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;valueFrom 필드 사용, mydata 시크릿의 특정 키를 뽑아서 환경 변수로 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
	    env:  
				valueFrom:
					secretKeyRef:
						key: id
						name: mydata
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-cm-vol.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-secret-vol
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      volumeMounts:
        - name: secret-vol
          mountPath: /myvol
  volumes:
    - name: secret-vol
      secret:
        name: mydata
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx POD 에 HTTPs 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;documentation root: /usr/share/nginx/html&lt;/li&gt;
&lt;li&gt;Configuration File: /etc/nginx/conf.d&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자체 서명 인증서 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Secret 리소스로 생성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Type:&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Private key&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;openssl genrsa -out nginx-tls.key 2048
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Public Key&lt;/h3&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;openssl rsa -in nginx-tls.key -pubout -out nginx-tls
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CSR&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;openssl req -new -key nginx-tls.key -out nginx-tls.csr

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]:Seoul
Locality Name (eg, city) []:Seoul
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Dev
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:www.example.com
Email Address []:admin@test.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CRT(인증서)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래라면 csr(certificate sign request) 파일을 CA 에 업로드해서 제 3자 인증을 받고 .crt 파일을 가져오면 되지만, 간단하게 자체 서명 인증을 통해 .crt 파일을 만든다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;openssl req -x509 -days 3650 -key nginx-tls.key -in nginx-tls.csr -out nginx-tls.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설정 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx-tls.conf&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;server {
    listen              80;
    listen              443 ssl;
    server_name         myapp.example.com;
    ssl_certificate     /etc/nginx/ssl/tls.crt;
    ssl_certificate_key /etc/nginx/ssl/tls.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Secret&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;base64 x509/nginx-tls.crt -w 0
base64 x509/nginx-tls.key -w 0
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
	name: nginx-secret
type: kubernetes.io/tls
data:
	tls.crt: |
		LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ1VENDQXMyZ0F3SUJBZ0lVQ2NFOTM4WEZFSnBLYjU2NTlJZERTUFBJYmQwd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZRXhDekFKQmdOVkJBWVRBa3RTTVE0d0RBWURWUVFJREFWVFpXOTFiREVPTUF3R0ExVUVCd3dGVTJWdgpkV3d4RERBS0JnTlZCQW9NQTBSbGRqRUxNQWtHQTFVRUN3d0NTVlF4R0RBV0JnTlZCQU1NRDNkM2R5NWxlR0Z0CmNHeGxMbU52YlRFZE1Cc0dDU3FHU0liM0RRRUpBUllPWVdSdGFXNUFkR1Z6ZEM1amIyMHdIaGNOTWpJd05USTAKTURjeE5UTTJXaGNOTXpJd05USXhNRGN4TlRNMldqQ0JnVEVMTUFrR0ExVUVCaE1DUzFJeERqQU1CZ05WQkFnTQpCVk5sYjNWc01RNHdEQVlEVlFRSERBVlRaVzkxYkRFTU1Bb0dBMVVFQ2d3RFJHVjJNUXN3Q1FZRFZRUUxEQUpKClZERVlNQllHQTFVRUF3d1BkM2QzTG1WNFlXMXdiR1V1WTI5dE1SMHdHd1lKS29aSWh2Y05BUWtCRmc1aFpHMXAKYmtCMFpYTjBMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTnBQR3RRRwp2OCtPRmFYeW1rZlBrQVU3cHRodFBjWlZ4bGRYVVV0M2hYZUh5NWRBWjhNSDhFaVRNZXdDRmlmYUNqZHpqcVVmCjZzd1JjVkxBQzJWYkt5bk56Z0U1cVJYMXRhazJDd2Uwb1hRcUFBSE1rY3FJSlJIaTRKRThZVlJZU3p3dUpxZmUKOUZ5N2h5Ri9ZY0xZR05DVmpkYVpKOXR6SnZ3UW85S2VKaEFlcnVNZzZSRmE5UnFEUGxRNFBFbnNaNVRKTllxNApRbmRKRzJQZW8xMHYrUVF6TmUxYmZqOG1CMnFtc0ZmMktIeXdPb3dxNytLMEpKUjl4MlY3ZUtUTzVxU2N6RWdrCk9DQ3VhbFFRcmVBa2xvVUZSZGFYazJuWXFYcUhna3c0UkZIc3JHS3RYQlhsd2ppSVZsWXdMUURKTGxpTFdTNVAKTUx5bis5TEpRQzU0ZHA4Q0F3RUFBYU5UTUZFd0hRWURWUjBPQkJZRUZHZnk1VDZuSDRPYjVDRksrQWd0bktTMwpzNjVaTUI4R0ExVWRJd1FZTUJhQUZHZnk1VDZuSDRPYjVDRksrQWd0bktTM3M2NVpNQThHQTFVZEV3RUIvd1FGCk1BTUJBZjh3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUZ0dlhBL1VCN3MvSDhGZHMyZXYxMjZlMU02aEtxTmoKRDhLV0dFMm1taW9hbjF0MVhPZGx1bDc1LzZEa0pkTGxNQVJWaGhUYzhHaVZDK2dqMXF5MVBlekpqQk5ZZDJXMApxVVVOcXYxczd4VmtKdEhpMSs1dlBDNy9OMXBhYXVPVDJXTFdNWlhMVVR4QWxLZmRwRWxHVWFYUzdsK0tLOUxmCjBEVEdxWFROemRsNE8xM2hFU1dkZ1dIQ1ptY0RSc2g5MTUxSG5JaU5vaHRuN1laY2FCL1RSbnJlbDJhRVRkeUYKS3ZablVRZTIvRHdGVzVNcVBMejQvdHYxbVlpWlZoMVN3bjl2ZHV5RG9qSTZUNUhnK0xHblBVOEhUZkg3eVBDUgp4cWRDMkovTnZFNWtOUEViTVF4MUlTMWhOR2o0TDJKVWNJNXEwNm1DM0VMUnpUSXBURi9mMTlRPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
	tls.key: |
		LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMms4YTFBYS96NDRWcGZLYVI4K1FCVHVtMkcwOXhsWEdWMWRSUzNlRmQ0ZkxsMEJuCnd3ZndTSk14N0FJV0o5b0tOM09PcFIvcXpCRnhVc0FMWlZzcktjM09BVG1wRmZXMXFUWUxCN1NoZENvQUFjeVIKeW9nbEVlTGdrVHhoVkZoTFBDNG1wOTcwWEx1SElYOWh3dGdZMEpXTjFwa24yM01tL0JDajBwNG1FQjZ1NHlEcApFVnIxR29NK1ZEZzhTZXhubE1rMWlyaENkMGtiWTk2alhTLzVCRE0xN1Z0K1B5WUhhcWF3Vi9Zb2ZMQTZqQ3J2CjRyUWtsSDNIWlh0NHBNN21wSnpNU0NRNElLNXFWQkN0NENTV2hRVkYxcGVUYWRpcGVvZUNURGhFVWV5c1lxMWMKRmVYQ09JaFdWakF0QU1rdVdJdFpMazh3dktmNzBzbEFMbmgybndJREFRQUJBb0lCQUdLUFhEbGxZcXUrSmgvcAp5NldvSEFtQXlPN2xRd0tNTHlPM2xFaFNDYnZSWHlWR09wSmV2eWpFNjhEMW9ETVZ5WThIYU5zNzhiRjRIb1dSCmxwT0grWkRDRGNPWDJMTEZYK0twTjRtZkQxVjJzTklmSGFNbW5EWGJEREFGNXB0QjBaVzE4RlhSM3RrUEFROHEKaDJTRVU0KzROWFV4YUVFM1Y5NXRWTWorQllrNHU0eDM0bnZ2enJwdGFISER3QmVUOGdqYU9XbXMrd2lQWk5uZgpzanJGRWJ4VTZGeFZSazlWSEF3SGlTZ2ZPbGdOeFlzU25zYU85ZGtrV05GU0I1Mi8vVXRKK0szRnZrV3JLUG1qCmJYN3FtSlNaeHJFN09SeXFhK3lqZkZBWmYrajkxalczL09MNkM2ajNPMFRXV05aTlA1RjRldHFYZVBiN1lIMmkKOUcwb01jRUNnWUVBOG5qc1dUZnVmUVZYQVBLWHZGOW1UQUtsU0lzL3Vua2t3RVloWFh1WGtFRU9wOExEaVN2WQpwSnFzT3NUSlRRaE5mM0g0dWg4Rkl5SHFSb3BlS3VYYy9jSmFsRTFUejREZUN0d2c5R0xjVHpuaGV1RVVsTWhnCnNycVFucjFDTDJtRHd1MTFLL0w0MURHTkNaWFEyYWNmT1pYcCtLdkEzV0ZheVRLNzRDbHNKY2tDZ1lFQTVuMFMKYVlvQy9saEhHeGRqeDdOQTZUNDJVV1grMTFaRTZSSGRKWWtTa3NQWXdhYzlYUnFLWHlzcytrUnY4U2tUaUZMOAp5dnlRcjFyaWlGRWp6VkN2YjA5WjllRWw3UngyT3F3eEpISEJ3bFdKWUEvRFdLN3ZkbHdvZ21wbUhCVzdaQ1k1ClBRVVJSOWhmbC9TVk1selVjSFpyK1J1QmhqWElhTFd2KzdBM2pTY0NnWUFJeHArblN3THl1M0src2drVDVGQUwKaUR6N055OERUTWNydmw0T3lCNWdOanFWajlNTDcrNVRadFc4K3NwZVkyS0tybEZXU3pFZ2FHWFdUOFBBd2JrUgo1aXJwR3pOaFcwU3VGL0dKWnRxYWMzblp2TFBGL3NxaExXZDJsMXNCNUV2RWpsdWpUNVA1K2lFa3E2dDNkVGtJCkdJanpXeVFMM3k0dUFnd1N2TzBSSVFLQmdCVlZha2plZjF4dmExVjBtc1czTDhEbkF6d1JocjhEdXlrZmdDcWoKUDFiYVRjdk80UDBuSWJ1aFVXNXd1elBGSzR1b3NzRlBFMDFIeWtQM3pxcUlWMVNrTVoxeWs0SVk2bENQODVSOQord016eTUxYW1DZ3pPUExwb001QmJ1WVdsTnUyTmdJUjRBR1lZM0M3TUx3U240OHhSdGt0MUpWSmtSMHdBL1AxCjRZNXRBb0dCQUpHTlJKK3plRmdnR25OK25NdjNVa0ZTVDYxMEp3bi9QeWdUYTZPK3FZMlp0dHFITTBYKzRGM04KalpiSGpmTHlZZS9JaWZNMVpKR1M1ZUxTc0xjOFBjM0NaRUF3S05VUDkvQ2xsTCthamhibmwrdHRJOVBBZTFGeQorU1JSSVlPV1pZakFMNC82ZkNjL0UxY3g1OW9UVzR4eHBFRUtEck5rckxIY09iUTcxVVNuCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConfigMap&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
	name: nginx-conf
data:  

server-conf: |
server {
    listen              80;
    listen              443 ssl;
    server_name         myapp.example.com;
    ssl_certificate     /etc/nginx/ssl/tls.crt;
    ssl_certificate_key /etc/nginx/ssl/tls.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod 생성&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
	name: nginx-https-pod
	labels:
		app: nginx
spec:
	containers:
		- name: nginx
			image: nginx
			volumeMounts:
				- name: confvol
					mountPath: /etc/nginx/conf.d
				- name: secvol
					mountPath: /etc/nginx/ssl
	volumes:
		- name: confvol
			configMap:
				name: nginx-conf
		- name: secvol
			secret:
				name: nginx-secret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion:
kind: Service
metadata:
	name: nginx-svc
spec:
	type: LoadBalancer
	selector:
		app: nginx
	ports:
		- name: http
			port: 80
			targetPort:80
		- name: httpsp
			port: 443
			targetPort: 443
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Test&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;curl --insecure&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;If you see this page, the nginx web server is successfully installed and working. Further configuration is required.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For online documentation and support please refer to &lt;a href=&quot;&amp;lt;&amp;lt;a href=http://nginx.org/&amp;gt;http://nginx.org/&amp;lt;/a&amp;gt;&amp;gt;&quot;&gt;nginx.org&lt;/a&gt;.&lt;br /&gt;Commercial support is available at &lt;a href=&quot;&amp;lt;&amp;lt;a href=http://nginx.com/&amp;gt;http://nginx.com/&amp;lt;/a&amp;gt;&amp;gt;&quot;&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;Thank you for using nginx.&lt;/i&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ConfigMap &amp;amp; Secret&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/configuration/configmap/&quot;&gt;https://kubernetes.io/ko/docs/concepts/configuration/configmap/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵은 키-값 쌍으로 기밀이 아닌 데이터를 저장하는 데 사용하는 API 오브젝트이다.&amp;nbsp;파드는&amp;nbsp;볼륨에서 &lt;b&gt;환경 변수&lt;/b&gt;, &lt;b&gt;커맨드-라인 인수&lt;/b&gt; 또는 **구성 파일(key=파일명, value=내용)**로 컨피그맵을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주로 설정파일, 인증서, 암호화 키를 제공할 때 ConfigMap 과 Secret 을 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그맵을 사용하면&amp;nbsp;컨테이너 이미지에서 환경별 구성을 분리하여, 애플리케이션을 쉽게 이식할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # 속성과 비슷한 키; 각 키는 간단한 값으로 매핑됨
  player_initial_lives: &quot;3&quot;
  ui_properties_file_name: &quot;user-interface.properties&quot;

  # 파일과 비슷한 키
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 변수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;my-config.yml&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: mymessage
data:
  MESSAGE: Customized Hello ConfigMap
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mymessage 컨피그맵의 모든 키를 환경 변수로 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      envFrom:
        - configMapRef:
            name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;valueFrom 필드 사용, mymessage 컨피그맵의 특정 키를 뽑아서 환경 변수로 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
	    env:  
				valueFrom:
					configMapKeyRef:
						key: MESSAGE
						name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;kubectl exec -it myweb-env -- sh
/ # env
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.233.0.1:443
HOSTNAME=myweb-env
SHLVL=1
HOME=/root
TERM=xterm
KUBERNETES_PORT_443_TCP_ADDR=10.233.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP=tcp://10.233.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
MESSAGE=Customized Hello ConfigMap
KUBERNETES_SERVICE_HOST=10.233.0.1
PWD=/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-cm-vol.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-cm-vol
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      volumeMounts:
        - name: cmvol
          mountPath: /myvol
  volumes:
    - name: cmvol
      configMap:
        name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;my-config.yml&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: mymessage
data:
  MESSAGE: Customized Hello ConfigMap
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Secret&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/configuration/secret/&quot;&gt;https://kubernetes.io/ko/docs/concepts/configuration/secret/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value 를 base64 로 인코딩하여 encoded data 를 만든다. 암호화라고 하지만 전혀 안전한 방식은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 AWS KMS, Hashicorp Vault 와 연동하여 암호화를 하여 안전하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 암호, 토큰 또는 키와 같은 소량의 중요한 데이터를 포함하는 오브젝트이다. 이를 사용하지 않으면 중요한 정보가&amp;nbsp;파드&amp;nbsp;spec 이나&amp;nbsp;컨테이너 이미지에 포함될 수 있다. 시크릿을 사용한다는 것은 사용자의 기밀 데이터를 애플리케이션 코드에 넣을 필요가 없음을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 시크릿을 사용하는 파드와 독립적으로 생성될 수 있기 때문에, 파드를 생성하고, 확인하고, 수정하는 워크플로우 동안 시크릿(그리고 데이터)이 노출되는 것에 대한 위험을 경감시킬 수 있다. 쿠버네티스 및 클러스터에서 실행되는 애플리케이션은 기밀 데이터를 비휘발성 저장소에 쓰는 것을 피하는 것과 같이, 시크릿에 대해 추가 예방 조치를 취할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은&amp;nbsp;컨피그맵과 유사하지만 특별히 기밀 데이터를 보관하기 위한 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;시크릿 타입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿을 생성할 때,&amp;nbsp;Secret&amp;nbsp;리소스의&amp;nbsp;type&amp;nbsp;필드를 사용하거나, (활용 가능하다면)&amp;nbsp;kubectl&amp;nbsp;의 유사한 특정 커맨드라인 플래그를 사용하여 시크릿의 타입을 명시할 수 있다. 시크릿 타입은 여러 종류의 기밀 데이터를 프로그래밍 방식으로 용이하게 처리하기 위해 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 일반적인 사용 시나리오를 위해 몇 가지 빌트인 타입을 제공한다. 이 타입은 쿠버네티스가 부과하여 수행되는 검증 및 제약에 따라 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Opaque, service-account-token, dockercfg, dockerconfigjson, tls 정도가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mydata.yml&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: mydata
type: Opaque
data:
  id: YWRtaW4K
  pwd: MTIzNAo=
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      envFrom:
        - configMapRef:
            name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mydata 시크릿의 모든 키를 환경 변수로 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      envFrom:
        - secretRef:
            name: mymessage
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;valueFrom 필드 사용, mydata 시크릿의 특정 키를 뽑아서 환경 변수로 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-env
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
	    env:  
				valueFrom:
					secretKeyRef:
						key: id
						name: mydata
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-cm-vol.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-secret-vol
spec:
  containers:
    - name: myweb
      image: ghcr.io/c1t1d0s7/go-myweb:alpine
      volumeMounts:
        - name: secret-vol
          mountPath: /myvol
  volumes:
    - name: secret-vol
      secret:
        name: mydata
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx POD 에 HTTPs 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;documentation root: /usr/share/nginx/html&lt;/li&gt;
&lt;li&gt;Configuration File: /etc/nginx/conf.d&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자체 서명 인증서 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Secret 리소스로 생성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Type:&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Private key&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;openssl genrsa -out nginx-tls.key 2048
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Public Key&lt;/h3&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;openssl rsa -in nginx-tls.key -pubout -out nginx-tls
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CSR&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;openssl req -new -key nginx-tls.key -out nginx-tls.csr

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]:Seoul
Locality Name (eg, city) []:Seoul
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Dev
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:www.example.com
Email Address []:admin@test.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CRT(인증서)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래라면 csr(certificate sign request) 파일을 CA 에 업로드해서 제 3자 인증을 받고 .crt 파일을 가져오면 되지만, 간단하게 자체 서명 인증을 통해 .crt 파일을 만든다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;openssl req -x509 -days 3650 -key nginx-tls.key -in nginx-tls.csr -out nginx-tls.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설정 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx-tls.conf&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;server {
    listen              80;
    listen              443 ssl;
    server_name         myapp.example.com;
    ssl_certificate     /etc/nginx/ssl/tls.crt;
    ssl_certificate_key /etc/nginx/ssl/tls.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Secret&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;base64 x509/nginx-tls.crt -w 0
base64 x509/nginx-tls.key -w 0
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
	name: nginx-secret
type: kubernetes.io/tls
data:
	tls.crt: |
		LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ1VENDQXMyZ0F3SUJBZ0lVQ2NFOTM4WEZFSnBLYjU2NTlJZERTUFBJYmQwd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZRXhDekFKQmdOVkJBWVRBa3RTTVE0d0RBWURWUVFJREFWVFpXOTFiREVPTUF3R0ExVUVCd3dGVTJWdgpkV3d4RERBS0JnTlZCQW9NQTBSbGRqRUxNQWtHQTFVRUN3d0NTVlF4R0RBV0JnTlZCQU1NRDNkM2R5NWxlR0Z0CmNHeGxMbU52YlRFZE1Cc0dDU3FHU0liM0RRRUpBUllPWVdSdGFXNUFkR1Z6ZEM1amIyMHdIaGNOTWpJd05USTAKTURjeE5UTTJXaGNOTXpJd05USXhNRGN4TlRNMldqQ0JnVEVMTUFrR0ExVUVCaE1DUzFJeERqQU1CZ05WQkFnTQpCVk5sYjNWc01RNHdEQVlEVlFRSERBVlRaVzkxYkRFTU1Bb0dBMVVFQ2d3RFJHVjJNUXN3Q1FZRFZRUUxEQUpKClZERVlNQllHQTFVRUF3d1BkM2QzTG1WNFlXMXdiR1V1WTI5dE1SMHdHd1lKS29aSWh2Y05BUWtCRmc1aFpHMXAKYmtCMFpYTjBMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTnBQR3RRRwp2OCtPRmFYeW1rZlBrQVU3cHRodFBjWlZ4bGRYVVV0M2hYZUh5NWRBWjhNSDhFaVRNZXdDRmlmYUNqZHpqcVVmCjZzd1JjVkxBQzJWYkt5bk56Z0U1cVJYMXRhazJDd2Uwb1hRcUFBSE1rY3FJSlJIaTRKRThZVlJZU3p3dUpxZmUKOUZ5N2h5Ri9ZY0xZR05DVmpkYVpKOXR6SnZ3UW85S2VKaEFlcnVNZzZSRmE5UnFEUGxRNFBFbnNaNVRKTllxNApRbmRKRzJQZW8xMHYrUVF6TmUxYmZqOG1CMnFtc0ZmMktIeXdPb3dxNytLMEpKUjl4MlY3ZUtUTzVxU2N6RWdrCk9DQ3VhbFFRcmVBa2xvVUZSZGFYazJuWXFYcUhna3c0UkZIc3JHS3RYQlhsd2ppSVZsWXdMUURKTGxpTFdTNVAKTUx5bis5TEpRQzU0ZHA4Q0F3RUFBYU5UTUZFd0hRWURWUjBPQkJZRUZHZnk1VDZuSDRPYjVDRksrQWd0bktTMwpzNjVaTUI4R0ExVWRJd1FZTUJhQUZHZnk1VDZuSDRPYjVDRksrQWd0bktTM3M2NVpNQThHQTFVZEV3RUIvd1FGCk1BTUJBZjh3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUZ0dlhBL1VCN3MvSDhGZHMyZXYxMjZlMU02aEtxTmoKRDhLV0dFMm1taW9hbjF0MVhPZGx1bDc1LzZEa0pkTGxNQVJWaGhUYzhHaVZDK2dqMXF5MVBlekpqQk5ZZDJXMApxVVVOcXYxczd4VmtKdEhpMSs1dlBDNy9OMXBhYXVPVDJXTFdNWlhMVVR4QWxLZmRwRWxHVWFYUzdsK0tLOUxmCjBEVEdxWFROemRsNE8xM2hFU1dkZ1dIQ1ptY0RSc2g5MTUxSG5JaU5vaHRuN1laY2FCL1RSbnJlbDJhRVRkeUYKS3ZablVRZTIvRHdGVzVNcVBMejQvdHYxbVlpWlZoMVN3bjl2ZHV5RG9qSTZUNUhnK0xHblBVOEhUZkg3eVBDUgp4cWRDMkovTnZFNWtOUEViTVF4MUlTMWhOR2o0TDJKVWNJNXEwNm1DM0VMUnpUSXBURi9mMTlRPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
	tls.key: |
		LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBMms4YTFBYS96NDRWcGZLYVI4K1FCVHVtMkcwOXhsWEdWMWRSUzNlRmQ0ZkxsMEJuCnd3ZndTSk14N0FJV0o5b0tOM09PcFIvcXpCRnhVc0FMWlZzcktjM09BVG1wRmZXMXFUWUxCN1NoZENvQUFjeVIKeW9nbEVlTGdrVHhoVkZoTFBDNG1wOTcwWEx1SElYOWh3dGdZMEpXTjFwa24yM01tL0JDajBwNG1FQjZ1NHlEcApFVnIxR29NK1ZEZzhTZXhubE1rMWlyaENkMGtiWTk2alhTLzVCRE0xN1Z0K1B5WUhhcWF3Vi9Zb2ZMQTZqQ3J2CjRyUWtsSDNIWlh0NHBNN21wSnpNU0NRNElLNXFWQkN0NENTV2hRVkYxcGVUYWRpcGVvZUNURGhFVWV5c1lxMWMKRmVYQ09JaFdWakF0QU1rdVdJdFpMazh3dktmNzBzbEFMbmgybndJREFRQUJBb0lCQUdLUFhEbGxZcXUrSmgvcAp5NldvSEFtQXlPN2xRd0tNTHlPM2xFaFNDYnZSWHlWR09wSmV2eWpFNjhEMW9ETVZ5WThIYU5zNzhiRjRIb1dSCmxwT0grWkRDRGNPWDJMTEZYK0twTjRtZkQxVjJzTklmSGFNbW5EWGJEREFGNXB0QjBaVzE4RlhSM3RrUEFROHEKaDJTRVU0KzROWFV4YUVFM1Y5NXRWTWorQllrNHU0eDM0bnZ2enJwdGFISER3QmVUOGdqYU9XbXMrd2lQWk5uZgpzanJGRWJ4VTZGeFZSazlWSEF3SGlTZ2ZPbGdOeFlzU25zYU85ZGtrV05GU0I1Mi8vVXRKK0szRnZrV3JLUG1qCmJYN3FtSlNaeHJFN09SeXFhK3lqZkZBWmYrajkxalczL09MNkM2ajNPMFRXV05aTlA1RjRldHFYZVBiN1lIMmkKOUcwb01jRUNnWUVBOG5qc1dUZnVmUVZYQVBLWHZGOW1UQUtsU0lzL3Vua2t3RVloWFh1WGtFRU9wOExEaVN2WQpwSnFzT3NUSlRRaE5mM0g0dWg4Rkl5SHFSb3BlS3VYYy9jSmFsRTFUejREZUN0d2c5R0xjVHpuaGV1RVVsTWhnCnNycVFucjFDTDJtRHd1MTFLL0w0MURHTkNaWFEyYWNmT1pYcCtLdkEzV0ZheVRLNzRDbHNKY2tDZ1lFQTVuMFMKYVlvQy9saEhHeGRqeDdOQTZUNDJVV1grMTFaRTZSSGRKWWtTa3NQWXdhYzlYUnFLWHlzcytrUnY4U2tUaUZMOAp5dnlRcjFyaWlGRWp6VkN2YjA5WjllRWw3UngyT3F3eEpISEJ3bFdKWUEvRFdLN3ZkbHdvZ21wbUhCVzdaQ1k1ClBRVVJSOWhmbC9TVk1selVjSFpyK1J1QmhqWElhTFd2KzdBM2pTY0NnWUFJeHArblN3THl1M0src2drVDVGQUwKaUR6N055OERUTWNydmw0T3lCNWdOanFWajlNTDcrNVRadFc4K3NwZVkyS0tybEZXU3pFZ2FHWFdUOFBBd2JrUgo1aXJwR3pOaFcwU3VGL0dKWnRxYWMzblp2TFBGL3NxaExXZDJsMXNCNUV2RWpsdWpUNVA1K2lFa3E2dDNkVGtJCkdJanpXeVFMM3k0dUFnd1N2TzBSSVFLQmdCVlZha2plZjF4dmExVjBtc1czTDhEbkF6d1JocjhEdXlrZmdDcWoKUDFiYVRjdk80UDBuSWJ1aFVXNXd1elBGSzR1b3NzRlBFMDFIeWtQM3pxcUlWMVNrTVoxeWs0SVk2bENQODVSOQord016eTUxYW1DZ3pPUExwb001QmJ1WVdsTnUyTmdJUjRBR1lZM0M3TUx3U240OHhSdGt0MUpWSmtSMHdBL1AxCjRZNXRBb0dCQUpHTlJKK3plRmdnR25OK25NdjNVa0ZTVDYxMEp3bi9QeWdUYTZPK3FZMlp0dHFITTBYKzRGM04KalpiSGpmTHlZZS9JaWZNMVpKR1M1ZUxTc0xjOFBjM0NaRUF3S05VUDkvQ2xsTCthamhibmwrdHRJOVBBZTFGeQorU1JSSVlPV1pZakFMNC82ZkNjL0UxY3g1OW9UVzR4eHBFRUtEck5rckxIY09iUTcxVVNuCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConfigMap&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
	name: nginx-conf
data:  

server-conf: |
server {
    listen              80;
    listen              443 ssl;
    server_name         myapp.example.com;
    ssl_certificate     /etc/nginx/ssl/tls.crt;
    ssl_certificate_key /etc/nginx/ssl/tls.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod 생성&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
	name: nginx-https-pod
	labels:
		app: nginx
spec:
	containers:
		- name: nginx
			image: nginx
			volumeMounts:
				- name: confvol
					mountPath: /etc/nginx/conf.d
				- name: secvol
					mountPath: /etc/nginx/ssl
	volumes:
		- name: confvol
			configMap:
				name: nginx-conf
		- name: secvol
			secret:
				name: nginx-secret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;apiVersion:
kind: Service
metadata:
  name: nginx-svc
spec:
  type: LoadBalancer
  selector:
	app: nginx
  ports:
	- name: http
		port: 80
		targetPort:80
	- name: httpsp
		port: 443
		targetPort: 443&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Test&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;curl --insecure https://192.168.100.240
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Welcome to nginx!&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;For online documentation and support please refer to
&amp;lt;a href=&quot;http://nginx.org/&quot;&amp;gt;nginx.org&amp;lt;/a&amp;gt;.&amp;lt;br/&amp;gt;
Commercial support is available at
&amp;lt;a href=&quot;http://nginx.com/&quot;&amp;gt;nginx.com&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Thank you for using nginx.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GAMbn/btrDmVA2z0m/b8c28LY7Cn0mymBtGKn7O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GAMbn/btrDmVA2z0m/b8c28LY7Cn0mymBtGKn7O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GAMbn/btrDmVA2z0m/b8c28LY7Cn0mymBtGKn7O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGAMbn%2FbtrDmVA2z0m%2Fb8c28LY7Cn0mymBtGKn7O1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;539&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/249</guid>
      <comments>https://ssunw.tistory.com/entry/ConfigMap-Secret#entry249comment</comments>
      <pubDate>Sun, 29 May 2022 15:22:15 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] NFS 동적 프로비저닝</title>
      <link>https://ssunw.tistory.com/entry/NFS-%EB%8F%99%EC%A0%81-%ED%94%84%EB%A1%9C%EB%B9%84%EC%A0%80%EB%8B%9D</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;NFS 동적 프로비저닝&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/storage/storage-classes/#프로비저너&quot;&gt;https://kubernetes.io/ko/docs/concepts/storage/storage-classes/#프로비저너&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner&quot;&gt;https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브에서 코드를 다운로드 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할을 만드는 rbac.yaml 파일을 실행한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;cd ~

git clone &amp;lt;https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git&amp;gt;

cd ~/nfs-subdir-external-provisioner/deploy

kubectl create -f rbac.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deployment.yaml 에서 실제 NFS Server 의 주소와 마운트되는 NFS 경로를 작성한 후에 파일을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/nfs-subdir-external-provisioner/deploy/deployment.yaml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;...
...
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner
            - name: NFS_SERVER
              value: 192.168.100.100
            - name: NFS_PATH
              value: /nfsvolume
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.100.100
            path: /nfsvolume
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;kubectl create -f deployment.yaml

kubectl create -f class.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 클래스를 생성하는 class.yml 파일을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;annotations 필드를 추가해서 생성되는 스토리지 클래스가 디폴트가 되도록 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/nfs-subdir-external-provisioner/class.yaml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
  annotations:
    storageclass.kubernetes.io/is-default-class: &quot;true&quot;
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: &quot;false&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl apply -f class.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class.yaml 에서 디폴트 스토리지 클래스를 설정했기 때문에 StorageClassName 를 작성하지 않아도 pvc 가 자동으로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mypvc.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc-dynamic
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1G
  # StorageClassName: 'nfs-cline'
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;kubectl get pv
NAME            ...
mypvc-dynamic   ...
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/248</guid>
      <comments>https://ssunw.tistory.com/entry/NFS-%EB%8F%99%EC%A0%81-%ED%94%84%EB%A1%9C%EB%B9%84%EC%A0%80%EB%8B%9D#entry248comment</comments>
      <pubDate>Sun, 29 May 2022 15:19:42 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] deployment</title>
      <link>https://ssunw.tistory.com/entry/deployment</link>
      <description>&lt;h1&gt;&lt;b&gt;디플로이먼트&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/workloads/controllers/deployment/&quot;&gt;https://kubernetes.io/ko/docs/concepts/workloads/controllers/deployment/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트(Deployment)&amp;nbsp;는&amp;nbsp;파드와&amp;nbsp;레플리카셋(ReplicaSet)에 대한 선언적 업데이트를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트에서&amp;nbsp;의도하는 상태&amp;nbsp;를 설명하고, 디플로이먼트&amp;nbsp;컨트롤러(Controller)는 현재 상태에서 의도하는 상태로 비율을 조정하며 변경한다. 새 레플리카셋을 생성하는 디플로이먼트를 정의하거나 기존 디플로이먼트를 제거하고, 모든 리소스를 새 디플로이먼트에 적용할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Use Case&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 디플로이먼트의 일반적인 사례이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레플리카셋을 롤아웃 할 디플로이먼트 생성. 레플리카셋은 백그라운드에서 파드를 생성한다. 롤아웃 상태를 체크해서 성공 여부를 확인한다.&lt;/li&gt;
&lt;li&gt;디플로이먼트의 PodTemplateSpec을 업데이트해서&amp;nbsp;파드의 새로운 상태를 선언한다. 새 레플리카셋이 생성되면, 디플로이먼트는 파드를 기존 레플리카셋에서 새로운 레플리카셋으로 속도를 제어하며 이동하는 것을 관리한다. 각각의 새로운 레플리카셋은 디플로이먼트의 수정 버전에 따라 업데이트한다.&lt;/li&gt;
&lt;li&gt;만약 디플로이먼트의 현재 상태가 안정적이지 않은 경우&amp;nbsp;디플로이먼트의 이전 버전으로 롤백한다. 각 롤백은 디플로이먼트의 수정 버전에 따라 업데이트한다.&lt;/li&gt;
&lt;li&gt;더 많은 로드를 위해 디플로이먼트의 스케일 업.&lt;/li&gt;
&lt;li&gt;디플로이먼트 롤아웃 일시 중지로 PodTemplateSpec에 여러 수정 사항을 적용하고, 재개하여 새로운 롤아웃을 시작한다.&lt;/li&gt;
&lt;li&gt;롤아웃이 막혀있는지를 나타내는&amp;nbsp;디플로이먼트 상태를 이용.&lt;/li&gt;
&lt;li&gt;더 이상 필요 없는&amp;nbsp;이전 레플리카셋 정리.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애플리케이션 배포 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션 배포 전략: &lt;a href=&quot;https://thenewstack.io/deployment-strategies/&quot;&gt;https://thenewstack.io/deployment-strategies/&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;Recreate&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pros&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 설정하기가 아주 쉽다.&lt;/li&gt;
&lt;li&gt;애플리케이션 상태가 완전히 갱신된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cons&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다운 타임이 발생한다. 그렇기 때문에 계획된 다운 타임을 사용하여 특정 시간에 서비스를 사용하지 못하게 하고 앱을 업데이트 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;Ramped(롤링 업데이트)&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카셋이 공존하고있는 상태로 하나씩 순차적으로 업데이트하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pros&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 설정하기가 아주 쉽다.&lt;/li&gt;
&lt;li&gt;업데이트가 인스턴스 간에 천천히 릴리스된다.&lt;/li&gt;
&lt;li&gt;데이터 재조정을 처리할 수 있는 상태 저장 애플리케이션에 편리하다.&lt;/li&gt;
&lt;li&gt;무중단 시스템이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cons&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;롤아웃/롤백에 시간이 걸린다.&lt;/li&gt;
&lt;li&gt;API 가 변경되는 경우 API 를 지원하는 데 문제가 발생한다.&lt;/li&gt;
&lt;li&gt;트래픽을 컨트롤할 방법이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;Blue/Green&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recreate 와 비슷하지만 업데이트할 버전의 리소스를 미리 만들어놓은 후에 버전 업그레이드를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pros&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;롤아웃/롤백을 즉각적으로 할 수 있다.&lt;/li&gt;
&lt;li&gt;버전 관리 문제를 피할 수 있고, 전체 애플리케이션 상태가 한 번에 변경된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cons&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스를 더 많이 사용하여 비용이 많이 든다.&lt;/li&gt;
&lt;li&gt;프로덕션에 배포하기 전에 전체 플랫폼에 대한 적절한 테스트를 수행해야 한다.&lt;/li&gt;
&lt;li&gt;stateful 애플리케이션을 처리하는 것이 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;Canary&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 트래픽을 업데이트하려는 버전으로 보내고 테스팅을 하며 문제가 있으면 해결하고 문제가 없으면 트래픽을 더 늘리는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pros&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일부 사용자를 위해 출시된 버전입니다.&lt;/li&gt;
&lt;li&gt;오류나 성능 모니터링 하기에 매우 좋다.&lt;/li&gt;
&lt;li&gt;롤백이 매우 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cons&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;롤아웃이 매우 느리다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;A/B testing&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A/B 테스트 배포는 특정 종류를 테스팅할 때 사용한다. 브라우저 쿠키, 쿼리 파라미터, 지리적 위 등을 사용하여 다른 버전으로 라우팅되게 한다. 예를 들어, 모바일 사용자와 데스크톱 사용자가 사용하는 배포 버전을 다르게 하여 테스팅하는 방식이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pros&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 버전이 병렬로 실행된다.&lt;/li&gt;
&lt;li&gt;트래픽 분산을 완벽하게 제어한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cons&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;intelligent 로드 밸런서가 필요하다.&lt;/li&gt;
&lt;li&gt;주어진 세션에 대한 오류를 해결하기 어렵기 때문에 분산 추적이 필수이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;Shadow&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서가 클라이언트로부터 받은 트래픽을 현재 버전과 업데이트 하려는 버전으로 모두 전송된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pros&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로덕션 레벨에서 받은 트래픽으로 업그레이드 버전의 성능을 테스트 할 수 있다.&lt;/li&gt;
&lt;li&gt;사용자에게 영향이 없다.&lt;/li&gt;
&lt;li&gt;애플리케이션의 안정성과 성능이 요구 사항을 충족할 때까지 롤아웃이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cons&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 배의 리소스가 필요하므로 비용이 많이 든다.&lt;/li&gt;
&lt;li&gt;실제 사용자 테스트가 아니기 때문에 오해의 소지가 있다.&lt;/li&gt;
&lt;li&gt;설정이 복잡하다.&lt;/li&gt;
&lt;li&gt;특정 경우에 모의 서비스가 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n8FBn/btrDowmxTxo/18a45topjgDA3N8END0Hk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n8FBn/btrDowmxTxo/18a45topjgDA3N8END0Hk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n8FBn/btrDowmxTxo/18a45topjgDA3N8END0Hk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn8FBn%2FbtrDowmxTxo%2F18a45topjgDA3N8END0Hk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;845&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;845&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디플로이먼트 Rollout&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-lb
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: myweb-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb:v1.0
          ports:
            - containerPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deployment &amp;rarr; RS &amp;rarr; Pod&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;kubectl rollout status deploy myweb-deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;kubectl rollout history deploy myweb-deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl set image deployments myweb-deploy myweb=ghcr.io/c1t1d0s7/go-myweb:v2.0 --record
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디플로이먼트 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.strategy&amp;nbsp;는 이전 파드를 새로운 파드로 대체하는 전략을 명시한다.&amp;nbsp;.spec.strategy.type&amp;nbsp;은 &quot;재생성&quot; 또는 &quot;롤링업데이트&quot;가 될 수 있다. &quot;롤링업데이트&quot;가 기본값이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;디플로이먼트 롤링 업데이트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트는&amp;nbsp;.spec.strategy.type==RollingUpdate&amp;nbsp;이면 파드를 롤링 업데이트 방식으로 업데이트 한다.&amp;nbsp;maxUnavailable&amp;nbsp;와&amp;nbsp;maxSurge&amp;nbsp;를 명시해서 롤링 업데이트 프로세스를 제어할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최대 불가(Max Unavailable)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.strategy.rollingUpdate.maxUnavailable&amp;nbsp;은 업데이트 프로세스 중에 사용할 수 없는 최대 파드의 수를 지정하는 선택적 필드이다. 이 값은 절대 숫자(예: 5) 또는 의도한 파드 비율(예: 10%)이 될 수 있다. 절대 값은 내림해서 백분율로 계산한다. 만약&amp;nbsp;.spec.strategy.rollingUpdate.maxSurge&amp;nbsp;가 0이면 값이 0이 될 수 없다. 기본 값은 25% 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이 값을 30%로 설정하면 롤링업데이트 시작시 즉각 이전 레플리카셋의 크기를 의도한 파드 중 70%를 스케일 다운할 수 있다. 새 파드가 준비되면 기존 레플리카셋을 스케일 다운할 수 있으며, 업데이트 중에 항상 사용 가능한 전체 파드의 수는 의도한 파드의 수의 70% 이상이 되도록 새 레플리카셋을 스케일 업할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최대 서지(Max Surge)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.strategy.rollingUpdate.maxSurge&amp;nbsp;는 의도한 파드의 수에 대해 생성할 수 있는 최대 파드의 수를 지정하는 선택적 필드이다. 이 값은 절대 숫자(예: 5) 또는 의도한 파드 비율(예: 10%)이 될 수 있다.&amp;nbsp;MaxUnavailable&amp;nbsp;값이 0이면 이 값은 0이 될 수 없다. 절대 값은 올림해서 백분율로 계산한다. 기본 값은 25% 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이 값을 30%로 설정하면 롤링업데이트 시작시 새 레플리카셋의 크기를 즉시 조정해서 기존 및 새 파드의 전체 갯수를 의도한 파드의 130%를 넘지 않도록 한다. 기존 파드가 죽으면 새로운 래플리카셋은 스케일 업할 수 있으며, 업데이트하는 동안 항상 실행하는 총 파드의 수는 최대 의도한 파드의 수의 130%가 되도록 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 최대 서지 값이 많으면 많을수록 기존의 파드를 삭제하지 않고 서지 값만큼 Rollout 이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: myweb-deploy
  annotations:
    kubernetes.io/change-cause: &quot;Go Myweb version from 2 to 4&quot;
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailabe: 1
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb:v4.0
          ports:
            - containerPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;진행 기한 시간(초)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.progressDeadlineSeconds&amp;nbsp;는 디플로어먼트가 표면적으로&amp;nbsp;type: Progressing,&amp;nbsp;status: &quot;False&quot;의 상태 그리고 리소스가&amp;nbsp;reason: ProgressDeadlineExceeded&amp;nbsp;상태로&amp;nbsp;진행 실패를 보고하기 전에 디플로이먼트가 진행되는 것을 대기시키는 시간(초)를 명시하는 선택적 필드이다. 디플로이먼트 컨트롤러는 디플로이먼트를 계속 재시도 한다. 기본값은 600(초)이다. 미래에 자동화된 롤백이 구현된다면 디플로이먼트 컨트롤러는 상태를 관찰하고, 그 즉시 디플로이먼트를 롤백할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 명시된다면 이 필드는&amp;nbsp;.spec.minReadySeconds&amp;nbsp;보다 커야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최소 대기 시간(초)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.minReadySeconds&amp;nbsp;는 새롭게 생성된 파드의 컨테이너가 어떤 것과도 충돌하지 않고 사 용할 수 있도록 준비되어야 하는 최소 시간(초)을 지정하는 선택적 필드이다. 이 기본 값은 0이다(파드는 준비되는 즉시 사용할 수 있는 것으로 간주됨). 파드가 준비되었다고 간주되는 시기에 대한 자세한 내용은&amp;nbsp;컨테이너 프로브를 참조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로브를 사용한다면 굳이 세팅할 필요는 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;수정 버전 기록 제한&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트의 수정 버전 기록은 자신이 컨트롤하는 레플리카셋에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.revisionHistoryLimit&amp;nbsp;은 롤백을 허용하기 위해 보존할 이전 레플리카셋의 수를 지정하는 선택적 필드이다. 이 이전 레플리카셋은&amp;nbsp;etcd&amp;nbsp;의 리소스를 소비하고,&amp;nbsp;kubectl get rs&amp;nbsp;의 결과를 가득차게 만든다. 각 디플로이먼트의 구성은 디플로이먼트의 레플리카셋에 저장된다. 이전 레플리카셋이 삭제되면 해당 디플로이먼트 수정 버전으로 롤백할 수 있는 기능이 사라진다. 기본적으로 10개의 기존 레플리카셋이 유지되지만 이상적인 값은 새로운 디플로이먼트의 빈도와 안정성에 따라 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더욱 구체적으로 이 필드를 0으로 설정하면 레플리카가 0이 되며 이전 레플리카셋이 정리된다. 이 경우, 새로운 디플로이먼트 롤아웃을 취소할 수 없다. 새로운 디플로이먼트 롤아웃은 수정 버전 이력이 정리되기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;일시 정지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.paused&amp;nbsp;는 디플로이먼트를 일시 중지나 재개하기 위한 선택적 부울 필드이다. 일시 중지 된 디플로이먼트와 일시 중지 되지 않은 디플로이먼트 사이의 유일한 차이점은 일시 중지된 디플로이먼트는 PodTemplateSpec에 대한 변경 사항이 일시중지 된 경우 새 롤아웃을 트리거 하지 않는다. 디플로이먼트는 생성시 기본적으로 일시 중지되지 않는다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/247</guid>
      <comments>https://ssunw.tistory.com/entry/deployment#entry247comment</comments>
      <pubDate>Sun, 29 May 2022 15:19:02 +0900</pubDate>
    </item>
    <item>
      <title>Ansible, Terraform, Packer 로 인프라 구현</title>
      <link>https://ssunw.tistory.com/entry/Ansible-Terraform-Packer-%EB%A1%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B5%AC%ED%98%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/seongwoo-choi/ansible_terraform_packer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/seongwoo-choi/ansible_terraform_packer&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;AWS&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구성도&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;무제.png&quot; data-origin-width=&quot;1071&quot; data-origin-height=&quot;691&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xsRAS/btrA5azqUie/0xmhCwkydAokDMzFDtC1i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xsRAS/btrA5azqUie/0xmhCwkydAokDMzFDtC1i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xsRAS/btrA5azqUie/0xmhCwkydAokDMzFDtC1i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxsRAS%2FbtrA5azqUie%2F0xmhCwkydAokDMzFDtC1i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1071&quot; height=&quot;691&quot; data-filename=&quot;무제.png&quot; data-origin-width=&quot;1071&quot; data-origin-height=&quot;691&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배스천 호스트와 관리 노드를 오토 스케일링 그룹으로 관리하고 EFS 를 사용해서 /var/www/html/wordpress 를 관리하는 인프라를 구현하고자 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS 사용 서비스&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Security Group&lt;/li&gt;
&lt;li&gt;RDS&lt;/li&gt;
&lt;li&gt;ASG, Launch Template, ALB, Target Group&lt;/li&gt;
&lt;li&gt;EFS&lt;/li&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;키 페어 생성&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;resource &quot;aws_key_pair&quot; &quot;app_server_key&quot; {  
  key_name   = &quot;app_server_key&quot;  
  public_key = file(&quot;/Users/csw/.ssh/id_rsa.pub&quot;)  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file 함수를 사용하여 id_rsa.pub 파일을 공개키로 등록을 했고 해당 키네임을 app_server_key 로 작성했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VPC 생성&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# NAT 게이트 용 EIP  
resource &quot;aws_eip&quot; &quot;nat&quot; {  
  count = 1  
  vpc   = true  
}  

module &quot;app_vpc&quot; {  
  source = &quot;terraform-aws-modules/vpc/aws&quot;  
  #  version = &quot;~&amp;gt; 3.0&quot;  

  name = &quot;app_vpc&quot;  
  cidr = &quot;10.0.0.0/16&quot;  

  azs              = [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;, &quot;ap-northeast-2c&quot;, &quot;ap-northeast-2d&quot;]  
  public_subnets   = [&quot;10.0.1.0/24&quot;, &quot;10.0.2.0/24&quot;, &quot;10.0.3.0/24&quot;, &quot;10.0.4.0/24&quot;]  
  private_subnets  = [&quot;10.0.10.0/24&quot;, &quot;10.0.11.0/24&quot;, &quot;10.0.12.0/24&quot;, &quot;10.0.13.0/24&quot;]  
  database_subnets = [&quot;10.0.20.0/24&quot;, &quot;10.0.21.0/24&quot;, &quot;10.0.22.0/24&quot;, &quot;10.0.23.0/24&quot;]  

  # 하나의 가용 영역에 한 개의 nat 게이트웨이 설정  
  enable_nat_gateway     = true  
  single_nat_gateway     = true  
  one_nat_gateway_per_az = false  
  reuse_nat_ips          = false  
  external_nat_ip_ids    = aws_eip.nat.*.id  

  create_database_subnet_group = true  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC module 을 사용하면 한 개의 블록에서 VPC, subnets, az, nat 등 네트워크 설정들을 한 번에 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAT 게이트웨이를 위해 탄력적 IP 를 resource 블록에서 한 개 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC 모듈에서 가용 영역을 설정하고 public subnet, private subnet, db subnet 의 CIDR 블록을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 하나의 가용 영역에 한 개의 NAT 게이트웨이를 추가할 것이기 때문에 nat 게이트웨이에 대한 설정을 해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enable_nat_gateway 은 nat 게이트웨이를 사용할 것인지 묻는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;single_nat_gateway 는 nat 게이트웨이를 하나만 사용할 것인지 묻는 것이다. one_nat_gateway_per_az 는 각 가용 영역 마다 nat 게이트웨이를 하나 씩 부착할 것인지 묻는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reuse_nat_ips 는 nat 게이트웨이에 부착된 ip 를 재사용할 지에 대한 여부이다. external_nat_ip_ids 는 nat 게이트웨이에 부착할 탄력적 ip 주소의 id 를 적으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;create_database_subnet_group 은 database 용 서브넷을 생성할 것인지에 대한 여부를 묻는 것 이다. 이 VPC 에서 데이터베이스 서브넷을 생성할 것이기 때문에 true 로 설정했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;vpc 생성.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLvoxV/btrA5szQbr9/UzzOWdnMcslUWhfytnKgBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLvoxV/btrA5szQbr9/UzzOWdnMcslUWhfytnKgBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLvoxV/btrA5szQbr9/UzzOWdnMcslUWhfytnKgBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLvoxV%2FbtrA5szQbr9%2FUzzOWdnMcslUWhfytnKgBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;225&quot; data-filename=&quot;vpc 생성.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 VPC 는 위 사진과 같이 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;서브넷 생성.png&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0qITo/btrA5tS4uJK/NnAtz12vitzzKPtZbDaZ5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0qITo/btrA5tS4uJK/NnAtz12vitzzKPtZbDaZ5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0qITo/btrA5tS4uJK/NnAtz12vitzzKPtZbDaZ5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0qITo%2FbtrA5tS4uJK%2FNnAtz12vitzzKPtZbDaZ5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1076&quot; height=&quot;496&quot; data-filename=&quot;서브넷 생성.png&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;서브넷도 코드로 작성한 값들이 제대로 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;라우팅 테이블 생성.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btcxKQ/btrA34frJcE/Uv2OBxkU0R2HaGeoejuRPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btcxKQ/btrA34frJcE/Uv2OBxkU0R2HaGeoejuRPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btcxKQ/btrA34frJcE/Uv2OBxkU0R2HaGeoejuRPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtcxKQ%2FbtrA34frJcE%2FUv2OBxkU0R2HaGeoejuRPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1332&quot; height=&quot;274&quot; data-filename=&quot;라우팅 테이블 생성.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;라우팅 테이블은 디폴트 값이 설정되어 있어 자동으로 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;igw.png&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZhczI/btrA5rVeUtE/LT9Bmc2ZxKR5PDwb3z3fZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZhczI/btrA5rVeUtE/LT9Bmc2ZxKR5PDwb3z3fZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZhczI/btrA5rVeUtE/LT9Bmc2ZxKR5PDwb3z3fZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZhczI%2FbtrA5rVeUtE%2FLT9Bmc2ZxKR5PDwb3z3fZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1124&quot; height=&quot;179&quot; data-filename=&quot;igw.png&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;nat.png&quot; data-origin-width=&quot;1591&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csCMSO/btrA4QVwtNR/yOniADNKgPWoRJyALFcaB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csCMSO/btrA4QVwtNR/yOniADNKgPWoRJyALFcaB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csCMSO/btrA4QVwtNR/yOniADNKgPWoRJyALFcaB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsCMSO%2FbtrA4QVwtNR%2FyOniADNKgPWoRJyALFcaB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1591&quot; height=&quot;187&quot; data-filename=&quot;nat.png&quot; data-origin-width=&quot;1591&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷 게이트웨이가 생성된 것을 확인할 수 있고, 한 개의 가용 영역에서 EIP 를 가진채로 NAT 게이트웨이가 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Security Group 생성&lt;/h3&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;aws_security_group&quot; &quot;bastion_sg&quot; {  
  name   = &quot;bastion_security_group&quot;  
  vpc_id = module.app_vpc.vpc_id  

  ingress {  
    from_port   = 22  
    to_port     = 22  
    protocol    = &quot;tcp&quot;  
    cidr_blocks = [file(&quot;./my_ip.txt&quot;)]  
  }  

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

  tags = {  
    Name = &quot;bastion_security_group&quot;  
  }  
}  

resource &quot;aws_security_group&quot; &quot;node_sg&quot; {  
  name   = &quot;node_security_group&quot;  
  vpc_id = module.app_vpc.vpc_id  

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

  ingress {  
    from_port       = 22  
    to_port         = 22  
    protocol        = &quot;tcp&quot;  
    security_groups = [aws_security_group.bastion_sg.id]  
  }  

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

resource &quot;aws_security_group&quot; &quot;alb_sg&quot; {  
  name   = &quot;alb_security_group&quot;  
  vpc_id = module.app_vpc.vpc_id  

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

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

  tags = {  
    Name = &quot;alb_security_group&quot;  
  }  
}  

resource &quot;aws_security_group&quot; &quot;node_efs_sg&quot; {  
  name   = &quot;node_efs_security_group&quot;  
  vpc_id = module.app_vpc.vpc_id  

  ingress {  
    from_port       = 2049  
    protocol        = &quot;tcp&quot;  
    to_port         = 2049  
    security_groups = [aws_security_group.node_sg.id]  
  }  

  egress {  
    from_port       = 0  
    protocol        = &quot;-1&quot;  
    to_port         = 0  
    security_groups = [aws_security_group.node_sg.id]  
  }  

  tags = {  
    Name = &quot;node_efs_sg&quot;  
  }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws_security_group 이라는 리소스 블록을 사용하여 보안 그룹을 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 보안 그룹에 이름 태그를 달아두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ingress 는 인바운드 규칙이고 egress 는 아웃바운드 규칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ingress 에서 살펴볼 것은 security_groups 와 cidr_blocks 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;security_groups 는 보안 그룹을 src 로 설정할 수 있고, cidr_blocks 은 CIDR 블록만 입력할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, vpc_id 를 입력하여 해당 보안 그룹을 원하는 VPC 에 설정할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;보안 그룹.png&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eHFa8X/btrA1AlLU3Z/tQrCYINZEHPF1uQ5VNBE71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eHFa8X/btrA1AlLU3Z/tQrCYINZEHPF1uQ5VNBE71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eHFa8X/btrA1AlLU3Z/tQrCYINZEHPF1uQ5VNBE71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeHFa8X%2FbtrA1AlLU3Z%2FtQrCYINZEHPF1uQ5VNBE71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1074&quot; height=&quot;318&quot; data-filename=&quot;보안 그룹.png&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 생성된 보안 그룹은 위와 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RDS 생성&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;module &quot;rds_allow&quot; {  
  source = &quot;terraform-aws-modules/security-group/aws&quot;  

  name            = &quot;rds_allow_sg&quot;  
  description     = &quot;rds&quot;  
  vpc_id          = module.app_vpc.vpc_id  
  use_name_prefix = false  

  ingress_with_source_security_group_id = [  
    {      
      rule                     = &quot;mysql-tcp&quot;  
      source_security_group_id = aws_security_group.node_sg.id  
    }  
  ]  

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

# 템플릿(프리티어), 디비 인스턴스 이름  
# 디비 마스터 사용자 이름, 비밀번호  
# 스토리지  
# 가용성 및 내구성(다중 AZ)  
# vpc 세팅 및 서브넷 설정, 퍼블릭 액세스 설정, VPC 보안 그룹 세팅  
# 데이터 베이스 인증 옵션(암호인증)  
# 초기 데이터베이스 이름 설정, 백업, 스냅샷, 디비 인스턴스 암호화 옵션 등  
module &quot;db&quot; {  
  source = &quot;terraform-aws-modules/rds/aws&quot;  

  identifier = &quot;wordpress&quot;  

  engine               = &quot;mysql&quot;  
  engine_version       = &quot;8.0.20&quot;  
  family               = &quot;mysql8.0&quot;  
  major_engine_version = &quot;8.0&quot;  
  instance_class       = &quot;db.t3.micro&quot;  

  allocated_storage     = 20  
  max_allocated_storage = 100  

  db_name                = &quot;wordpress&quot;  
  username               = &quot;admin&quot;  
  password               = &quot;chma0326&quot;  
  create_random_password = false  
  port                   = &quot;3306&quot;  

  # DB subnet group  
  multi_az               = true  
  subnet_ids             = module.app_vpc.database_subnets  
  vpc_security_group_ids = [module.rds_allow.security_group_id]  
  create_db_subnet_group = false  
  db_subnet_group_name   = module.app_vpc.database_subnet_group_name  

  # 삭제 시 스냅샷을 생성할지에 대한 여부, true 로 설정하여 스냅샷 생성 안 함  
  skip_final_snapshot = true  


  # 모니터링 환경 설정
  maintenance_window              = &quot;Mon:00:00-Mon:03:00&quot;  
  backup_window                   = &quot;03:00-06:00&quot;  
  enabled_cloudwatch_logs_exports = [&quot;general&quot;]  
  create_cloudwatch_log_group     = true  
  backup_retention_period         = 0  
  skip_final_snapshot             = true  
  deletion_protection             = false  

  parameters = [  
    { 
      name  = &quot;character_set_client&quot;  
      value = &quot;utf8mb4&quot;  
    },  
    {  
      name  = &quot;character_set_server&quot;  
      value = &quot;utf8mb4&quot;  
    }  
  ]  

  depends_on = [  
    module.app_vpc  
  ]  
}  

resource &quot;null_resource&quot; &quot;rds_endpoint_info&quot; {  
  provisioner &quot;local-exec&quot; {  
    command = &quot;echo database_host: ${module.db.db_instance_endpoint} &amp;gt; /Users/csw/Downloads/terraform_anbile_packer/vars/rds_endpoint_info.yml&quot;  
  }  

  depends_on = [  
    module.db  
  ]  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 RDS 인스턴스 보안 그룹을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC 세팅과 이름을 설정해줬고 node_sg 보안 그룹에서 온 트래픽들이 데이터 베이스에 접속할 수 있도록 3306 포트를 열어놓았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 DB 설정은 RDS 모듈을 사용하였다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;  source = &quot;terraform-aws-modules/rds/aws&quot;  

  identifier = &quot;wordpress&quot;  

  engine               = &quot;mysql&quot;  
  engine_version       = &quot;8.0.20&quot;  
  family               = &quot;mysql8.0&quot;  
  major_engine_version = &quot;8.0&quot;  
  instance_class       = &quot;db.t3.micro&quot;  

  allocated_storage     = 20  
  max_allocated_storage = 100  &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identifier 는 생성되는 RDS 의 이름이고 engine, engine_version 은 어떤 데이터베이스를 사용할 것인지와 몇 버전을 사용할 것인지를 작성하는 곳이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;instance_class 에서 사용하고자 하는 인스턴스 유형을 작성하면 되고, allocated_storage 와 max_allocated_storage 는 디비 인스턴스의 초기 용량 크기와 최대로 커질 수 있는 용량 크기를 정하는 곳이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;  db_name                = &quot;wordpress&quot;  
  username               = &quot;admin&quot;  
  password               = &quot;chma0326&quot;  
  create_random_password = false  
  port                   = &quot;3306&quot;  &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db_name 은 데이터베이스 이름을, username 은 루트 사용자 이름을, password 는 루트 사용자 비밀번호를, create_random_password 는 랜덤하게 비밀번호를 생성할 것인지에 대한 여부를, port 어떤 포트로 트래픽이 들어오게 할 것인지를 작성하는 곳이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;  # DB subnet group  
  # multi_az             = true  
  subnet_ids             = module.app_vpc.database_subnets  
  vpc_security_group_ids = [module.rds_allow.security_group_id]  
  create_db_subnet_group = false  
  db_subnet_group_name   = module.app_vpc.database_subnet_group_name  

  # 삭제 시 스냅샷을 생성할지에 대한 여부, true 로 설정하여 스냅샷 생성 안 함  
  skip_final_snapshot = true  &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;multi_az 를 true 로 하여 마스터 슬레이브 형태로 디비 인스턴스를 띄울수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subnet_ids 에 어떤 CIDR 블록들을 DB 서브넷으로 사용할 것인지 설정하고, vpc_security_group_ids 에 rds 보안 그룹을 세팅하고, 이미 VPC 에서 DB 서브넷을 만들었기 때문에 또 DB 서브넷을 만들 필요가 없다 그래서 create_db_subnet_group 를 false 로 세팅한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;db_subnet_group_name&lt;/b&gt; 을 설정하지 않으면 디폴트 VPC 로 잡히기 때문에 꼭 여기에 DB 서브넷 그룹명을 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;skip_final_snapshot 은 디비 인스턴스 최종 삭제 시에 최종 스냅샷을 생성할 것인지 말 것인지에 대한 여부를 적는 곳으로 디폴트는 false 이다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;  maintenance_window              = &quot;Mon:00:00-Mon:03:00&quot;
  backup_window                   = &quot;03:00-06:00&quot;
  enabled_cloudwatch_logs_exports = [&quot;general&quot;]
  create_cloudwatch_log_group     = true

  backup_retention_period = 0
  skip_final_snapshot     = true
  deletion_protection     = false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설정들은 모두 모니터링에 관련된 설정들이다.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;  parameters = [  
    { 
      name  = &quot;character_set_client&quot;  
      value = &quot;utf8mb4&quot;  
    },  
    {  
      name  = &quot;character_set_server&quot;  
      value = &quot;utf8mb4&quot;  
    }  
  ]  &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디비 서버와 클라이언트에서 utf8 사용하도록 설정&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;resource &quot;null_resource&quot; &quot;rds_endpoint_info&quot; {  
  provisioner &quot;local-exec&quot; {  
    command = &quot;echo database_host: ${module.db.db_instance_endpoint} &amp;gt; /Users/csw/Downloads/terraform_anbile_packer/vars/rds_endpoint_info.yml&quot;  
  }  

  depends_on = [  
    module.db  
  ]  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디비 인스턴스가 생성되고 난 후에 /Users/csw/Downloads/terraform_anbile_packer/vars 디렉토리 밑에 rds_endpoint_info.yml 파일에 생성된 RDS 의 엔드 포인트가 &lt;code&gt;database: RDS엔드포인트&lt;/code&gt; 로 입력되도록 설정한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;database_host: wordpress.c8xnvne1yftk.ap-northeast-2.rds.amazonaws.com:3306&lt;/code&gt; 실제로 이렇게 작성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rds 1.png&quot; data-origin-width=&quot;1594&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxq6mW/btrAUj5H7nJ/POJbU5o48VkZJN6NGHLar0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxq6mW/btrAUj5H7nJ/POJbU5o48VkZJN6NGHLar0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxq6mW/btrAUj5H7nJ/POJbU5o48VkZJN6NGHLar0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxq6mW%2FbtrAUj5H7nJ%2FPOJbU5o48VkZJN6NGHLar0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1594&quot; height=&quot;280&quot; data-filename=&quot;rds 1.png&quot; data-origin-width=&quot;1594&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rds 2.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VBpv9/btrA5bd33gD/K2VLYnvkKNLhnrAocC8vMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VBpv9/btrA5bd33gD/K2VLYnvkKNLhnrAocC8vMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VBpv9/btrA5bd33gD/K2VLYnvkKNLhnrAocC8vMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVBpv9%2FbtrA5bd33gD%2FK2VLYnvkKNLhnrAocC8vMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;586&quot; data-filename=&quot;rds 2.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 디비 인스턴스는 위 사진처럼 생성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EFS 생성&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;aws_efs_file_system&quot; &quot;node_efs_file_system&quot; {  
  creation_token   = &quot;node_file_system&quot;  
  performance_mode = &quot;generalPurpose&quot;  
  encrypted        = &quot;true&quot;  

  tags = {  
    Name = &quot;node_file_system&quot;  
  }  
}  

resource &quot;aws_efs_mount_target&quot; &quot;node_efs_mount_target&quot; {  
  file_system_id  = aws_efs_file_system.node_efs_file_system.id  
  subnet_id       = module.app_vpc.private_subnets[0]  
  security_groups = [aws_security_group.node_efs_sg.id]  
}  

resource &quot;aws_efs_access_point&quot; &quot;node_efs_access_point&quot; {  
  file_system_id = aws_efs_file_system.node_efs_file_system.id  
  root_directory {  
    path = &quot;/var/www/html/wordpress&quot;  
  }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws_efs_file_system 리소스로 efs 파일 시스템을 생성한다. 이름과 퍼포먼스 유형, 보안 여부 등을 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws_efs_mount_target 리소스로 efs 마운트 타겟을 설정한다. 어떤 파일 시스템을 사용할 것인지 어떤 서브넷에 존재하는지 해당 파일 시스템에 마운트 된 타겟에 대해 어떤 트래픽이 들어오고 나가게 할 것인지를 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws_efs_access_point 리소스로 액세스 포인트를 설정한다. 어떤 파일 시스템을 사용할 것인지 어떤 경로가 efs 로 사용될 것인지를 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;파일시스템.png&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wDkKy/btrA3sU92V2/NSpVPhlnYDJstgH5iYkubK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wDkKy/btrA3sU92V2/NSpVPhlnYDJstgH5iYkubK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wDkKy/btrA3sU92V2/NSpVPhlnYDJstgH5iYkubK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwDkKy%2FbtrA3sU92V2%2FNSpVPhlnYDJstgH5iYkubK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1333&quot; height=&quot;276&quot; data-filename=&quot;파일시스템.png&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;액세스 포인트.png&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A94ej/btrA331TiXk/kQASbIbu4Aa7kA4uc7YRJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A94ej/btrA331TiXk/kQASbIbu4Aa7kA4uc7YRJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A94ej/btrA331TiXk/kQASbIbu4Aa7kA4uc7YRJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA94ej%2FbtrA331TiXk%2FkQASbIbu4Aa7kA4uc7YRJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1186&quot; height=&quot;299&quot; data-filename=&quot;액세스 포인트.png&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 시스템과 액세스 포인트는 위 사진처럼 생성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Packer 로 AMI 생성&lt;/h3&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;packer {  
  required_plugins {  
    amazon = {  
      version = &quot;&amp;gt;= 0.0.2&quot;  
      source  = &quot;github.com/hashicorp/amazon&quot;  
    }  
  }}  


source &quot;amazon-ebs&quot; &quot;linux&quot; {  
  region  = &quot;ap-northeast-2&quot;  
  profile = &quot;default&quot;  

  ami_name      = &quot;wordpress_image&quot;  
  instance_type = &quot;t2.micro&quot;  
  source_ami_filter {  
    filters = {  
      name                = var.image_filter  
      root-device-type    = &quot;ebs&quot;  
      virtualization-type = &quot;hvm&quot;  
    }  
    most_recent = true  
    owners      = [&quot;099720109477&quot;]  
  }  ssh_username     = var.ssh_account  
  force_deregister = true  
}  


build {  
  name = &quot;wordpress_image&quot;  
  sources = [  
    &quot;source.amazon-ebs.linux&quot;  
  ]  


  provisioner &quot;ansible&quot; {  
    playbook_file = &quot;/Users/csw/Downloads/terraform_anbile_packer/ansible/web.yml&quot;  
    extra_arguments = [  
      &quot;--become&quot;  
    ]  
    ansible_env_vars = [  
      &quot;ANSIBLE_HOST_KEY_CHECKING=False&quot;,  
    ]  
  }}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ubuntu 18.04 LTS 버전에 /Users/csw/Downloads/terraform_anbile_packer/ansible/web.yml 플레이북 파일을 실행하여 AMI 를 생성한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;- hosts: all  
  become: yes  
  vars_files:  
    - /Users/csw/Downloads/terraform_anbile_packer/vars/rds_endpoint_info.yml  
  roles:  
    - web&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;web.yml 은 위와 같이 구성되어 있고 vars_files 를 사용하여 rds_endpoint_info 파일의 변수들을 가져온다. 그리고 web roles 를 실행시킨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20220501231511.png&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tPESk/btrA33HCkif/v9xrkTD4RC0GvpxydI7Pp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tPESk/btrA33HCkif/v9xrkTD4RC0GvpxydI7Pp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tPESk/btrA33HCkif/v9xrkTD4RC0GvpxydI7Pp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtPESk%2FbtrA33HCkif%2Fv9xrkTD4RC0GvpxydI7Pp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;260&quot; height=&quot;225&quot; data-filename=&quot;Pasted image 20220501231511.png&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 roles 구조는 위와 같이 되어 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;---  
- name: Install Apache Server  
  apt:  
    name: apache2  
    state: present  

- name: Install mysql-client  
  apt:  
    name: mysql-client  
    state: present  

- name: Install php  
  apt:  
    name: &quot;{{ php_packages }}&quot;  
    state: present  

- name: Download Wordpress Archive file  
  get_url:  
    url: &quot;{{ wordpress_url }}&quot;  
    dest: /home/ubuntu  

- name: Decompress Archive file  
  unarchive:  
    src: &quot;/home/ubuntu/{{ wordpress_filename }}&quot;  
    remote_src: yes  
    dest: /var/www/html/  

- name: Copy Database Configure File for Wordpress  
  template:  
    src: wp-config.php.j2  
    dest: /var/www/html/wordpress/wp-config.php  

- name: restart apache2  
  service:  
    name: apache2  
    state: restarted&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tasks 에선 apache2, mysql-client, php 관련 패키지들을 설치하고 wget 을 통해 워드프레스를 다운받은 후 압축해제 한 뒤 로컬 머신에 있는 wp-config.php.j2 파일을 가져가서 wp-config.php 파일로 사용한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;---  
php_packages: php,php-curl,php-xml,php-gd,php-mysql,python3-mysqldb  
wordpress_version: 5.9.3  
wordpress_filename: &quot;wordpress-{{ wordpress_version }}.tar.gz&quot;  
wordpress_url: &quot;https://wordpress.org/{{ wordpress_filename }}&quot;  

# RDS  
database:  
  name: wordpress  
  user: admin  
  pwd: chma0326&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 파일은 위와 같다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;&amp;lt;?php  
// ** Database settings - You can get this info from your web host ** //  
/** The name of the database for WordPress */  
define( 'DB_NAME', '{{ database[&quot;name&quot;] }}' );  

/** Database username */  
define( 'DB_USER', '{{ database[&quot;user&quot;] }}' );  

/** Database password */  
define( 'DB_PASSWORD', '{{ database[&quot;pwd&quot;] }}' );  

/** Database hostname */  
define( 'DB_HOST', '{{ database_host }}' );  

/** Database charset to use in creating database tables. */  
define( 'DB_CHARSET', 'utf8' );  

/** The database collate type. Don't change this if in doubt. */  
define( 'DB_COLLATE', '' );  

define( 'AUTH_KEY',         'put your unique phrase here' );  
define( 'SECURE_AUTH_KEY',  'put your unique phrase here' );  
define( 'LOGGED_IN_KEY',    'put your unique phrase here' );  
define( 'NONCE_KEY',        'put your unique phrase here' );  
define( 'AUTH_SALT',        'put your unique phrase here' );  
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );  
define( 'LOGGED_IN_SALT',   'put your unique phrase here' );  
define( 'NONCE_SALT',       'put your unique phrase here' );  

$table_prefix = 'wp_';  

define( 'WP_DEBUG', false );  

if ( ! defined( 'ABSPATH' ) ) {  
        define( 'ABSPATH', __DIR__ . '/' );  
}  

require_once ABSPATH . 'wp-settings.php';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿 파일은 위와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ami.png&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPTBHD/btrA4Q2g2KX/If691AXKsZvf03X08XL7f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPTBHD/btrA4Q2g2KX/If691AXKsZvf03X08XL7f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPTBHD/btrA4Q2g2KX/If691AXKsZvf03X08XL7f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPTBHD%2FbtrA4Q2g2KX%2FIf691AXKsZvf03X08XL7f0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1197&quot; height=&quot;161&quot; data-filename=&quot;ami.png&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;packer 를 사용하여 위 사진처럼 AMI 를 생성할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Bation Host 생성&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 런치 템플릿, 오토 스케일링 그룹, alb  

resource &quot;aws_launch_template&quot; &quot;bastion_host_template&quot; {  

  name          = &quot;bastion_host_template&quot;  
  image_id      = &quot;ami-02e05347a68e9c76f&quot;  
  instance_type = &quot;t3.small&quot;  
  key_name      = aws_key_pair.app_server_key.key_name  

  vpc_security_group_ids = [aws_security_group.bastion_sg.id]  

  tags = {  
    Name = &quot;bastion_host_template&quot;  
  }  

  depends_on = [  
    aws_security_group.bastion_sg  
  ]  
}  

resource &quot;aws_autoscaling_group&quot; &quot;bastion_host_asg&quot; {  
  name              = &quot;bastion_host_asg&quot;  
  health_check_type = &quot;ELB&quot;  

  vpc_zone_identifier = module.app_vpc.public_subnets  
  min_size            = 1  
  max_size            = 3  

  launch_template {  
    id = aws_launch_template.bastion_host_template.id  
  }  
}  

resource &quot;aws_autoscaling_policy&quot; &quot;bastion_host_asg_policy&quot; {  
  name                   = &quot;bastion_host_asg_policy&quot;  
  policy_type            = &quot;TargetTrackingScaling&quot;  
  autoscaling_group_name = aws_autoscaling_group.bastion_host_asg.name  

  target_tracking_configuration {  
    predefined_metric_specification {  
      predefined_metric_type = &quot;ASGAverageCPUUtilization&quot;  
    }  
    target_value = 40.0  
  }  
}  

resource &quot;aws_lb&quot; &quot;bastion_host_alb&quot; {  
  name               = &quot;bastion-host-alb&quot;  
  internal           = false  
  load_balancer_type = &quot;application&quot;  
  security_groups    = [aws_security_group.alb_sg.id]  
  subnets            = module.app_vpc.public_subnets  

  tags = {  
    Name = &quot;bastion_host_alb&quot;  
  }  
}  

resource &quot;aws_lb_listener&quot; &quot;bastion_host_alb_listener&quot; {  
  load_balancer_arn = aws_lb.bastion_host_alb.arn  

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

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

  tags = {  
    Name = &quot;bastion_host_alb_listener&quot;  
  }  
}  

resource &quot;aws_lb_target_group&quot; &quot;bastion_host_target_group&quot; {  
  name     = &quot;bastion-host-target-group&quot;  
  port     = 80  
  protocol = &quot;HTTP&quot;  
  vpc_id   = module.app_vpc.vpc_id  

  health_check {  
    port     = &quot;traffic-port&quot;  
    protocol = &quot;HTTP&quot;  
    path     = &quot;/&quot;  
  }  

  tags = {  
    Name = &quot;bastion-host-target-group&quot;  
  }  
}  

resource &quot;aws_lb_target_group_attachment&quot; &quot;bastion_host_target_group_attachment&quot; {  
  count = 1  

  port             = 80  
  target_group_arn = aws_lb_target_group.bastion_host_target_group.arn  
  target_id        = element(data.aws_instances.bastion_host_instances.ids, count.index)  
  depends_on       = [data.aws_instances.bastion_host_instances]  
}  

data &quot;aws_instances&quot; &quot;bastion_host_instances&quot; {  

  instance_tags = {  
    &quot;aws:autoscaling:groupName&quot; = aws_autoscaling_group.bastion_host_asg.name  
  }  
  depends_on = [aws_launch_template.bastion_host_template]  

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;aws_launch_template&quot; &quot;bastion_host_template&quot; {  

  name          = &quot;bastion_host_template&quot;  
  image_id      = &quot;ami-02e05347a68e9c76f&quot;  
  instance_type = &quot;t3.small&quot;  
  key_name      = aws_key_pair.app_server_key.key_name  

  vpc_security_group_ids = [aws_security_group.bastion_sg.id]  

  tags = {  
    Name = &quot;bastion_host_template&quot;  
  }  

  depends_on = [  
    aws_security_group.bastion_sg  
  ]  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 aws_launch_template 을 생성한다. 이미지는 amazon_linux 이미지를 사용했고 인스턴스 유형은 t3.small 을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 인스턴스에 접속하기 위한 키 페어를 등록을 해야 하는데 미리 만들어놓은 키 페어의 이름을 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 그룹도 미리 만들어놓은 보안 그룹을 작성해준다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;aws_autoscaling_group&quot; &quot;bastion_host_asg&quot; {  
  name              = &quot;bastion_host_asg&quot;  
  health_check_type = &quot;ELB&quot;  

  vpc_zone_identifier = module.app_vpc.public_subnets  
  min_size            = 1  
  max_size            = 3  

  launch_template {  
    id = aws_launch_template.bastion_host_template.id  
  }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오토 스케일링 그룹을 생성한다. 헬스 체크 타입을 지정해주고, 어떤 서브넷에서 오토 스케일링 그룹이 위치할 지 지정하고 최소 최대 갯수를 지정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 런치 템플릿을 사용할 지 id 값을 설정해준다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;aws_autoscaling_policy&quot; &quot;bastion_host_asg_policy&quot; {  
  name                   = &quot;bastion_host_asg_policy&quot;  
  policy_type            = &quot;TargetTrackingScaling&quot;  
  autoscaling_group_name = aws_autoscaling_group.bastion_host_asg.name  

  target_tracking_configuration {  
    predefined_metric_specification {  
      predefined_metric_type = &quot;ASGAverageCPUUtilization&quot;  
    }  
    target_value = 40.0  
  }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오토 스케일링 정책을 생성한다. 이름과 정책 타입을 설정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 오토 스케일링 그룹에 적용할지 설정하기 위해 오토 스케일링 그룹 이름도 설정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 평균 사용률이 40% 가 초과하면 인스턴스가 생성되도록 설정했다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;aws_lb&quot; &quot;bastion_host_alb&quot; {  
  name               = &quot;bastion-host-alb&quot;  
  internal           = false  
  load_balancer_type = &quot;application&quot;  
  security_groups    = [aws_security_group.alb_sg.id]  
  subnets            = module.app_vpc.public_subnets  

  tags = {  
    Name = &quot;bastion_host_alb&quot;  
  }  
}  

resource &quot;aws_lb_listener&quot; &quot;bastion_host_alb_listener&quot; {  
  load_balancer_arn = aws_lb.bastion_host_alb.arn  

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

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

  tags = {  
    Name = &quot;bastion_host_alb_listener&quot;  
  }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bastion host 용 application load balancer 를 생성한다. 보안 그룹, 로드 밸런서 타입, 이름, 내부용인지 외부용인지, 어떤 서브넷을 사용할 것인지에 대한 설정을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서 리스너를 설정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;80 번 포트로 HTTP 프로토콜로 트래픽이 로드 밸런서로 들어올 경우에 기본적으로 대상 그룹으로 포워딩하게 설정했다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;aws_lb_target_group&quot; &quot;bastion_host_target_group&quot; {  
  name     = &quot;bastion-host-target-group&quot;  
  port     = 80  
  protocol = &quot;HTTP&quot;  
  vpc_id   = module.app_vpc.vpc_id  

  health_check {  
    port     = &quot;traffic-port&quot;  
    protocol = &quot;HTTP&quot;  
    path     = &quot;/&quot;  
  }  

  tags = {  
    Name = &quot;bastion-host-target-group&quot;  
  }  
}  

resource &quot;aws_lb_target_group_attachment&quot; &quot;bastion_host_target_group_attachment&quot; {  
  count = 1  

  port             = 80  
  target_group_arn = aws_lb_target_group.bastion_host_target_group.arn  
  target_id        = element(data.aws_instances.bastion_host_instances.ids, count.index)  
  depends_on       = [data.aws_instances.bastion_host_instances]  
}  

data &quot;aws_instances&quot; &quot;bastion_host_instances&quot; {  

  instance_tags = {  
    &quot;aws:autoscaling:groupName&quot; = aws_autoscaling_group.bastion_host_asg.name  
  }  
  depends_on = [aws_launch_template.bastion_host_template]  

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서 대상 그룹을 생성한다. HTTP 프로토콜로 80번 포트로 오는 요청만 받아들이도록 설정했고 헬스 체크를 하도록 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 대상 그룹에 인스턴스를 설정하기 위해서 data 소스로 인스턴스를 가져오고 해당 인스턴스를 대상 그룹에 붙여준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Node&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 런치 템플릿, 오토 스케일링 그룹, alb  

resource &quot;aws_launch_template&quot; &quot;node_template&quot; {  
  name          = &quot;node_template&quot;  
  image_id      = data.aws_ami.wordpress_image.id  
  instance_type = &quot;t3.small&quot;  
  key_name      = aws_key_pair.app_server_key.key_name  

  vpc_security_group_ids = [aws_security_group.node_sg.id]  

  tags = {  
    Name = &quot;node_template&quot;  
  }  
}  

resource &quot;aws_autoscaling_group&quot; &quot;node_asg&quot; {  
  name              = &quot;node_asg&quot;  
  health_check_type = &quot;ELB&quot;  

  vpc_zone_identifier = module.app_vpc.private_subnets  
  min_size            = 1  
  max_size            = 3  

  launch_template {  
    id = aws_launch_template.node_template.id  
  }  
}  

resource &quot;aws_autoscaling_policy&quot; &quot;node_asg_policy&quot; {  
  name                   = &quot;node_asg_policy&quot;  
  policy_type            = &quot;TargetTrackingScaling&quot;  
  autoscaling_group_name = aws_autoscaling_group.node_asg.name  

  target_tracking_configuration {  
    predefined_metric_specification {  
      predefined_metric_type = &quot;ASGAverageCPUUtilization&quot;  
    }  

    target_value = 40.0  
  }  
}  

resource &quot;aws_lb&quot; &quot;node_alb&quot; {  
  name               = &quot;node-alb&quot;  
  internal           = false  
  load_balancer_type = &quot;application&quot;  
  security_groups    = [aws_security_group.alb_sg.id]  
  subnets            = module.app_vpc.public_subnets  

  tags = {  
    Name = &quot;node_alb&quot;  
  }  
}  

resource &quot;aws_lb_listener&quot; &quot;node_alb_listener&quot; {  
  load_balancer_arn = aws_lb.node_alb.arn  

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

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

  tags = {  
    Name = &quot;node_alb_listener&quot;  
  }  
}  

resource &quot;aws_lb_target_group&quot; &quot;node_target_group&quot; {  
  name     = &quot;node-target-group&quot;  
  port     = 80  
  protocol = &quot;HTTP&quot;  
  vpc_id   = module.app_vpc.vpc_id  

  health_check {  
    port     = &quot;traffic-port&quot;  
    protocol = &quot;HTTP&quot;  
    path     = &quot;/wordpress&quot;  
    matcher  = &quot;301&quot;  
  }  
  
  # efs 를 ansible 에서 mount 하지 않았기 때문에 sticky session 을 사용했다.
  stickiness {
    type            = &quot;app_cookie&quot;
    enabled         = true
    cookie_duration = 86400
  }

  tags = {  
    Name = &quot;node_target_group&quot;  
  }  
}  

resource &quot;aws_lb_target_group_attachment&quot; &quot;node_target_group_attachment&quot; {  
  count = 1  

  port             = 80  
  target_group_arn = aws_lb_target_group.node_target_group.arn  
  target_id        = element(data.aws_instances.node_instances.ids, count.index)  
  depends_on       = [data.aws_instances.node_instances]  
}  

data &quot;aws_instances&quot; &quot;node_instances&quot; {  
  instance_tags = {  
    &quot;aws:autoscaling:groupName&quot; = aws_autoscaling_group.node_asg.name  
  }  
  depends_on = [aws_launch_template.node_template]  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적으로 Bation Host 와 동일하지만 AMI 만 다르다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;data &quot;aws_ami&quot; &quot;wordpress_image&quot; {  
  most_recent = true  
  owners      = [&quot;self&quot;]  
  name_regex  = &quot;wordpress-*&quot;  

  filter {  
    name   = &quot;architecture&quot;  
    values = [&quot;x86_64&quot;]  
  }  
  filter {  
    name   = &quot;root-device-type&quot;  
    values = [&quot;ebs&quot;]  
  }  
  filter {  
    name   = &quot;virtualization-type&quot;  
    values = [&quot;hvm&quot;]  
  }}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런치 템플릿에서 사용하는 AMI 는 packer 로 만든 ami 를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ami 는 이미 만들어졌기 때문에 data 소스를 사용하여 ami 를 가져오도록 했다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;aws_launch_template&quot; &quot;node_template&quot; {  
  name          = &quot;node_template&quot;  
  image_id      = data.aws_ami.wordpress_image.id  
  instance_type = &quot;t3.small&quot;  
  key_name      = aws_key_pair.app_server_key.key_name  

  vpc_security_group_ids = [aws_security_group.node_sg.id]  

  tags = {  
    Name = &quot;node_template&quot;  
  }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ami 에 data 소스에서 가져온 ami id 값을 입력하도록 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;launch template.png&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpATWg/btrA5s0Xcb2/G9EjUt4d55T9vV3ZmYSxyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpATWg/btrA5s0Xcb2/G9EjUt4d55T9vV3ZmYSxyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpATWg/btrA5s0Xcb2/G9EjUt4d55T9vV3ZmYSxyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpATWg%2FbtrA5s0Xcb2%2FG9EjUt4d55T9vV3ZmYSxyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1480&quot; height=&quot;266&quot; data-filename=&quot;launch template.png&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 생성된 런치 템플릿은 위와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;대상 그룹.png&quot; data-origin-width=&quot;1371&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUKOls/btrAV4AuIXN/6HuqdyaSofMdSzfmGGZPuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUKOls/btrAV4AuIXN/6HuqdyaSofMdSzfmGGZPuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUKOls/btrAV4AuIXN/6HuqdyaSofMdSzfmGGZPuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUKOls%2FbtrAV4AuIXN%2F6HuqdyaSofMdSzfmGGZPuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1371&quot; height=&quot;253&quot; data-filename=&quot;대상 그룹.png&quot; data-origin-width=&quot;1371&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 생성된 대상 그룹은 위와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;오토 스케일링 그룹.png&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyBk5T/btrA32IILaF/anLt4yb9hKtXCOC1pI1gM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyBk5T/btrA32IILaF/anLt4yb9hKtXCOC1pI1gM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyBk5T/btrA32IILaF/anLt4yb9hKtXCOC1pI1gM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyBk5T%2FbtrA32IILaF%2FanLt4yb9hKtXCOC1pI1gM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1442&quot; height=&quot;255&quot; data-filename=&quot;오토 스케일링 그룹.png&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 생성된 오토 스케일링 그룹은 위와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;load_balancer.png&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsivvQ/btrAV4gaNzL/qQhjNxRkMoISOyoC67dz30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsivvQ/btrAV4gaNzL/qQhjNxRkMoISOyoC67dz30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsivvQ/btrAV4gaNzL/qQhjNxRkMoISOyoC67dz30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsivvQ%2FbtrAV4gaNzL%2FqQhjNxRkMoISOyoC67dz30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1278&quot; height=&quot;159&quot; data-filename=&quot;load_balancer.png&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 생성된 로드 밸런서는 위와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;node, bastion_host instance.png&quot; data-origin-width=&quot;1549&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nOAuP/btrA2Brh2X7/VUQPn23k5FYU4IdjuMAkf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nOAuP/btrA2Brh2X7/VUQPn23k5FYU4IdjuMAkf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nOAuP/btrA2Brh2X7/VUQPn23k5FYU4IdjuMAkf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnOAuP%2FbtrA2Brh2X7%2FVUQPn23k5FYU4IdjuMAkf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1549&quot; height=&quot;170&quot; data-filename=&quot;node, bastion_host instance.png&quot; data-origin-width=&quot;1549&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 생성된 노드와 bastion host&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;load_balancer DNS 이름으로 접속.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;823&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I6NlF/btrA5ss7yXL/W4HfGPXd0kwLPQ249KOeU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I6NlF/btrA5ss7yXL/W4HfGPXd0kwLPQ249KOeU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I6NlF/btrA5ss7yXL/W4HfGPXd0kwLPQ249KOeU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI6NlF%2FbtrA5ss7yXL%2FW4HfGPXd0kwLPQ249KOeU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1063&quot; height=&quot;823&quot; data-filename=&quot;load_balancer DNS 이름으로 접속.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;823&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node 로드 밸런서의 DNS 이름으로 접속할 경우 위와 같이 페이지가 나타나는 것을 볼 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선할 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EFS 를 terraform 에서 구현하는 것은 간단했지만 ansible 에서 EFS 를 마운트하기 위해 efs 전용 모듈을 사용하고 packer 로 ami 를 만들어야 한다.&lt;/li&gt;
&lt;li&gt;변수를 거의 사용하지 않았다.&lt;/li&gt;
&lt;li&gt;HTTPS 설정을 하지 않았고, Route 53 을 사용하여 레코드를 등록하여 사용하면 좋을 것이다.&lt;/li&gt;
&lt;li&gt;디렉토리 구조를 더 깔끔하게 바꿀 수 있을 것이다.&lt;/li&gt;
&lt;li&gt;템플릿이나 AMI 등 버전 관리 설정 등을 사용하여 편하게 관리할 수 있도록 설정할 수 있을 것이다.&lt;/li&gt;
&lt;li&gt;data 소스를 적극적으로 사용하여 모듈 별로 실행할 수 있게 분리하면 좋을 것같다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Ansible, Terraform</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/206</guid>
      <comments>https://ssunw.tistory.com/entry/Ansible-Terraform-Packer-%EB%A1%9C-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B5%AC%ED%98%84#entry206comment</comments>
      <pubDate>Mon, 23 May 2022 15:01:48 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Service &amp;amp; Ingress</title>
      <link>https://ssunw.tistory.com/entry/Service-Ingress</link>
      <description>&lt;h1&gt;Service&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/services-networking/service/&quot;&gt;https://kubernetes.io/ko/docs/concepts/services-networking/service/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드 집합에서 실행중인 애플리케이션을 네트워크 서비스로 노출하는 추상화 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스를 사용하면 익숙하지 않은 서비스 디스커버리 메커니즘을 사용하기 위해 애플리케이션을 수정할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 파드에게 고유한 IP 주소와 파드 집합에 대한 단일 DNS 명을 부여하고, 그것들 간에 로드-밸런스를 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스는 selector 를 사용해서 pod 의 label 을 찾고 뒷단으로 연결시키기 때문에 만약 다른 pod 에서 만든 컨테이너의 label 이 셀렉터에서 찾는 label 과 같다면 서비스의 뒷단에 연결해버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, label 과 selector 를 잘 설정해줘야 한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;kubectl explain svc.spec
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;clusteIP: 서비스에 IP 주소를 지정한다. 서비스의 갯수가 많아지면 IP 주소를 관리하기 힘들어지기 때문에 굳이 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;ports: 서비스의 포트를 지정한다. 즉, 클라이언트가 서비스에 접속할 포트 번호이다.&lt;/li&gt;
&lt;li&gt;selector: 서비스가 연결하기 위한 파드의 label 을 지정한다.&lt;/li&gt;
&lt;li&gt;type: 서비스의 타입을 지정한다. ExternalName, ClusterIP, NodePort, LoadBalancer 가 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NodePort, LoadBalancer: 클러스터 외부에 노출하기 위한 것&lt;/li&gt;
&lt;li&gt;ClusterIP: 클러스터 내부에서 사용하기 위한 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;kubectl explain svc.spec.ports
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name: 포트의 이름&lt;/li&gt;
&lt;li&gt;port: 서비스를 외부에 노출시킬 포트를 지정&lt;/li&gt;
&lt;li&gt;protocol: 프로토콜을 지정한다. (TCP, UDP, STCP)&lt;/li&gt;
&lt;li&gt;targetPort: 서비스와 연결하려는 파드의 포트를 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 이름과 똑같은 엔드포인트가 만들어지고, 엔드포인트가 실제 파드의 정보를 갖고 있다. 연결된 파드가 삭제되거나 종료될 경우 자동으로 파드를 만들어서 엔드포인트에 연결시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-rs.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;metadata:
  name: myweb-rs-set
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-svc.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: myweb-svc
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool

bash-5.1# curl 10.101.197.28
Hello World!
myweb-rs-set-89q25

bash-5.1# cat /etc/resolv.conf
nameserver 10.96.0.10  # &amp;lt;- 쿠버네티스 CoreDNS 주소. 즉, 쿠버네티스 안에서 자기들끼리 통신이 가능하다.
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

bash-5.1# curl myweb-svc
Hello World!
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;sessionAffinity&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;kubectl explain svc.spec
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sessionAffinity: 스티키 세션. 즉, 세션을 고정시키는 것&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;[~/k8s/svc]$ cat myweb-svc-ses.yml
apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-ses
spec:
  type: ClusterIP
  sessionAffinity: ClientIP
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;[~/k8s/svc]$ kubectl create -f myweb-svc-ses.yml -f myweb-rs.yml
service/myweb-svc-ses created
[~/k8s/svc]$ kubectl get svc,ep,rs,pod
NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/kubernetes      ClusterIP   10.96.0.1      &amp;lt;none&amp;gt;        443/TCP   35h
service/myweb-svc-ses   ClusterIP   10.99.16.135   &amp;lt;none&amp;gt;        80/TCP    11s

NAME                      ENDPOINTS                                         AGE
endpoints/kubernetes      192.168.49.2:8443                                 35h
endpoints/myweb-svc-ses   10.244.0.3:8080,10.244.1.2:8080,10.244.2.2:8080   11s

NAME                           DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-set   3         3         3       28s

NAME                     READY   STATUS    RESTARTS   AGE
pod/myweb-rs-set-69bgq   1/1     Running   0          28s
pod/myweb-rs-set-8h7ht   1/1     Running   0          28s
pod/myweb-rs-set-n6xzw   1/1     Running   0          28s
[~/k8s/svc]$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # curl myweb-svc-ses
Hello World!
myweb-rs-set-n6xzw
/ # curl myweb-svc-ses
Hello World!
myweb-rs-set-n6xzw
/ # curl myweb-svc-ses
Hello World!
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Named Port&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;[~/k8s/svc]$ cat myweb-rs-named.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-naemd
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
              name: web8080 # containerPort/protocol 의 alias 를 뜻함. 즉, 8080/TCP 라고 생각하면 된다.

[~/k8s/svc]$ cat myweb-svc-named.yml
apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-named
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: web8080 # containerPort/protocol 의 alias 를 가져온다.
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[~/k8s/svc]$ kubectl get svc,ep,rs,pod
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/kubernetes        ClusterIP   10.96.0.1        &amp;lt;none&amp;gt;        443/TCP   35h
service/myweb-svc-named   ClusterIP   10.110.107.247   &amp;lt;none&amp;gt;        80/TCP    4s

NAME                        ENDPOINTS                                         AGE
endpoints/kubernetes        192.168.49.2:8443                                 35h
endpoints/myweb-svc-named   10.244.0.4:8080,10.244.1.3:8080,10.244.2.5:8080   4s

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-naemd   3         3         3       4s

NAME                       READY   STATUS    RESTARTS   AGE
pod/myweb-rs-naemd-4qs2k   1/1     Running   0          4s
pod/myweb-rs-naemd-ctnrn   1/1     Running   0          4s
pod/myweb-rs-naemd-xfgfj   1/1     Running   0          4s
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name 필드로 containerPort/protocol 의 alias 를 사용하여 쓸 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;multi-port&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;[~/k8s/svc]$ cat myweb-svc-multi.yml
apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-multi
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
      name: http
    - port: 443
      targetPort: 8443
      name: https
[~/k8s/svc]$ cat myweb-rs-multi.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-multi
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
            - containerPort: 8443
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[~/k8s/svc]$ kubectl get svc,ep,rs,pod
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/kubernetes        ClusterIP   10.96.0.1       &amp;lt;none&amp;gt;        443/TCP          35h
service/myweb-svc-multi   ClusterIP   10.104.41.191   &amp;lt;none&amp;gt;        80/TCP,443/TCP   5s

NAME                        ENDPOINTS                                                     AGE
endpoints/kubernetes        192.168.49.2:8443                                             35h
endpoints/myweb-svc-multi   10.244.0.5:8443,10.244.1.4:8443,10.244.2.6:8443 + 3 more...   5s

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-multi   3         3         3       46s

NAME                       READY   STATUS    RESTARTS   AGE
pod/myweb-rs-multi-489wk   1/1     Running   0          46s
pod/myweb-rs-multi-fbvhm   1/1     Running   0          46s
pod/myweb-rs-multi-jqcl2   1/1     Running   0          46s
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;multi-port 는 말 그대로 포트를 여러개 열어놓고 해당하는 포트로 들어올 경우 연결된 포트로 포워딩 시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 80 으로 들어올 경우 8080 으로 포워딩하고 443 으로 들어올 경우 8443 으로 포워딩 해준다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Service Discovery - DNS&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVC 로드 밸런서를 찾을 수 있는 DNS 이름이 필요로 하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 변수를 이용한 Service Discovery&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 파드는 실행 시 현재 시점의 서비스 목록을 환경 변수로 제공한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[~/k8s/svc]$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
/ # env
MYWEB_SVC_PORT_80_TCP_ADDR=10.108.235.162
PWD=/
KUBERNETES_SERVICE_HOST=10.96.0.1
MYWEB_SVC_SERVICE_PORT=80
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
MYWEB_SVC_PORT=tcp://10.108.235.162:80
MYWEB_SVC_SERVICE_HOST=10.108.235.162
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
TERM=xterm
MYWEB_SVC_PORT_80_TCP=tcp://10.108.235.162:80
HOME=/root
SHLVL=1
HOSTNAME=nettool
MYWEB_SVC_PORT_80_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
MYWEB_SVC_PORT_80_TCP_PORT=80
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DNS 를 이용한 Service Discovery&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;host 명령어는 DNS 서버에 쿼리하는 명령어이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;/ # host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.108.235.162
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-svc.default.svc.cluster.local 는 FQDN 으로 완전한 주소 이름이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 내부에서 FQDN 은 [서비스_이름].[Name_Space].[오브젝트_타입].[Domain] 으로 구성된다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;cat /etc/resolv.conf
nameserver 169.254.25.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 nameserver 는 coredns 주소가 아닌 nodelocal DNS(DNS Cache Server) 주소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;search default.svc.cluster.local svc.cluster.local cluster.local 라고 되어있는 데 myweb-svc 를 질의를 던질 때 myweb-svc 뒤에 search 에 있는 것을 하나씩 붙여서 DNS 에 쿼리를 날리는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, myweb-svc 로 쿼리를 날리면 myweb-svc.default.svc.cluster.local 라는 FQDN 을 만들어서 쿼리를 날린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 myweb-svc.default 로 쿼리를 날리면 search 순서에 따라 myweb-svc.default.default.svc.cluster.local 으로 쿼리를 날린다. 당연하게도 쿼리가 실패하게 되고 그 다음 search 를 붙여서 쿼리를 날린다. 즉, svc.cluster.local 를 붙여서 myweb-svc.default.svc.cluster.local 를 쿼리로 날리는 것이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;/ # host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.108.235.162
/ # host myweb-svc.default
myweb-svc.default.svc.cluster.local has address 10.108.235.162
/ # host myweb-svc.default.svc
myweb-svc.default.svc.cluster.local has address 10.108.235.162
/ # host myweb-svc.default.svc.cluster.local
myweb-svc.default.svc.cluster.local has address 10.108.235.162
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;만약 Name Space 가 dev 일 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes-docsy-staging.netlify.app/ko/docs/concepts/services-networking/dns-pod-service/&quot;&gt;https://kubernetes-docsy-staging.netlify.app/ko/docs/concepts/services-networking/dns-pod-service/&lt;/a&gt;, &lt;a href=&quot;https://kubernetes.io/ko/docs/tasks/administer-cluster/dns-custom-nameservers/&quot;&gt;https://kubernetes.io/ko/docs/tasks/administer-cluster/dns-custom-nameservers/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Name Space 가 default 가 아닐 경우에는 dns 서버에 쿼리를 날려도 호스트 IP 를 가져올 수 없다. 왜냐하면 etc/resolv.conf 의 search 에 세팅된 Name Space 가 dev 로 설정되어 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Name Space 가 default 인 녀석을 추가해줘야 한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;kubectl create ns dev
kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm --namespace=dev --overrides='{&quot;spec&quot;: { &quot;dnsConfig&quot;: { &quot;searches&quot;: [&quot;default.svc.cluster.local&quot;] } }}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run 할 때 --overrides 옵션을 사용하여 default.svc.cluster.local 을 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--overrides='{&quot;spec&quot;: { &quot;dnsConfig&quot;: { &quot;searches&quot;: [&quot;default.svc.cluster.local&quot;] } }}'&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;nodelocal DNS(DNS Cache Server, DNS Forwarder)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/&quot;&gt;https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckKg4x/btrCUmybvx1/BuxkS9XYqEKP2Lu8YMpEo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckKg4x/btrCUmybvx1/BuxkS9XYqEKP2Lu8YMpEo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckKg4x/btrCUmybvx1/BuxkS9XYqEKP2Lu8YMpEo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckKg4x%2FbtrCUmybvx1%2FBuxkS9XYqEKP2Lu8YMpEo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;492&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache Hit: 캐시 서버에 캐싱된 값을 돌려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache miss: 캐시 서버에 캐싱되지 않아 실제 서버에 쿼리를 보내서 값을 가져온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;vagrant@node-1 ~ &amp;raquo; sudo ss -tnlp
State  Recv-Q Send-Q     Local Address:Port    Peer Address:Port Process
LISTEN 0      4096       169.254.25.10:9254         0.0.0.0:*     users:((&quot;node-cache&quot;,pid=1978,fd=7))
LISTEN 0      4096           127.0.0.1:10248        0.0.0.0:*     users:((&quot;kubelet&quot;,pid=772,fd=21))
....
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;169.254.25.10:9254 을 보면 node-cache 로 되어있는데 이게 바로 nodelocal DNS 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상의 DNS 로, pod 가 어떤 노드에 배치되든 간에 pod 는 coredns SVC(kube-system NS) 로 바로 쿼리를 날리지 않고 nodelocal DNS 에 쿼리를 날린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache DNS Server 에 실질적인 레코드는 없고 pod 로 부터 쿼리를 받으면 해당 쿼리를 대신해서 DNS Server 에 보내고 DNS Answer 가 오면 해당 값을 캐싱한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;vagrant@node-1 ~ &amp;raquo; kubectl get pod -A                                                            
NAMESPACE     NAME                                      READY   STATUS    RESTARTS        AGE
....
kube-system   nodelocaldns-42hw5                        1/1     Running   50 (179m ago)   3d21h
kube-system   nodelocaldns-4rdnt                        1/1     Running   32 (178m ago)   3d21h
kube-system   nodelocaldns-kdz54                        1/1     Running   3 (177m ago)    24h
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중인 각각의 노드 마다 하나의 nodelocaldns 가 세팅된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodelocal DNS 캐시 사용 시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod --dns &amp;rarr; DNS Cache Server &amp;rarr; coredns SVC(kube-system NS) &amp;rarr; coredns Pod&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodelocal DNS 캐시 사용 X&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod --dns &amp;rarr; corends SVC(kube-system NS) &amp;rarr; coredns Pod&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Service - nodePort&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodePort의 범위: 30000-32767&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ClusterIP: 클러스터 내부에서 사용하는 LB&lt;/li&gt;
&lt;li&gt;Load Balancer: 클러스터 외부에서 접근하는 LB&lt;/li&gt;
&lt;li&gt;NodePort: 클러스터 외부에서 접근하는 포트&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Service - LoadBalancer&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Metallb - Addon&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metallb 공식 문서: &lt;a href=&quot;https://metallb.universe.tf/&quot;&gt;https://metallb.universe.tf/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubespray metallb 설치 공식문서: &lt;a href=&quot;https://kubespray.io/#/docs/metallb?id=metallb&quot;&gt;https://kubespray.io/#/docs/metallb?id=metallb&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cyuu.tistory.com/170&quot;&gt;https://cyuu.tistory.com/170&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@youwins/MetalLB&quot;&gt;https://velog.io/@youwins/MetalLB&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bareMetal 환경에서 LoadBalancer type의 서비스를 만들기 위해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 metalLB을 사용하느냐 클라우드 없이 LoadBalancer type의 서비스를 구현할 때 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod는 휘발성을 가지므로 고정된 엔드포인트를 자체적으로 가지기 힘들다. Kubernetes의 Pod는 상황에 따라 소멸-재생성을 반복하게 된다. 문제는 이 때마다 지정되는 IP가 바뀌어 엔드포인트로 호출이 어렵다. 또한 여러 Pod에 같은 애플리케이션을 운용할 때 어느 Pod로 트래픽을 보낼지 결정해주는 로드밸런싱 기능이 필요하다.&lt;/li&gt;
&lt;li&gt;Service가 고정된 IP로 Pod에 접근할 수 있게 해준다. Kubernetes에서는 Service가 Pod의 한계를 보완해주는 역할을 한다. Service는 4개의 Type-ClusterIP, NodePort, LoadBalancer, ExternalName-으로 나누어져 있다. 각 타입별로 네트워크 구성이 달라진다. 상용 서비스에서 외부로 IP를 노출시킬 때는 LoadBalancer Type을 주로 사용한다. 더 상세한 설명은&amp;nbsp;&lt;a href=&quot;https://coffeewhale.com/k8s/network/2019/05/30/k8s-network-03/&quot;&gt;여기&lt;/a&gt;를 참고&lt;/li&gt;
&lt;li&gt;Service type 중 LoadBalancer type을 쓰려면 External IP를 만들어줘야한다. LoadBalancer type을 쓰려면 로드밸런서에 외부에서 접근가능한 External IP(외부 IP)를 만들어 줘야 한다. 기존에는 이 LoadBalancer Type의 Kubernetes Service 객체를 만들려면 클라우드 공급자(ex IBM 클라우드, AWS, GCP 등)가 제공해주는 IP를 사용해야 했다. 이 IP가 없으면 External IP에 영원히&amp;nbsp;pending이라고 찍혀있는 것을 봐야했다. 그럼&amp;nbsp;만약 클라우드 서비스를 못쓰는 환경이라면 어떻게 해야할까&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://metallb.universe.tf/concepts/&quot;&gt;**MetalLB&lt;/a&gt;를 활용하면 클라우드 공급자에서 실행되지 않는 클러스터에서도 External IP를 할당할 수 있고, 결과적으로 LoadBalancer Type의 Service 객체를 만들 수 있다.**&amp;nbsp;이름부터 Bare&lt;b&gt;Metal&lt;/b&gt;&amp;nbsp;&lt;b&gt;L&lt;/b&gt;oad&lt;b&gt;B&lt;/b&gt;alancer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubespray 에는 group_vars 를 변경하는 것으로 아주 간단하게 metallb 를 설치할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래를 참고하여 설치할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/kubespray/inventory/mycluster/group_vars/k8s_cluster/addons.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;...
138 # MetalLB deployment
139 metallb_enabled: true
140 metallb_speaker_enabled: true
141 metallb_ip_range:
142   - &quot;192.168.100.240-192.168.100.249&quot;
...
168 metallb_protocol: &quot;layer2&quot;
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/kubespray/inventory/mycluster/group_vars/k8s_cluster/k8s_cluster.yml&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;129 kube_proxy_strict_arp: true
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;cd ~/kubespray
ansible-playbook -i inventory/mycluster/inventory.ini cluster.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Metallb 는 Layer2 모드(소규모 10대 미만)와 BGP 모드(대규모 10대 이상) 두 가지 방식이 있다. 디폴트는 layer2 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Metallb 는 Layer2 모드로 실행할 경우 각각의 노드에 speaker 파드가 띄워진다. 그리고 contoller 파드가 띄워지는데 이게 바로 LB 역할을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 사용자가 speaker 파드의 IP 주소로 접속을 하면 speaker 파드는 controller 파드로 보내준다. 이 controller 가 LB 역할을 하고 뒷단에 연결된 NodePort 중 하나로 보내주고 NodePort 가 연결된 Service 로 보내주게 된다. 그 후에 Service 에 연결된 파드들로 분배된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 정리하자면 speker &amp;rarr; controller(LB) &amp;rarr; NodePort &amp;rarr; SVC &amp;rarr; Pod 로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BGP 모드를 사용하기 위해서는 물리적인 L3 스위치가 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L3 스위치가 로드 밸런서의 역할을 하고 서비스로 분배해준다. 이게 옳게 된 구성이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Service - ExternalName&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 내부에서 클러스터 외부의 특정 서비스에 접속하기 위해 DNS CANME 을 설정&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;vagrant@node-1 svc/extname &amp;raquo; cat weather-svc.yml
apiVersion: v1
kind: Service
metadata:
  name: weather-service
spec:
  type: ExternalName
  externalName: wttr.in

vagrant@node-1 svc/extname &amp;raquo; kubectl get svc
NAME              TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
kubernetes        ClusterIP      10.233.0.1     &amp;lt;none&amp;gt;            443/TCP        4d
myweb-svc-np      LoadBalancer   10.233.22.31   192.168.100.240   80:31313/TCP   49m
weather-service   ExternalName   &amp;lt;none&amp;gt;         google.com        &amp;lt;none&amp;gt;         10m
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;외부 IP&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나 이상의 클러스터 노드로 라우팅되는 외부 IP가 있는 경우, 쿠버네티스 서비스는 이러한 externalIPs에 노출될 수 있다. 서비스 포트에서 외부 IP (목적지 IP)를 사용하여 클러스터로 들어오는 트래픽은 서비스 엔드포인트 중 하나로 라우팅된다. externalIPs는 쿠버네티스에 의해 관리되지 않으며 클러스터 관리자에게 책임이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 명세에서,&amp;nbsp;externalIPs는 모든&amp;nbsp;ServiceTypes와 함께 지정할 수 있다. 아래 예에서, 클라이언트는 &quot;80.11.12.10:80&quot;(외부 IP:포트)로 &quot;my-service&quot;에 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
  externalIPs:
    - 80.11.12.10
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Service - Ingress&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/services-networking/ingress-controllers/&quot;&gt;https://kubernetes.io/ko/docs/concepts/services-networking/ingress-controllers/&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인그레스 컨트롤러&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스 리소스가 작동하려면, 클러스터는 실행 중인 인그레스 컨트롤러가 반드시 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kube-controller-manager&amp;nbsp;바이너리의 일부로 실행되는 컨트롤러의 다른 타입과 달리 인그레스 컨트롤러는 클러스터와 함께 자동으로 실행되지 않는다. 클러스터에 가장 적합한 인그레스 컨트롤러 구현을 선택하는데 이 페이지를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트로서 쿠버네티스는&amp;nbsp;AWS,&amp;nbsp;GCE와&amp;nbsp;nginx&amp;nbsp;인그레스 컨트롤러를 지원하고 유지한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인그레스(Ingress)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 내의 서비스에 대한 외부 접근을 관리하는 API 오브젝트이며, 일반적으로 HTTP를 관리함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스는 부하 분산, SSL Termination, 명칭 기반의 가상 호스팅을 제공할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스는 클러스터 외부에서 클러스터 내부&amp;nbsp;서비스로 HTTP와 HTTPS 경로를 노출한다. 트래픽 라우팅은 인그레스 리소스에 정의된 규칙에 의해 컨트롤된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 인그레스가 모든 트래픽을 하나의 서비스로 보내는 간단한 예시이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/euOwr1/btrCNOQt3hM/ZLGYhBy2VMn9DQm6EVK8NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euOwr1/btrCNOQt3hM/ZLGYhBy2VMn9DQm6EVK8NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euOwr1/btrCNOQt3hM/ZLGYhBy2VMn9DQm6EVK8NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeuOwr1%2FbtrCNOQt3hM%2FZLGYhBy2VMn9DQm6EVK8NK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;320&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 타입은 일반적으로 NodePort 타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스는 외부에서 서비스로 접속이 가능한 URL, 로드 밸런스 트래픽, SSL / TLS 종료 그리고 이름-기반의 가상 호스팅을 제공하도록 구성할 수 있다.&amp;nbsp;인그레스 컨트롤러는 일반적으로 로드 밸런서를 사용해서 인그레스를 수행할 책임이 있으며, 트래픽을 처리하는데 도움이 되도록 에지 라우터 또는 추가 프런트 엔드를 구성할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스는 임의의 포트 또는 프로토콜을 노출시키지 않는다. HTTP와 HTTPS 이외의 서비스를 인터넷에 노출하려면 보통&amp;nbsp;Service.Type=NodePort&amp;nbsp;또는&amp;nbsp;Service.Type=LoadBalancer&amp;nbsp;유형의 서비스를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ingress-example.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx-example
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 rules 는 라우팅 규칙을 뜻한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pathType&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Exact: URL 경로의 대소문자를 엄격하게 일치시킨다.&lt;/li&gt;
&lt;li&gt;Prefix: URL 경로의 접두사를&amp;nbsp;/&amp;nbsp;를 기준으로 분리한 값과 일치시킨다. 일치는 대소문자를 구분하고, 요소별로 경로 요소에 대해 수행한다. 모든&amp;nbsp;p 가 요청 경로의 요소별 접두사가&amp;nbsp;p 인 경우 요청은&amp;nbsp;p&amp;nbsp;경로에 일치한다.&lt;/li&gt;
&lt;li&gt;implementationSpecific: 이 경로 유형의 일치 여부는 IngressClass에 따라 달라진다. 이를 구현할 때 별도&amp;nbsp;pathType&amp;nbsp;으로 처리하거나,&amp;nbsp;Prefix&amp;nbsp;또는&amp;nbsp;Exact&amp;nbsp;경로 유형과 같이 동일하게 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;backend&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;resource: 다른 쿠버네티스의 리소스 그룹을 넣어줄 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;api Group&lt;/li&gt;
&lt;li&gt;kind&lt;/li&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;service
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name: 실제 서비스의 이름을 지정한다.&lt;/li&gt;
&lt;li&gt;port:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name: 포트 이름&lt;/li&gt;
&lt;li&gt;number: 포트 번호&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전제 조건들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스 컨트롤러가 있어야 인그레스를 충족할 수 있다. 인그레스 리소스만 생성한다면 효과가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ingress-nginx와 같은 인그레스 컨트롤러를 배포해야 할 수도 있다. 여러&amp;nbsp;인그레스 컨트롤러&amp;nbsp;중에서 선택할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상적으로, 모든 인그레스 컨트롤러는 참조 사양이 맞아야 한다. 실제로, 다양한 인그레스 컨트롤러는 조금 다르게 작동한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;kubespray 에 ingress-nginx 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 애드온 설정 파일에서 ingress-nginx 를 true 로 설정하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Niginx Ingress Controller&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/kubespray/inventory/mycluster/group_vars/k8s-cluster/addons.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt; 93 ingress_nginx_enabled: true

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;metrics-server&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/kubespray/inventory/mycluster/group_vars/k8s-cluster/addons.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt; 16 metrics_server_enabled: true

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 사용하여 애드온을 kubespray 에 적용한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;cd ~/kubespray
ansible-playbook -i inventory/mycluster/inventory.ini cluster.yml -b
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;인그레스 리소스&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 모든 쿠버네티스 리소스와 마찬가지로 인그레스에는&amp;nbsp;apiVersion,&amp;nbsp;kind, 그리고&amp;nbsp;metadata&amp;nbsp;필드가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스 오브젝트의 이름은 유효한&amp;nbsp;DNS 서브도메인 이름이어야 한다. 설정 파일의 작성에 대한 일반적인 내용은&amp;nbsp;애플리케이션 배포하기,&amp;nbsp;&lt;a href=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/&quot;&gt;컨테이너 구성하기&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/cluster-administration/manage-deployment/&quot;&gt;리소스 관리하기&lt;/a&gt;를 참조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스는 종종 어노테이션을 이용해서 인그레스 컨트롤러에 따라 몇 가지 옵션을 구성하는데, 그 예시는&amp;nbsp;&lt;a href=&quot;https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/rewrite/README.md&quot;&gt;재작성-타겟 어노테이션&lt;/a&gt;이다. 서로 다른&amp;nbsp;인그레스 컨트롤러는 서로 다른 어노테이션을 지원한다. 지원되는 어노테이션을 확인하려면 선택한 인그레스 컨트롤러의 설명서를 검토한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인그레스&amp;nbsp;사양&amp;nbsp;에는 로드 밸런서 또는 프록시 서버를 구성하는데 필요한 모든 정보가 있다. 가장 중요한 것은, 들어오는 요청과 일치하는 규칙 목록을 포함하는 것이다. 인그레스 리소스는 HTTP(S) 트래픽을 지시하는 규칙만 지원한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인그레스 규칙&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 HTTP 규칙에는 다음의 정보가 포함된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선택적 호스트. 이 예시에서는, 호스트가 지정되지 않기에 지정된 IP 주소를 통해 모든 인바운드 HTTP 트래픽에 규칙이 적용 된다. 만약 호스트가 제공되면(예, foo.bar.com), 규칙이 해당 호스트에 적용된다.&lt;/li&gt;
&lt;li&gt;경로 목록 (예,&amp;nbsp;/testpath)에는 각각&amp;nbsp;service.name&amp;nbsp;과&amp;nbsp;service.port.name&amp;nbsp;또는&amp;nbsp;service.port.number&amp;nbsp;가 정의되어 있는 관련 백엔드를 가지고 있다. 로드 밸런서가 트래픽을 참조된 서비스로 보내기 전에 호스트와 경로가 모두 수신 요청의 내용과 일치해야 한다.&lt;/li&gt;
&lt;li&gt;백엔드는&amp;nbsp;서비스 문서&amp;nbsp;또는&amp;nbsp;&lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/services-networking/ingress/#resource-backend&quot;&gt;사용자 정의 리소스 백엔드&lt;/a&gt;에 설명된 바와 같이 서비스와 포트 이름의 조합이다. 호스트와 규칙 경로가 일치하는 인그레스에 대한 HTTP(와 HTTPS) 요청은 백엔드 목록으로 전송된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defaultBackend&amp;nbsp;는 종종 사양의 경로와 일치하지 않는 서비스에 대한 모든 요청을 처리하도록 인그레스 컨트롤러에 구성되는 경우가 많다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DefaultBackend&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙이 없는 인그레스는 모든 트래픽을 단일 기본 백엔드로 전송한다.&amp;nbsp;defaultBackend&amp;nbsp;는 일반적으로&amp;nbsp;인그레스 컨트롤러의 구성 옵션이며, 인그레스 리소스에 지정되어 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 인그레스 오브젝트의 HTTP 요청과 일치하는 호스트 또는 경로가 없으면, 트래픽은 기본 백엔드로 라우팅 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;리소스 백엔드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Resource&amp;nbsp;백엔드는 인그레스 오브젝트와 동일한 네임스페이스 내에 있는 다른 쿠버네티스 리소스에 대한 ObjectRef이다.&amp;nbsp;Resource&amp;nbsp;는 서비스와 상호 배타적인 설정이며, 둘 다 지정하면 유효성 검사에 실패한다.&amp;nbsp;Resource&amp;nbsp;백엔드의 일반적인 용도는 정적 자산이 있는 오브젝트 스토리지 백엔드로 데이터를 수신하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 방식으로 구성을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-rs-np.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;vagrant@node-1 cont/ing &amp;raquo; cat myweb-rs-np.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-set
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-svc-np.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;vagrant@node-1 cont/ing &amp;raquo; cat myweb-svc-np.yml
apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-np
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-ing.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;vagrant@node-1 cont/ing &amp;raquo; cat myweb-ing.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  rules:
  - http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: myweb-svc-np
            port:
              number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인을 구매하지 않고 호스트 필드 사용하기&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;vagrant@node-1 cont/ing &amp;raquo; cat myweb-ing.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  rules:
	- host: '*.test.xyz'
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: myweb-svc-np
            port:
              number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;curl --resolve 옵션을 사용&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;curl --resolve &amp;lt;host&amp;gt;:&amp;lt;port&amp;gt;:&amp;lt;ingress_ip&amp;gt; &amp;lt;host&amp;gt;

curl --resolve www.test.xyz:80:192.168.100.103 www.test.xyz
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/etc/nsswitch.conf 에서 hosts 가 files dns 로 되어있는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/etc/hosts 파일 수정&lt;/p&gt;
&lt;pre class=&quot;accesslog&quot;&gt;&lt;code&gt;...
192.168.100.103 www.test.xyz
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;curl &amp;lt;http://www.test.xyz&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nip.io/&quot;&gt;https://nip.io/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와일드 카드 DNS 서비스를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와일드 카드 DNS: 어떤 URL 을 요청해도 response 를 돌려준다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  rules:
    - host: '*.nip.io'
      http:
        paths:
        - pathType: Prefix
          path: /
          backend:
            service:
              name: myweb-svc-np
              port:
                number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;vagrant@node-1 cont/ing &amp;raquo; curl &amp;lt;http://app-192-168-100-103.nip.io&amp;gt;
Hello World!
myweb-rs-set-l6z59
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Ingress 예제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 생성 및 레지스트리에 푸시&lt;/li&gt;
&lt;li&gt;레플리카셋 구성&lt;/li&gt;
&lt;li&gt;서비스 구성(NodePort 타입)&lt;/li&gt;
&lt;li&gt;인그레스 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QrbK8/btrCVO12ZXg/zSbqAnyLcri23HWsrl4Eu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QrbK8/btrCVO12ZXg/zSbqAnyLcri23HWsrl4Eu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QrbK8/btrCVO12ZXg/zSbqAnyLcri23HWsrl4Eu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQrbK8%2FbtrCVO12ZXg%2FzSbqAnyLcri23HWsrl4Eu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;961&quot; height=&quot;462&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hello:one 이미지 Dockerfile&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;FROM httpd
COPY index.html /usr/local/apache2/htdocs/index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.html&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt; Hello One &amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hello:two 이미지 Dockerfile&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;FROM httpd
COPY index.html /usr/local/apache2/htdocs/index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.html&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt; Hello Two &amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;docker image build X/hello:one
docker image build X/hello:two
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;docker login
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;docker push X/hello:one
docker push X/hello:two
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;one-rs.yaml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: one-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-one
  template:
    metadata:
      labels:
        app: hello-one
    spec:
      containers:
        - name: hello-one
          image: c1t1d0s7/hello:one
          ports:
            - containerPort: 80
              protocol: TCP

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;two-rs.yaml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: two-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-two
  template:
    metadata:
      labels:
        app: hello-two
    spec:
      containers:
        - name: hello-two
          image: c1t1d0s7/hello:two
          ports:
            - containerPort: 80
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;one-svc-np.yaml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: one-svc-np
spec:
  type: NodePort
  selector:
    app: hello-one
  ports:
    - port: 80
      targetPort: 80

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;two-svc-np.yaml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: two-svc-np
spec:
  type: NodePort
  selector:
    app: hello-two
  ports:
    - port: 80
      targetPort: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hello-ing.yaml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-ing
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: '*.nip.io'
      http:
        paths:
          - path: /one
            pathType: Prefix
            backend:
              service:
                name: one-svc-np
                port:
                  number: 80
          - path: /two
            pathType: Prefix
            backend:
              service:
                name: two-svc-np
                port:
                  number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x.nip.io/one 으로 접속을 하게 되면 pod_ip/one 주소로 접속하게 된다. 현재 /one 경로에는 아무런 값이 없기 때문에 x.nip.io/one 으로 접속해도 / 경로로 접속이 되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어노테이션에서 nginx.ingress.kubernetes.io/rewrite-target: / 을 사용했다. rewrite-target: / 은 어떤 경로로 들어오든 간에 타겟 경로를 / 경로로 들어오도록 변경하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl create -f .

curl http://192-168-100-103.nip.io/one
&amp;lt;h1&amp;gt; Hello One &amp;lt;/h1&amp;gt;

curl http://192-168-100-103.nip.io/two
&amp;lt;h1&amp;gt; Hello Two &amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Readiness Probe&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에 파드를 연결시키기 전에 파드의 상태가 정상적인지 확인(헬스 체크)을 하고 엔드포인트에 등록을 시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-rs-set.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-set
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      env: dev
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb:alpine
          ports:
            - containerPort: 8080
              protocol: TCP
          readinessProbe:
            exec:
              command:
                - ls
                - /tmp/ready
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-svc-np.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-np
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 31313
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;readiness probe 가 ls /tmp/ready 명령어를 실행하여 제대로 작동을 하는지 헬스 체크를 한다. 하지만 현재 pod 에는 해당 파일이 존재하지 않기 때문에 비정상적인 파드라고 판단을 하여 파드가 준비 상태로 변하지 않는다. 따라서 endpoint 에 파드의 IP 주소가 등록되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;vagrant@node-1 cont/readiness &amp;raquo; kubectl get pod,svc,rs,ep
NAME                     READY   STATUS    RESTARTS   AGE
pod/myweb-rs-set-b7mxc   0/1     Running   0          7m48s
pod/myweb-rs-set-wknpx   0/1     Running   0          7m48s
pod/myweb-rs-set-x74kb   0/1     Running   0          7m48s

NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
service/kubernetes     ClusterIP      10.233.0.1     &amp;lt;none&amp;gt;            443/TCP        6d21h
service/myweb-svc-np   LoadBalancer   10.233.53.30   192.168.100.240   80:31313/TCP   7m48s

NAME                           DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-set   3         3         0       36s

NAME                     ENDPOINTS              AGE
endpoints/kubernetes     192.168.100.103:6443   6d21h
endpoints/myweb-svc-np                          36s
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 사용하여 파드에 /tmp/ready 파일을 생성하면 pod 가 준비 상태가 되고 서비스에 연결된다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;kubectl exec &amp;lt;POD&amp;gt; -- touch /tmp/ready
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;vagrant@node-1 cont/readiness &amp;raquo; kubectl exec myweb-rs-set-b7mxc -- touch /tmp/ready
vagrant@node-1 cont/readiness &amp;raquo; kubectl exec myweb-rs-set-wknpx -- touch /tmp/ready
vagrant@node-1 cont/readiness &amp;raquo; kubectl exec myweb-rs-set-x74kb -- touch /tmp/ready

vagrant@node-1 cont/readiness &amp;raquo; kubectl get pod,svc,rs,ep
NAME                     READY   STATUS    RESTARTS   AGE
pod/myweb-rs-set-b7mxc   1/1     Running   0          7m48s
pod/myweb-rs-set-wknpx   1/1     Running   0          7m48s
pod/myweb-rs-set-x74kb   1/1     Running   0          7m48s

NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
service/kubernetes     ClusterIP      10.233.0.1     &amp;lt;none&amp;gt;            443/TCP        6d21h
service/myweb-svc-np   LoadBalancer   10.233.53.30   192.168.100.240   80:31313/TCP   7m48s

NAME                           DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-set   3         3         3       7m48s

NAME                     ENDPOINTS                                                 AGE
endpoints/kubernetes     192.168.100.103:6443                                      6d21h
endpoints/myweb-svc-np   10.233.109.23:8080,10.233.112.42:8080,10.233.69.54:8080   7m48s
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/246</guid>
      <comments>https://ssunw.tistory.com/entry/Service-Ingress#entry246comment</comments>
      <pubDate>Mon, 23 May 2022 13:19:23 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] DaemonSet &amp;amp; Job</title>
      <link>https://ssunw.tistory.com/entry/DaemonSet-Job</link>
      <description>&lt;h1&gt;crictl&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;container runtime interface controller 의 약자로 말 그대로 실행 중인 컨테이너를 제어하는 컨트롤러이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/docs/reference/tools/map-crictl-dockercli/&quot;&gt;https://kubernetes.io/docs/reference/tools/map-crictl-dockercli/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k8s 에서 crictl 로 도커 명령어를 대신할 수 있다.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;데몬셋&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데몬셋은 모든(또는 일부) 노드가 파드의 사본을 실행하도록 한다. 노드가 클러스터에 추가되면 파드도 추가된다. 노드가 클러스터에서 제거되면 해당 파드는 가비지(garbage)로 수집된다. 데몬셋을 삭제하면 데몬셋이 생성한 파드들이 정리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데몬셋은 모든 노드에 하나씩 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데몬셋의 일부 대표적인 용도는 다음과 같다. 주로 에이전트의 역할을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 노드에서 클러스터 스토리지 데몬 실행&lt;/li&gt;
&lt;li&gt;모든 노드에서 로그 수집 데몬 실행&lt;/li&gt;
&lt;li&gt;모든 노드에서 노드 모니터링 데몬 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 케이스에서는, 각 데몬 유형의 처리를 위해서 모든 노드를 커버하는 하나의 데몬셋이 사용된다. 더 복잡한 구성에서는 단일 유형의 데몬에 여러 데몬셋을 사용할 수 있지만, 각기 다른 하드웨어 유형에 따라 서로 다른 플래그, 메모리, CPU 요구가 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-ds.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: myweb-ds
spec:
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - myweb
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: myweb
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kubespray 에서 node 추가 및 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kubespray.io/#/docs/getting-started&quot;&gt;https://kubespray.io/#/docs/getting-started&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;cd ~/kubespray
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 제거&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;ansible-playbook inventory/mycluster/inventory.ini remove-node.yml -b --extra-vars &quot;node=node-2,node-3&quot; --extra-vars reset_nodes=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 추가&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;ansible-playbook inventory/mycluster/inventory.ini scale.yml -b 
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&lt;b&gt;잡&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡에서 하나 이상의 파드를 생성하고 지정된 수의 파드가 성공적으로 종료될 때까지 계속해서 파드의 실행을 재시도한다. 파드가 성공적으로 완료되면, 성공적으로 완료된 잡을 추적한다. 지정된 수의 성공 완료에 도달하면, 작업(즉, 잡)이 완료된다. 잡을 삭제하면 잡이 생성한 파드가 정리된다. 작업을 일시 중지하면 작업이 다시 재개될 때까지 활성 파드가 삭제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 사례는 잡 오브젝트를 하나 생성해서 파드 하나를 안정적으로 실행하고 완료하는 것이다. 첫 번째 파드가 실패 또는 삭제된 경우(예로는 노드 하드웨어의 실패 또는 노드 재부팅) 잡 오브젝트는 새로운 파드를 기동시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡을 사용하면 여러 파드를 병렬로 실행할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드 템플릿의 레이블 / 잡 컨트롤러의 레이블 셀렉터는 지정하지 않는다. &amp;rarr; 잘못된 매핑으로 기존의 파드를 종료하지 않게 하기 위함이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잡 예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 잡 설정 예시이다. 예시는 파이(&amp;pi;)의 2000 자리까지 계산해서 출력한다. 이를 완료하는 데 약 10초가 소요된다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: [&quot;perl&quot;,  &quot;-Mbignum=bpi&quot;, &quot;-wle&quot;, &quot;print bpi(2000)&quot;]
      restartPolicy: Never
  backoffLimit: 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl 을 사용해서 잡 상태를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;kubectl describe jobs/pi
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Name:           pi
Namespace:      default
Selector:       controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels:         controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
                job-name=pi
Annotations:    kubectl.kubernetes.io/last-applied-configuration:
                  {&quot;apiVersion&quot;:&quot;batch/v1&quot;,&quot;kind&quot;:&quot;Job&quot;,&quot;metadata&quot;:{&quot;annotations&quot;:{},&quot;name&quot;:&quot;pi&quot;,&quot;namespace&quot;:&quot;default&quot;},&quot;spec&quot;:{&quot;backoffLimit&quot;:4,&quot;template&quot;:...
Parallelism:    1
Completions:    1
Start Time:     Mon, 02 Dec 2019 15:20:11 +0200
Completed At:   Mon, 02 Dec 2019 15:21:16 +0200
Duration:       65s
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
           job-name=pi
  Containers:
   pi:
    Image:      perl
    Port:       &amp;lt;none&amp;gt;
    Host Port:  &amp;lt;none&amp;gt;
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  &amp;lt;none&amp;gt;
    Mounts:       &amp;lt;none&amp;gt;
  Volumes:        &amp;lt;none&amp;gt;
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  14m   job-controller  Created pod: pi-5rwd7
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl get pods&amp;nbsp;를 사용해서 잡의 완료된 파드를 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡에 속하는 모든 파드를 기계적으로 읽을 수 있는 양식으로 나열하려면, 다음과 같은 명령을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;pi-5rwd7
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 셀렉터는 잡의 셀렉터와 동일하다.&amp;nbsp;--output=jsonpath&amp;nbsp;옵션은 반환된 목록에 있는 각 파드의 이름으로 표현식을 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드 중 하나를 표준 출력으로 본다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;kubectl logs $pods
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;잡 spec 작성하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 쿠버네티스의 설정과 마찬가지로 잡에는&amp;nbsp;apiVersion,&amp;nbsp;kind&amp;nbsp;그리고&amp;nbsp;metadata&amp;nbsp;필드가 필요하다. 잡의 이름은 유효한&amp;nbsp;DNS 서브도메인 이름이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡에는&amp;nbsp;.spec&amp;nbsp;섹션도 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;파드 템플릿&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.template&amp;nbsp;은&amp;nbsp;.spec&amp;nbsp;의 유일한 필수 필드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.template&amp;nbsp;은&amp;nbsp;파드 템플릿이다. 이것은&amp;nbsp;apiVersion&amp;nbsp;또는&amp;nbsp;kind&amp;nbsp;가 없다는 것을 제외한다면&amp;nbsp;파드와 정확하게 같은 스키마를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 파드의 필수 필드 외에도 잡의 파드 템플릿은 적절한 레이블(파드 셀렉터를 본다)과 적절한 재시작 정책을 명시해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Never&amp;nbsp;또는&amp;nbsp;OnFailure&amp;nbsp;와 같은&amp;nbsp;RestartPolicy만 허용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;파드 셀렉터&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.selector&amp;nbsp;필드는 선택 사항이다. 대부분의 케이스에서 지정해서는 안된다.&amp;nbsp;자신의 파드 셀렉터를 지정하기&amp;nbsp;섹션을 참고한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;잡에 대한 병렬 실행&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡으로 실행하기에 적합한 작업 유형은 크게 세 가지가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비-병렬(Non-parallel) 잡:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로, 파드가 실패하지 않은 한, 하나의 파드만 시작된다.&lt;/li&gt;
&lt;li&gt;파드가 성공적으로 종료하자마자 즉시 잡이 완료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고정적(fixed)인 완료 횟수&amp;nbsp;를 가진 병렬 잡:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.spec.completions&amp;nbsp;에 0이 아닌 양수 값을 지정한다.&lt;/li&gt;
&lt;li&gt;잡은 전체 작업을 나타내며,&amp;nbsp;.spec.completions&amp;nbsp;성공한 파드가 있을 때 완료된다.&lt;/li&gt;
&lt;li&gt;.spec.completionMode=&quot;Indexed&quot;&amp;nbsp;를 사용할 때, 각 파드는 0에서&amp;nbsp;.spec.completions-1&amp;nbsp;범위 내의 서로 다른 인덱스를 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;작업 큐(queue)&amp;nbsp;가 있는 병렬 잡:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.spec.completions&amp;nbsp;를 지정하지 않고,&amp;nbsp;.spec.parallelism&amp;nbsp;를 기본으로 한다.&lt;/li&gt;
&lt;li&gt;파드는 각자 또는 외부 서비스 간에 조정을 통해 각각의 작업을 결정해야 한다. 예를 들어 파드는 작업 큐에서 최대 N 개의 항목을 일괄로 가져올(fetch) 수 있다.&lt;/li&gt;
&lt;li&gt;각 파드는 모든 피어들의 작업이 완료되었는지 여부를 독립적으로 판단할 수 있으며, 결과적으로 전체 잡이 완료되게 한다.&lt;/li&gt;
&lt;li&gt;잡의&amp;nbsp;모든&amp;nbsp;파드가 성공적으로 종료되면, 새로운 파드는 생성되지 않는다.&lt;/li&gt;
&lt;li&gt;하나 이상의 파드가 성공적으로 종료되고, 모든 파드가 종료되면 잡은 성공적으로 완료된다.&lt;/li&gt;
&lt;li&gt;성공적으로 종료된 파드가 하나라도 생긴 경우, 다른 파드들은 해당 작업을 지속하지 않아야 하며 어떠한 출력도 작성하면 안 된다. 파드들은 모두 종료되는 과정에 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비-병렬&amp;nbsp;잡은&amp;nbsp;.spec.completions&amp;nbsp;와&amp;nbsp;.spec.parallelism&amp;nbsp;모두를 설정하지 않은 채로 둘 수 있다. 이때 둘 다 설정하지 않은 경우 1이 기본으로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정적인 완료 횟수&amp;nbsp;잡은&amp;nbsp;.spec.completions&amp;nbsp;을 필요한 완료 횟수로 설정해야 한다.&amp;nbsp;.spec.parallelism&amp;nbsp;을 설정할 수 있고, 설정하지 않으면 1이 기본으로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 큐&amp;nbsp;잡은&amp;nbsp;.spec.completions&amp;nbsp;를 설정하지 않은 상태로 두고,&amp;nbsp;.spec.parallelism&amp;nbsp;을 음수가 아닌 정수로 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 유형의 잡을 사용하는 방법에 대한 더 자세한 정보는&amp;nbsp;&lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/workloads/controllers/job/#%EC%9E%A1-%ED%8C%A8%ED%84%B4&quot;&gt;잡 패턴&lt;/a&gt;&amp;nbsp;섹션을 본다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;병렬 처리 제어하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parallelism: 3 일 경우 3개의 파드를 병렬적으로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청된 병렬 처리(.spec.parallelism)는 음수가 아닌 값으로 설정할 수 있다. 만약 지정되지 않은 경우에는 1이 기본이 된다. 만약 0으로 지정되면 병렬 처리가 증가할 때까지 사실상 일시 중지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 병렬 처리(모든 인스턴스에서 실행되는 파드의 수)는 여러가지 이유로 요청된 병렬 처리보다 많거나 적을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고정적인 완료 횟수(fixed completion count)&amp;nbsp;잡의 경우, 병렬로 실행 중인 파드의 수는 남은 완료 수를 초과하지 않는다.&amp;nbsp;.spec.parallelism&amp;nbsp;의 더 큰 값은 사실상 무시된다.&lt;/li&gt;
&lt;li&gt;작업 큐&amp;nbsp;잡은 파드가 성공한 이후에 새로운 파드가 시작되지 않는다. 그러나 나머지 파드는 완료될 수 있다.&lt;/li&gt;
&lt;li&gt;만약 잡&amp;nbsp;컨트롤러&amp;nbsp;가 반응할 시간이 없는 경우&lt;/li&gt;
&lt;li&gt;만약 잡 컨트롤러가 어떤 이유(ResourceQuota&amp;nbsp;의 부족, 권한 부족 등)로든 파드 생성에 실패한 경우, 요청한 것보다 적은 수의 파드가 있을 수 있다.&lt;/li&gt;
&lt;li&gt;잡 컨트롤러는 동일한 잡에서 과도하게 실패한 이전 파드들로 인해 새로운 파드의 생성을 조절할 수 있다.&lt;/li&gt;
&lt;li&gt;파드가 정상적으로(gracefully) 종료되면, 중지하는데 시간이 소요된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;완료 모드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;completions: 3 일 경우에는 첫 번째 파드를 만들어서 실행한 후 종료하고, 두 번째 파드를 만들고 실행한 후 종료하고, 세 번 째 파드를 만들고 실행한 후 종료한다. 즉 작업의 완료 횟수가 3번 이라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;completions 의 수는 parallelism 의 수 보다 많아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료 횟수가&amp;nbsp;고정적인 완료 횟수&amp;nbsp;즉, null이 아닌&amp;nbsp;.spec.completions&amp;nbsp;가 있는 잡은&amp;nbsp;.spec.completionMode&amp;nbsp;에 지정된 완료 모드를 가질 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NonIndexed&amp;nbsp;(기본값):&amp;nbsp;.spec.completions&amp;nbsp;가 성공적으로 완료된 파드가 있는 경우 작업이 완료된 것으로 간주된다. 즉, 각 파드 완료는 서로 상동하다(homologous). null&amp;nbsp;.spec.completions&amp;nbsp;가 있는 잡은 암시적으로&amp;nbsp;NonIndexed&amp;nbsp;이다.&lt;/li&gt;
&lt;li&gt;Indexed: 잡의 파드는 연결된 완료 인덱스를 0에서&amp;nbsp;.spec.completions-1&amp;nbsp;까지 가져온다. 이 인덱스는 다음의 세 가지 메카니즘으로 얻을 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파드 어노테이션&amp;nbsp;batch.kubernetes.io/job-completion-index.&lt;/li&gt;
&lt;li&gt;파드 호스트네임 중 일부($(job-name)-$(index)&amp;nbsp;형태). 인덱스된(Indexed) 잡과&amp;nbsp;서비스를 결합하여 사용하고 있다면, 잡에 속한 파드는 DNS를 이용하여 서로를 디스커버 하기 위해 사전에 결정된 호스트네임을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;컨테이너화된 태스크의 경우,&amp;nbsp;JOB_COMPLETION_INDEX&amp;nbsp;환경 변수.&lt;/li&gt;
&lt;/ul&gt;
각 인덱스에 대해 성공적으로 완료된 파드가 하나 있으면 작업이 완료된 것으로 간주된다. 이 모드를 사용하는 방법에 대한 자세한 내용은&amp;nbsp;정적 작업 할당을 사용한 병렬 처리를 위해 인덱싱된 잡을 참고한다. 참고로, 드물기는 하지만, 동일한 인덱스에 대해 둘 이상의 파드를 시작할 수 있지만, 그 중 하나만 완료 횟수에 포함된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;파드와 컨테이너 장애 처리하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드내 컨테이너의 프로세스가 0이 아닌 종료 코드로 종료되었거나 컨테이너 메모리 제한을 초과해서 죽는 등의 여러가지 이유로 실패할 수 있다. 만약 이런 일이 발생하고&amp;nbsp;.spec.template.spec.restartPolicy = &quot;OnFailure&quot;&amp;nbsp;라면 파드는 노드에 그대로 유지되지만, 컨테이너는 다시 실행된다. 따라서 프로그램은 로컬에서 재시작될 때의 케이스를 다루거나&amp;nbsp;.spec.template.spec.restartPolicy = &quot;Never&quot;&amp;nbsp;로 지정해야 한다. 더 자세한 정보는&amp;nbsp;파드 라이프사이클의&amp;nbsp;restartPolicy&amp;nbsp;를 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드가 노드에서 내보내지는 경우(노드 업그레이드, 재부팅, 삭제 등) 또는 파드의 컨테이너가 실패되고 .spec.template.spec.restartPolicy = &quot;Never&quot; 로 설정됨과 같은 여러 이유로 전체 파드가 실패할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드가 실패하면 잡 컨트롤러는 새 파드를 시작한다. 이 의미는 애플리케이션이 새 파드에서 재시작될 때 이 케이스를 처리해야 한다는 점이다. 특히, 이전 실행으로 인한 임시파일, 잠금, 불완전한 출력 그리고 이와 유사한 것들을 처리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.parallelism = 1,&amp;nbsp;.spec.completions = 1&amp;nbsp;그리고&amp;nbsp;.spec.template.spec.restartPolicy = &quot;Never&quot;&amp;nbsp;를 지정하더라도 같은 프로그램을 두 번 시작하는 경우가 있다는 점을 참고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.spec.parallelism&amp;nbsp;그리고&amp;nbsp;.spec.completions&amp;nbsp;를 모두 1보다 크게 지정한다면 한번에 여러 개의 파드가 실행될 수 있다. 따라서 파드는 동시성에 대해서도 관대(tolerant)해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;파드 백오프(backoff) 실패 정책&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 등의 논리적 오류로 인해 약간의 재시도 이후에 잡을 실패하게 만들려는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하려면&amp;nbsp;.spec.backoffLimit&amp;nbsp;에 잡을 실패로 간주하기 이전에 재시도할 횟수를 설정한다. 백오프 제한은 기본적으로 6으로 설정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡과 관련한 실패한 파드는 최대 6분안에서 기하급수적으로 증가하는 백-오프 지연 (10초, 20초, 40초 ...) 한도가 되어 잡 컨트롤러에 의해 재생성된다. 잡의 파드가 삭제되거나 해당 시간 동안 잡에 대한 다른 파드가 실패 없이 성공했을 때 백 오프 카운트가 재설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고:&amp;nbsp;만약 잡에&amp;nbsp;restartPolicy = &quot;OnFailure&quot;&amp;nbsp;가 있는 경우 잡 백오프 한계에 도달하면 잡을 실행 중인 파드가 종료된다. 이로 인해 잡 실행 파일의 디버깅이 더 어려워질 수 있다. 디버깅하거나 로깅 시스템을 사용해서 실패한 작업의 결과를 실수로 손실되지 않도록 하려면&amp;nbsp;restartPolicy = &quot;Never&quot;&amp;nbsp;로 설정하는 것을 권장한다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;잡의 종료와 정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡이 완료되면 파드가 더 이상 생성되지도 않지만,&amp;nbsp;일반적으로는&amp;nbsp;삭제되지도 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 유지하면 완료된 파드의 로그를 계속 보며 에러, 경고 또는 다른 기타 진단 출력을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡 오브젝트는 완료된 후에도 상태를 볼 수 있도록 남아 있다. 상태를 확인한 후 이전 잡을 삭제하는 것은 사용자의 몫이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl&amp;nbsp;로 잡을 삭제할 수 있다 (예:&amp;nbsp;kubectl delete jobs/pi&amp;nbsp;또는&amp;nbsp;kubectl delete -f ./job.yaml).&amp;nbsp;kubectl&amp;nbsp;을 사용해서 잡을 삭제하면 생성된 모든 파드도 함께 삭제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 파드의 실패(restartPolicy=Never) 또는 컨테이너가 오류(restartPolicy=OnFailure)로 종료되지 않는 한, 잡은 중단되지 않고 실행되고 이때 위에서 설명했던&amp;nbsp;.spec.backoffLimit&amp;nbsp;까지 연기된다.&amp;nbsp;.spec.backoffLimit&amp;nbsp;에 도달하면 잡은 실패로 표기되고 실행 중인 모든 파드는 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡을 종료하는 또 다른 방법은 유효 데드라인을 설정하는 것이다. 잡의&amp;nbsp;.spec.activeDeadlineSeconds&amp;nbsp;필드를 초 단위로 설정하면 된다.&amp;nbsp;activeDeadlineSeconds&amp;nbsp;는 생성된 파드의 수에 관계 없이 잡의 기간에 적용된다. 잡이&amp;nbsp;activeDeadlineSeconds&amp;nbsp;에 도달하면, 실행 중인 모든 파드가 종료되고 잡의 상태는&amp;nbsp;reason: DeadlineExceeded&amp;nbsp;와 함께&amp;nbsp;type: Failed&amp;nbsp;가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡의&amp;nbsp;.spec.activeDeadlineSeconds&amp;nbsp;는&amp;nbsp;.spec.backoffLimit&amp;nbsp;보다 우선한다는 점을 참고한다. 따라서 하나 이상 실패한 파드를 재시도하는 잡은&amp;nbsp;backoffLimit&amp;nbsp;에 도달하지 않은 경우에도&amp;nbsp;activeDeadlineSeconds&amp;nbsp;에 지정된 시간 제한에 도달하면 추가 파드를 배포하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: [&quot;perl&quot;,  &quot;-Mbignum=bpi&quot;, &quot;-wle&quot;, &quot;print bpi(2000)&quot;]
      restartPolicy: Never&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡의 사양과 잡의&amp;nbsp;파드 템플릿 사양에는 모두&amp;nbsp;activeDeadlineSeconds&amp;nbsp;필드가 있다는 점을 참고한다. 이 필드를 적절한 레벨로 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;restartPolicy&amp;nbsp;는 잡 자체에 적용되는 것이 아니라 파드에 적용된다는 점을 유념한다. 잡의 상태가&amp;nbsp;type: Failed&amp;nbsp;이 되면, 잡의 자동 재시작은 없다. 즉,&amp;nbsp;.spec.activeDeadlineSeconds&amp;nbsp;와&amp;nbsp;.spec.backoffLimit&amp;nbsp;로 활성화된 잡의 종료 메커니즘은 영구적인 잡의 실패를 유발하며 이를 해결하기 위해 수동 개입이 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;완료된 잡을 자동으로 정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료된 잡은 일반적으로 시스템에서 더 이상 필요로 하지 않는다. 시스템 내에 이를 유지한다면 API 서버에 부담이 된다. 만약&amp;nbsp;크론잡과 같은 상위 레벨 컨트롤러가 잡을 직접 관리하는 경우, 지정된 용량 기반 정리 정책에 따라 크론잡이 잡을 정리할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;완료된 잡을 위한 TTL 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료된 잡 (Complete&amp;nbsp;또는&amp;nbsp;Failed)을 자동으로 정리하는 또 다른 방법은 잡의&amp;nbsp;.spec.ttlSecondsAfterFinished&amp;nbsp;필드를 지정해서 완료된 리소스에 대해&amp;nbsp;TTL 컨트롤러에서 제공하는 TTL 메커니즘을 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 컨트롤러는 잡을 정리하면 잡을 계단식으로 삭제한다. 즉, 잡과 함께 파드와 같은 종속 오브젝트를 삭제한다. 잡을 삭제하면 finalizer와 같은 라이프사이클 보증이 보장되는 것을 참고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: [&quot;perl&quot;,  &quot;-Mbignum=bpi&quot;, &quot;-wle&quot;, &quot;print bpi(2000)&quot;]
      restartPolicy: Never&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pi-with-ttl&amp;nbsp;잡은 완료 후&amp;nbsp;100&amp;nbsp;초 이후에 자동으로 삭제될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 필드를&amp;nbsp;0&amp;nbsp;으로 설정하면, 잡이 완료된 직후에 자동으로 삭제되도록 할 수 있다. 만약 필드를 설정하지 않으면, 이 잡이 완료된 후에 TTL 컨트롤러에 의해 정리되지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cron Job&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크론잡은&amp;nbsp;반복 일정에 따라&amp;nbsp;잡을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 크론잡 오브젝트는&amp;nbsp;크론탭&amp;nbsp;(크론 테이블) 파일의 한 줄과 같다. 크론잡은 잡을&amp;nbsp;크론&amp;nbsp;형식으로 쓰여진 주어진 일정에 따라 주기적으로 동작시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크론잡은 백업, 리포트 생성 등의 정기적 작업을 수행하기 위해 사용된다. 각 작업은 무기한 반복되도록 구성해야 한다(예: 1일/1주/1달마다 1회). 작업을 시작해야 하는 해당 간격 내 특정 시점을 정의할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: &quot;* * * * *&quot;
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;크론 스케줄 문법&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# ┌───────────── 분 (0 - 59)
# │ ┌───────────── 시 (0 - 23)
# │ │ ┌───────────── 일 (1 - 31)
# │ │ │ ┌───────────── 월 (1 - 12)
# │ │ │ │ ┌───────────── 요일 (0 - 6) (일요일부터 토요일까지;
# │ │ │ │ │                                   특정 시스템에서는 7도 일요일)
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, 다음은 해당 작업이 매주 금요일 자정에 시작되어야 하고, 매월 13일 자정에도 시작되어야 한다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0 0 13 * 5&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크론잡 스케줄 표현을 생성하기 위해서&amp;nbsp;&lt;a href=&quot;https://crontab.guru/&quot;&gt;crontab.guru&lt;/a&gt;와 같은 웹 도구를 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 크론잡에 대해 크론잡&amp;nbsp;컨트롤러는 마지막 일정부터 지금까지 얼마나 많은 일정이 누락되었는지 확인한다. 만약 100회 이상의 일정이 누락되었다면, 잡을 실행하지 않고 아래와 같은 에러 로그를 남긴다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Cannot determine if job needs to be started. Too many missed start time (&amp;gt; 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 만약&amp;nbsp;startingDeadlineSeconds&amp;nbsp;필드가 설정이 되면(nil&amp;nbsp;이 아닌 값으로), 컨트롤러는 마지막 일정부터 지금까지 대신&amp;nbsp;startingDeadlineSeconds&amp;nbsp;값에서 몇 개의 잡이 누락되었는지 카운팅한다. 예를 들면,&amp;nbsp;startingDeadlineSeconds&amp;nbsp;가&amp;nbsp;200&amp;nbsp;이면, 컨트롤러는 최근 200초 내 몇 개의 잡이 누락되었는지 카운팅한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크론잡은 정해진 일정에 잡 실행을 실패하면 놓쳤다고 카운팅된다. 예를 들면,&amp;nbsp;concurrencyPolicy&amp;nbsp;가&amp;nbsp;Forbid&amp;nbsp;로 설정되었고, 크론잡이 이전 일정이 스케줄되어 여전히 시도하고 있을 때, 그 때 누락되었다고 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 크론잡이&amp;nbsp;08:30:00&amp;nbsp;에 시작하여 매 분마다 새로운 잡을 실행하도록 설정이 되었고,&amp;nbsp;startingDeadlineSeconds&amp;nbsp;값이 설정되어 있지 않는다고 가정해보자. 만약 크론잡 컨트롤러가&amp;nbsp;08:29:00&amp;nbsp;부터&amp;nbsp;10:21:00&amp;nbsp;까지 고장이 나면, 일정을 놓친 작업 수가 100개를 초과하여 잡이 실행되지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 개념을 더 자세히 설명하자면, 크론잡이&amp;nbsp;08:30:00&amp;nbsp;부터 매 분 실행되는 일정으로 설정되고,&amp;nbsp;startingDeadlineSeconds&amp;nbsp;이 200이라고 가정한다. 크론잡 컨트롤러가 전의 예시와 같이 고장났다고 하면 (08:29:00&amp;nbsp;부터&amp;nbsp;10:21:00&amp;nbsp;까지), 잡은 10:22:00 부터 시작될 것이다. 이 경우, 컨트롤러가 마지막 일정부터 지금까지가 아니라, 최근 200초 안에 얼마나 놓쳤는지 체크하기 때문이다. (여기서는 3번 놓쳤다고 체크함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크론잡은 오직 그 일정에 맞는 잡 생성에 책임이 있고, 잡은 그 잡이 대표하는 파드 관리에 책임이 있다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/245</guid>
      <comments>https://ssunw.tistory.com/entry/DaemonSet-Job#entry245comment</comments>
      <pubDate>Mon, 23 May 2022 12:59:08 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Controller</title>
      <link>https://ssunw.tistory.com/entry/Controller-1</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Controller&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크로드는 쿠버네티스에서 구동되는 애플리케이션이다. 컨트롤러는 하나 이상의 파드의 집합이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각&amp;nbsp;Pod&amp;nbsp;를 직접 관리할 필요는 없도록 만들었다. 대신, 사용자를 대신하여 파드 집합을 관리하는&amp;nbsp;워크로드 리소스를 사용할 수 있다. 이러한 리소스는 지정한 상태와 일치하도록 올바른 수의 올바른 파드 유형이 실행되고 있는지 확인하는&amp;nbsp;컨트롤러를 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 다음과 같이 여러 가지 빌트인(built-in) 워크로드 리소스를 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Deployment&amp;nbsp;및&amp;nbsp;ReplicaSet&amp;nbsp;(레거시 리소스&amp;nbsp;레플리케이션 컨트롤러&lt;a href=&quot;https://kubernetes.io/ko/docs/reference/glossary/?all=true#term-replication-controller&quot;&gt;(&lt;/a&gt;ReplicationController&lt;a href=&quot;https://kubernetes.io/ko/docs/reference/glossary/?all=true#term-replication-controller&quot;&gt;)&lt;/a&gt;를 대체).&amp;nbsp;Deployment&amp;nbsp;는&amp;nbsp;Deployment&amp;nbsp;의 모든&amp;nbsp;Pod&amp;nbsp;가 필요 시 교체 또는 상호 교체 가능한 경우, 클러스터의 스테이트리스 애플리케이션 워크로드를 관리하기에 적합하다.&lt;/li&gt;
&lt;li&gt;StatefulSet는 어떻게든 스테이트(state)를 추적하는 하나 이상의 파드를 동작하게 해준다. 예를 들면, 워크로드가 데이터를 지속적으로 기록하는 경우, 사용자는&amp;nbsp;Pod&amp;nbsp;와&amp;nbsp;PersistentVolume을 연계하는&amp;nbsp;StatefulSet&amp;nbsp;을 실행할 수 있다. 전체적인 회복력 향상을 위해서,&amp;nbsp;StatefulSet&amp;nbsp;의&amp;nbsp;Pods&amp;nbsp;에서 동작 중인 코드는 동일한&amp;nbsp;StatefulSet&amp;nbsp;의 다른&amp;nbsp;Pods&amp;nbsp;로 데이터를 복제할 수 있다.&lt;/li&gt;
&lt;li&gt;DaemonSet은 노드-로컬 기능(node-local facilities)을 제공하는&amp;nbsp;Pods&amp;nbsp;를 정의한다. 이러한 기능들은 클러스터를 운용하는 데 기본적인 것일 것이다. 예를 들면, 네트워킹 지원 도구 또는&amp;nbsp;add-on&amp;nbsp;등이 있다.&amp;nbsp;DaemonSet&amp;nbsp;의 명세에 맞는 노드를 클러스터에 추가할 때마다, 컨트롤 플레인은 해당 신규 노드에&amp;nbsp;DaemonSet&amp;nbsp;을 위한&amp;nbsp;Pod&amp;nbsp;를 스케줄한다.&lt;/li&gt;
&lt;li&gt;Job&amp;nbsp;및&amp;nbsp;CronJob은 실행 완료 후 중단되는 작업을 정의한다.&amp;nbsp;CronJobs&amp;nbsp;이 스케줄에 따라 반복되는 반면, 잡은 단 한 번의 작업을 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Replication Controller&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디플로이먼트나 레플리카셋을 더 추천한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레플리케이션 컨트롤러의 동작방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드가 너무 많으면 레플리케이션 컨트롤러가 추가적인 파드를 제거한다. 너무 적으면 레플리케이션 컨트롤러는 더 많은 파드를 시작한다. 수동으로 생성된 파드와 달리 레플리케이션 컨트롤러가 유지 관리하는 파드는 실패하거나 삭제되거나 종료되는 경우 자동으로 교체(새로운 파드를 만들어서 교체)된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 커널 업그레이드와 같이 파괴적인 유지 보수 작업을 하고 난 이후의 노드에서 파드가 다시 생성된다. 따라서 애플리케이션에 하나의 파드만 필요한 경우에도 레플리케이션 컨트롤러를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리케이션 컨트롤러는 프로세스 감시자(supervisor)와 유사하지만 단일 노드에서 개별 프로세스를 감시하는 대신 레플리케이션 컨트롤러는 여러 노드에서 여러 파드를 감시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리케이션 컨트롤러는 디스커션에서 종종 &quot;rc&quot;로 축약되며 kubectl 명령에서 숏컷으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 경우는 하나의 레플리케이션 컨트롤러 오브젝트를 생성하여 한 개의 파드 인스턴스를 영구히 안정적으로 실행하는 것이다. 보다 복잡한 사용 사례는 웹 서버와 같이 복제된 서비스의 동일한 레플리카를 여러 개 실행하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;vagrant@node-1 cont/rc &amp;raquo; kubectl explain rc.spec
KIND:     ReplicationController
VERSION:  v1

RESOURCE: spec 

DESCRIPTION:
     Spec defines the specification of the desired behavior of the replication
     controller. More info:
     &amp;lt;https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status&amp;gt;

     ReplicationControllerSpec is the specification of a replication controller.

FIELDS:
   minReadySeconds      
     Minimum number of seconds for which a newly created pod should be ready
     without any of its container crashing, for it to be considered available.
     Defaults to 0 (pod will be considered available as soon as it is ready)

   replicas     
     Replicas is the number of desired replicas. This is a pointer to
     distinguish between explicit zero and unspecified. Defaults to 1. More
     info:
     &amp;lt;https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller&amp;gt;

   selector     &amp;lt;map[string]string&amp;gt;
     Selector is a label query over pods that should match the Replicas count.
     If Selector is empty, it is defaulted to the labels present on the Pod
     template. Label keys and values that must match in order to be controlled
     by this replication controller, if empty defaulted to labels on Pod
     template. More info:
     &amp;lt;https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors&amp;gt;

   template     
     Template is the object that describes the pod that will be created if
     insufficient replicas are detected. This takes precedence over a
     TemplateRef. More info:
     &amp;lt;https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template&amp;gt;
&amp;lt;/map[string]string&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rc.spec.replicas &amp;rarr; 내가 설정하고 싶은 복제본 갯수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rc.spec.template &amp;rarr; Pod 를 만들 템플릿을 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rc.spec.template.metadata &amp;rarr; 여기서 반드시 Pod 의 label 을 지정해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rc.spec.template.spec &amp;rarr; Pod 의 spec 을 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rc.spec.selector &amp;rarr; 수많은 Pod 중에 원하는 label 을 갖는 Pod 의 key, value 를 입력한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ReplicationController
metadata:
  name: myweb-rc
spec:
  replicas: 3
  # template 에서 생성된 Pod 의 label 을 확인하고 가져올 수 있다.  
  selector:
    app: web

  # Pod Configuration
  # 여기서 Pod 를 구성하고 label 을 붙인다.
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: myweb
          image: httpd
          ports:
            - containerPort: 80
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RC 스케일링&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;replace&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령형 커맨드&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl scale rc &amp;lt;RC_이름&amp;gt; --replicas=5
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yml 파일에서 replicas 를 3개에서 2개로 변경 후에 변경사항을 적용시키고 싶다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ReplicationController
metadata:
  name: myweb-rc
spec:
#  replicas: 3
  replicas: 2 
  selector: 
    app: web

  # Pod Configuration
  template:
    metadata:
      labels: 
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: httpd
          ports: 
            - containerPort: 80
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 replace 명령어를 사용한다. replace 명령어를 사용하면 변경한 필드값을 업데이트 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;kubectl replace -f &amp;lt;파일명.yml&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 업데이트가 불가능한 필드도 있다. 업데이트가 가능하고 불가능한 필드를 확인하기 위해서 explain 명령어를 사용해서 확인 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, template 을 변경하고 새로 적용하고 싶다면 pod 를 일부러 지워야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 template 은 RC 를 새로 만들 때 적용되기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl delete pods -l &amp;lt;labels_key=labels_value&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;patch&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;kubectl patch -f &amp;lt;파일_이름.yml&amp;gt; -p '{&quot;spec&quot;: {&quot;replicas&quot;: 2}}'
kubectl patch rc &amp;lt;RC_이름&amp;gt; -p '{&quot;spec&quot;: {&quot;replicas&quot;: 2}}'
kubectl patch rc &amp;lt;RC_이름&amp;gt; --patch-file &amp;lt;파일_이름.json&amp;gt;

kubectl patch -f myweb-rc.yml -p '{&quot;spec&quot;: {&quot;replicas&quot;: 2}}'
kubectl patch rc myweb-rc -p '{&quot;spec&quot;: {&quot;replicas&quot;: 2}}'ㅣ
kubectl patch rc myweb-rc --patch-file replace.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 파일 이름을 적었다고 해서 파일을 수정하는 것이 아니라 어떤 리소스를 수정할 지 찾기 위해 사용하는 것이다. 변경할 수 없는 값은 변경할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;replace 는 파일을 수정하고 replace 명령어를 사용하여 업데이트 한다. 즉, 파일의 spec 자체가 변경된 것이다. replace 는 완전한 구조(yml 파일 자체)를 제공해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;patch 는 JSON 형태로 일부만 제공하여 업데이트 할 수 있다. 즉, 필요한 부분만 골라서 업데이트할 수 있다는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;edit&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl edit -f &amp;lt;파일_이름.yml&amp;gt;
kubectl edit rc/&amp;lt;RC_이름&amp;gt;
kubectl edit rc &amp;lt;RC_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 이름을 적었다고 해서 파일을 수정하는 것이 아니라 어떤 리소스를 수정할 지 찾기 위해 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;edit 를 사용하면 etcd 에 저장된 파일 자체를 수정할 수 있다. 변경할 수 없는 값은 변경할 수 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;apply&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언형 오브젝트 구성&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;kubectl apply -f &amp;lt;파일_이름.yml&amp;gt;
kubectl apply rc &amp;lt;RC_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;없으면 생성해주고 변경됐으면 변경사항을 적용해준다. 즉, create 로 RC 를 생성하지 않고 apply 로도 생성이 가능하다. 변경 사항이 생겼을 경우에도 apply 를 사용하면 자동으로 업데이트가 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;logs&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;kubectl logs rc/&amp;lt;RC_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 logs 명령어는 pods 로그를 디폴트로해서 로그를 찾기 때문에 pods 가 아닌 리소스의 로그를 찾을 때는 어떤 리소스 타입인지 표기해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ReplicaSet&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReplicationController &amp;rarr; ReplicaSet&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReplicaSet 은 파드의&amp;nbsp;metadata.ownerReferences&amp;nbsp;필드를 통해 파드에 연결ㅏ되며, 이는 현재 오브젝트가 소유한 리소스를 명시한다. 레플리카셋이 가지고 있는 모든 파드의 ownerReferences 필드는 해당 파드를 소유한 레플리카셋을 식별하기 위한 소유자 정보를 가진다. 이 링크를 통해 레플리카셋은 자신이 유지하는 파드의 상태를 확인하고 이에 따라 관리 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카셋은 셀렉터를 이용해서 필요한 새 파드를 식별한다. 만약 파드에 OwnerReference이 없거나 OwnerReference가&amp;nbsp;컨트롤러(Controller)&amp;nbsp;가 아니고 레플리카셋의 셀렉터와 일치한다면 레플리카셋이 즉각 파드를 가지게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드에 어떤 컨트롤러에 의해 관리되는지 정보가 추가 되어 파드의 labels 가 같더라도 컨트롤러에 따라 별개로 작동한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;레플리카셋을 사용하는 시기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카셋은 지정된 수의 파드 레플리카가 항상 실행되도록 보장한다. 그러나 디플로이먼트는 레플리카셋을 관리하고 다른 유용한 기능과 함께 파드에 대한 선언적 업데이트를 제공하는 상위 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 우리는 사용자 지정 오케스트레이션이 필요하거나 업데이트가 전혀 필요하지 않은 경우라면 레플리카셋을 직접적으로 사용하기 보다는 디플로이먼트를 사용하는 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 레플리카셋 오브젝트를 직접 조작할 필요가 없다는 것을 의미한다. 대신 디플로이먼트를 이용하고 사양 부분에서 애플리케이션을 정의하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-rs-set.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-set
spec:
  replicas: 2
  selector:
    matchExpressions:
      - key: app
        operator: In
        values: 
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: nginx
          ports: 
            - containerPort: 8080
              protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;frontend-rs.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # 케이스에 따라 레플리카를 수정한다.
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타데이터의 ownerReferences 필드에 설정되어 있는 프런트엔드 레플리카셋의 정보가 다음과 유사하게 나오는 것을 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;kubectl get pods frontend-b2zdv -o yaml

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: &quot;2022-05-12T07:06:16Z&quot;
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-b2zdv
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: f391f6db-bb9b-4c09-ae74-6a1f77f3d5cf
...
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/244</guid>
      <comments>https://ssunw.tistory.com/entry/Controller-1#entry244comment</comments>
      <pubDate>Mon, 23 May 2022 12:54:36 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] POD Lifecycle</title>
      <link>https://ssunw.tistory.com/entry/POD-Lifecycle</link>
      <description>&lt;h1&gt;Pod Lifecycle&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/workloads/pods/pod-lifecycle/&quot;&gt;https://kubernetes.io/ko/docs/concepts/workloads/pods/pod-lifecycle/&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;파드의 단계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드의&amp;nbsp;status&amp;nbsp;필드는&amp;nbsp;phase&amp;nbsp;필드를 포함하는&amp;nbsp;&lt;a href=&quot;https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#podstatus-v1-core&quot;&gt;PodStatus&lt;/a&gt;&amp;nbsp;오브젝트로 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드의 phase는 파드가 라이프사이클 중 어느 단계에 해당하는지 표현하는 간단한 고수준의 요약이다. Phase는 컨테이너나 파드의 관측 정보에 대한 포괄적인 롤업이나, 포괄적인 상태 머신을 표현하도록 의도되지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드 phase 값에서 숫자와 의미는 엄격하게 지켜진다. 여기에 문서화된 내용 이외에는, 파드와 파드에 주어진&amp;nbsp;phase&amp;nbsp;값에 대해서 어떤 사항도 가정되어서는 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;phase에 가능한 값은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pending: 스케줄링 되기 전, 이미지를 받기 전, 컨테이너가 준비되기 전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Running: 컨테이너가 실행 중, 실행 전, 재시작 중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Succeeded: 정상 종료(0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Failed: 비정상 종료(!0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unknown: 어떤 이유에 의해서 파드의 상태를 얻을 수 없다. 이 단계는 일반적으로 파드가 실행되어야 하는 노드와의 통신 오류로 인해 발생한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Container 상태&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Waiting: 이미지 받기 전, 볼륨 연결 되기 전&lt;/li&gt;
&lt;li&gt;Running: 실행 중&lt;/li&gt;
&lt;li&gt;Terminated: 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;재시작 정책&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pod.spec.restartPolicy
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Always(기본)&lt;/li&gt;
&lt;li&gt;OnFailure&lt;/li&gt;
&lt;li&gt;Never&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지수 백오프&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파드 실패 시 재시작 정책에 의해 재시작을 하게 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재시작 시간 10, 20, 40, 80&amp;hellip;300 초 까지 유예 시간을 갖는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;컨테이너 프로브(probe)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로브&amp;nbsp;는 컨테이너에서&amp;nbsp;kubelet에 의해 주기적으로 수행되는 진단(diagnostic)이다. 진단을 수행하기 위해서, kubelet은 컨테이너 안에서 코드를 실행하거나, 또는 네트워크 요청을 전송한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로브 종류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;livenessProbe: 컨테이너가 동작 중인지 여부를 나타낸다. 만약 활성 프로브(liveness probe)에 실패한다면, kubelet은 컨테이너를 죽이고, 해당 컨테이너는&amp;nbsp;재시작 정책의 대상이 된다. 만약 컨테이너가 활성 프로브를 제공하지 않는 경우, 기본 상태는&amp;nbsp;Success 이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl explain pods.spec.containers.livenessProbe&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;readinessProbe: 컨테이너가 요청을 처리할 준비가 되었는지 여부를 나타낸다. 만약 준비성 프로브(readiness probe)가 실패한다면, 엔드포인트 컨트롤러는 파드에 연관된 모든 서비스들의 엔드포인트에서 파드의 IP주소를 제거한다. 준비성 프로브의 초기 지연 이전의 기본 상태는&amp;nbsp;Failure&amp;nbsp;이다. 만약 컨테이너가 준비성 프로브를 지원하지 않는다면, 기본 상태는&amp;nbsp;Success&amp;nbsp;이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl explain pods.spec.containers.readinessProbe&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;startupProbe: 컨테이너 내의 애플리케이션이 시작되었는지를 나타낸다. &lt;b&gt;스타트업 프로브(startup probe)가 주어진 경우, 성공할 때까지 다른 나머지 프로브는 활성화되지 않는다.&lt;/b&gt; 만약 스타트업 프로브가 실패하면, kubelet이 컨테이너를 죽이고, 컨테이너는&amp;nbsp;재시작 정책에 따라 처리된다. 컨테이너에 스타트업 프로브가 없는 경우, 기본 상태는&amp;nbsp;Success 이다. startupProbe 의 상태가 Success 가 되어야만 livenessProbe 가 실행된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl explain pods.spec.containers.startupProbe&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;체크 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로브를 사용하여 컨테이너를 체크하는 방법에는 4가지가 있다. 각 프로브는 다음의 4가지 메커니즘 중 단 하나만을 정의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;exec:&lt;/b&gt; 컨테이너 내에서 지정된 명령어를 실행한다. 명령어가 상태 코드 0으로 종료되면 진단이 성공한 것으로 간주한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;grpc:&lt;/b&gt; gRPC를 사용하여 원격 프로시저 호출을 수행한다. 체크 대상이&amp;nbsp;gRPC 헬스 체크를 구현해야 한다. 응답의&amp;nbsp;status&amp;nbsp;가&amp;nbsp;SERVING&amp;nbsp;이면 진단이 성공했다고 간주한다. gRPC 프로브는 알파 기능이며&amp;nbsp;GRPCContainerProbe&amp;nbsp;기능 게이트를 활성화해야 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;httpGet:&lt;/b&gt; 지정한 포트 및 경로에서 컨테이너의 IP주소에 대한 HTTP&amp;nbsp;GET&amp;nbsp;요청을 수행한다. 응답의 상태 코드가 200 이상 400 미만이면 진단이 성공한 것으로 간주한다. 애플리케이션이 web app 일 경우 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl explain pods.spec.containers.livenessProbe.httpGet&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;tcpSocket:&lt;/b&gt; 지정된 포트에서 컨테이너의 IP주소에 대해 TCP 검사를 수행한다. 포트가 활성화되어 있다면 진단이 성공한 것으로 간주한다. 원격 시스템(컨테이너)가 연결을 연 이후 즉시 닫는다면, 이 또한 진단이 성공한 것으로 간주한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl explain pods.spec.containers.livenessProbe.tcpSocket&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로브 결과&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 probe는 다음 세 가지 결과 중 하나를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Success:&lt;/b&gt; 컨테이너가 진단을 통과함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Failure:&lt;/b&gt; 컨테이너가 진단에 실패함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Unknown:&lt;/b&gt; 진단 자체가 실패함(아무런 조치를 수행해서는 안 되며, kubelet이 추가 체크를 수행할 것이다)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류를 일으키키 위한 코드는 아래와 같다. livenessProbe 의 port 를 80 으로 수정하면 정상 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-liveness-err.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-liveness-err
  labels:
    APP: apache
    ENV: dev
  annotations:
    Created-by: Choi
spec:
  containers:
    - name: myweb
      image: httpd
      livenessProbe:
        httpGet:
          path: &quot;/&quot;
#          port: 80
          port: 8080
      ports:
        - containerPort: 80
          name: myweb
          protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Liveness probe 가 실패한 것을 describe 에서 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl create -f myweb-liveness-err.yml

kubectl describe pods myweb-liveness-err
...
Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  13s   default-scheduler  Successfully assigned default/myweb-liveness-err to node-2
  Normal   Pulling    12s   kubelet            Pulling image &quot;httpd&quot;
  Normal   Pulled     10s   kubelet            Successfully pulled image &quot;httpd&quot; in 1.932221412s
  Normal   Created    10s   kubelet            Created container myweb
  Normal   Started    10s   kubelet            Started container myweb
  Warning  Unhealthy  3s    kubelet            Liveness probe failed: Get &quot;&amp;lt;http://10.233.69.13:8080/&amp;gt;&quot;: dial tcp 10.233.69.13:8080: connect: connection refused
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--watch 옵션으로 상태를 추적하면 계속해서 재시작되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl get pods --watch
NAME                 READY   STATUS    RESTARTS   AGE
myweb-liveness       1/1     Running   0          2m42s
myweb-liveness-err   1/1     Running   0          24s
myweb-liveness-err   1/1     Running   1 (3s ago)   34s
myweb-liveness-err   1/1     Running   2 (3s ago)   64s
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류를 발생시키기 위한 startup 코드는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-startup.yml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb-startup
  labels:
    APP: apache
    ENV: dev
  annotations:
    Created-by: Choi
spec:
  containers:
    - name: myweb
      image: httpd
      livenessProbe:
        httpGet:
          path: &quot;/&quot;
          port: 80
      startupProbe:
        exec:
          command:
            - &quot;ls /tmp/abc&quot;
# httpGet 으로 정상 작동시킬 수 있다.
#        httpGet:
#          path: &quot;/&quot;
#          port: 80
      ports:
        - containerPort: 80
          name: myweb
          protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Startup probe 가 실패한 것을 describe 에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Startup probe 의 특성상 성공할 때까지 다른 나머지 프로브는 활성화되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Events:
  Type     Reason     Age                    From               Message
  ----     ------     ----                   ----               -------
  Normal   Scheduled  7m9s                   default-scheduler  Successfully assigned default/myweb-startup to node-3
  Normal   Pulling    7m9s                   kubelet            Pulling image &quot;httpd&quot;
  Normal   Pulled     7m7s                   kubelet            Successfully pulled image &quot;httpd&quot; in 1.869534872s
  Normal   Created    7m7s                   kubelet            Created container myweb
  Normal   Started    7m7s                   kubelet            Started container myweb
  Warning  Unhealthy  6m59s                  kubelet            Startup probe errored: rpc error: code = Unknown desc = failed to exec in container: failed to start exec &quot;d159db91a9f554ab155bd15e47b782ee35adee733973ef21baa90450ca04792e&quot;: OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: &quot;ls /tmp/abc&quot;: stat ls /tmp/abc: no such file or directory: unknown
  Warning  Unhealthy  6m49s                  kubelet            Startup probe errored: rpc error: code = Unknown desc = failed to exec in container: failed to start exec &quot;b49bcf86f7748bcb6824f1868264dcd09031c62a82de86b6bef11117d6b80292&quot;: OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: &quot;ls /tmp/abc&quot;: stat ls /tmp/abc: no such file or directory: unknown
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/243</guid>
      <comments>https://ssunw.tistory.com/entry/POD-Lifecycle#entry243comment</comments>
      <pubDate>Mon, 23 May 2022 12:53:18 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] NameSpace  &amp;amp; Label &amp;amp; Annotation</title>
      <link>https://ssunw.tistory.com/entry/NameSpace-Label-Annotation</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Namespace&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스를 분리(격리)한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 별&lt;/li&gt;
&lt;li&gt;사용자 별&lt;/li&gt;
&lt;li&gt;환경: 개발, 스테이징, 프로덕션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스: NDS 이름이 분리되는 용도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RBAC: 권한을 NS 에 설정&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[~]$ kubectl get namespace
NAME              STATUS   AGE
default           Active   23h
kube-node-lease   Active   23h
kube-public       Active   23h
kube-system       Active   23h
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kube-system: Kubernetes 의 핵심 컴포넌트&lt;/li&gt;
&lt;li&gt;kube-public: 모든 사용자가 읽기 권한&lt;/li&gt;
&lt;li&gt;kube-node-lease: Worker Node 에 장애가 발생했는지 확인&lt;/li&gt;
&lt;li&gt;default: 기본 작업 공간&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;kubectl create ns developments
kubectl delete ns developments
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;kubectl get pods -A | --all-namespaces
kubectl get pods -n kube-system
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ns-dev.yml&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Namespace
metadata:
  name: dev
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;kubectl create -f ns-dev.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb-dev.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb
  namespace: dev
spec:
  containers:
    - name: myweb
      image: httpd
      ports:
      - containerPort: 80
        name: myweb
        protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레이블과 셀렉터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/labels/&quot;&gt;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/labels/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블&amp;nbsp;은 파드와 같은 오브젝트에 첨부된 키와 값의 쌍이다. 레이블은 오브젝트의 특성을 식별하는 데 사용되어 사용자에게 중요하지만, 코어 시스템에 직접적인 의미는 없다. 레이블로 오브젝트의 하위 집합을 선택하고, 구성하는데 사용할 수 있다. 레이블은 오브젝트를 생성할 때에 붙이거나 생성 이후에 붙이거나 언제든지 수정이 가능하다. 오브젝트마다 키와 값으로 레이블을 정의할 수 있다. 오브젝트의 키는 고유한 값이어야 한다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;metadata&quot;: {
  **&quot;labels&quot;**: {
    **&quot;key1&quot;** : &quot;value1&quot;,
    **&quot;key2&quot;** : &quot;value2&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블은 UI와 CLI에서 효율적인 쿼리를 사용하고 검색에 사용하기에 적합하다. 식별되지 않는 정보는&amp;nbsp;어노테이션으로 기록해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;권장 레이블&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권장 레이블 공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/common-labels/&quot;&gt;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/common-labels/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블은 주로 검색을 하거나 리소스를 연결할 때 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레이블 명령어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블 확인&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;kubectl get pods --show-labels

kubectl get pods &amp;lt;파드_이름&amp;gt; -o yaml

kubectl describe pods &amp;lt;파드_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블 관리&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;kubectl label pods &amp;lt;파드_이름&amp;gt; Key=Value # key 추가

kubectl label pods &amp;lt;파드_이름&amp;gt; Key=Value --overwrite # value 덮어쓰기

kubectl label pods &amp;lt;파드_이름&amp;gt; Key-   # key 삭제
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;YAML 파일에서 label&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb
  namespace: dev
  labels:
    APP: apache
    ENV: dev
spec:
  containers:
    - name: myweb
      image: httpd
      ports:
      - containerPort: 80
        name: myweb
        protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사용 동기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블을 이용하면 사용자가 느슨하게 결합한 방식으로 조직 구조와 시스템 오브젝트를 매핑할 수 있으며, 클라이언트에 매핑 정보를 저장할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 배포와 배치 프로세싱 파이프라인은 흔히 다차원의 엔티티들이다(예: 다중 파티션 또는 배포, 다중 릴리스 트랙, 다중 계층, 계층 속 여러 마이크로 서비스들). 관리에는 크로스-커팅 작업이 필요한 경우가 많은데 이 작업은 사용자보다는 인프라에 의해 결정된 엄격한 계층 표현인 캡슐화를 깨트린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블 예시:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;release&quot; : &quot;stable&quot;,&amp;nbsp;&quot;release&quot; : &quot;canary&quot;&lt;/li&gt;
&lt;li&gt;&quot;environment&quot; : &quot;dev&quot;,&amp;nbsp;&quot;environment&quot; : &quot;qa&quot;,&amp;nbsp;&quot;environment&quot; : &quot;production&quot;&lt;/li&gt;
&lt;li&gt;&quot;tier&quot; : &quot;frontend&quot;,&amp;nbsp;&quot;tier&quot; : &quot;backend&quot;,&amp;nbsp;&quot;tier&quot; : &quot;cache&quot;&lt;/li&gt;
&lt;li&gt;&quot;partition&quot; : &quot;customerA&quot;,&amp;nbsp;&quot;partition&quot; : &quot;customerB&quot;&lt;/li&gt;
&lt;li&gt;&quot;track&quot; : &quot;daily&quot;,&amp;nbsp;&quot;track&quot; : &quot;weekly&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시는&amp;nbsp;일반적으로 사용하는 레이블이며, 사용자는 자신만의 규칙(convention)에 따라 자유롭게 개발할 수 있다. 오브젝트에 붙여진 레이블 키는 고유해야 한다는 것을 기억해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;구문과 캐릭터 셋&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블&amp;nbsp;은 키와 값의 쌍이다. 유효한 레이블 키에는 슬래시(/)로 구분되는 선택한 접두사와 이름이라는 2개의 세그먼트가 있다. 이름 세그먼트는 63자 미만으로 시작과 끝은 알파벳과 숫자([a-z0-9A-Z])이며, 대시(-), 밑줄(_), 점(.)과 함께 사용할 수 있다. 접두사는 선택이다. 만약 접두사를 지정한 경우 접두사는 DNS의 하위 도메인으로 해야 하며, 점(.)과 전체 253자 이하, 슬래시(/)로 구분되는 DNS 레이블이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접두사를 생략하면 키 레이블은 개인용으로 간주한다. 최종 사용자의 오브젝트에 자동화된 시스템 컴포넌트(예:&amp;nbsp;kube-scheduler,&amp;nbsp;kube-controller-manager,&amp;nbsp;kube-apiserver,&amp;nbsp;kubectl&amp;nbsp;또는 다른 타사의 자동화 구성 요소)의 접두사를 지정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubernetes.io/와&amp;nbsp;k8s.io/&amp;nbsp;접두사는 쿠버네티스의 핵심 컴포넌트로&amp;nbsp;예약되어 있다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;kubectl get nodes --show-labels
NAME     STATUS   ROLES                  AGE   VERSION   LABELS
node-1   Ready    control-plane,master   24h   v1.22.8   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node-1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
node-2   Ready    &amp;lt;none&amp;gt;                 24h   v1.22.8   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node-2,kubernetes.io/os=linux
node-3   Ready    &amp;lt;none&amp;gt;                 24h   v1.22.8   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node-3,kubernetes.io/os=linux
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LabelSelector&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색을 담당&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리소스 간의 연결을 담당&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일치성(equality base)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;=&lt;/li&gt;
&lt;li&gt;==&lt;/li&gt;
&lt;li&gt;!=&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pods -l APP=nginx

kubectl get pods -l APP==nginx

kubectl get pods -l 'APP!=nginx'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;집합성(set base)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;in&lt;/li&gt;
&lt;li&gt;notin&lt;/li&gt;
&lt;li&gt;exists: 키만 매칭&lt;/li&gt;
&lt;li&gt;doesnotexists: 키 제외 매칭&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pods -l 'ENV in (dev, staging)'

kubectl get pods -l 'ENV notin (dev)'

kubectl get pods -l 'ENV'

kubectl get pods -l '!ENV'
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Annotations&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 어노테이션을 사용하여 임의의 비-식별 메타데이터를 오브젝트에 첨부할 수 있다. 도구 및 라이브러리와 같은 클라이언트는 이 메타데이터를 검색할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 레이블과 다르게 검색과 리소스 간 연결을 하는 특별한 기능이 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;오브젝트에 메타데이터 첨부&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블이나 어노테이션을 사용하여 쿠버네티스 오브젝트에 메타데이터를 첨부할 수 있다. 레이블을 사용하여 오브젝트를 선택하고, 특정 조건을 만족하는 오브젝트 컬렉션을 찾을 수 있다. 반면에, 어노테이션은 오브젝트를 식별하고 선택하는데 사용되지 않는다. 어노테이션의 메타데이터는 작거나 크고, 구조적이거나 구조적이지 않을 수 있으며, 레이블에서 허용되지 않는 문자를 포함할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션은 레이블과 같이 키/값 맵이다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;metadata&quot;: {
  &quot;annotations&quot;: {
    &quot;key1&quot; : &quot;value1&quot;,
    &quot;key2&quot; : &quot;value2&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 어노테이션에 기록할 수 있는 정보의 예제이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필드는 선언적 구성 계층에 의해 관리된다. 이러한 필드를 어노테이션으로 첨부하는 것은 클라이언트 또는 서버가 설정한 기본 값, 자동 생성된 필드, 그리고 오토사이징 또는 오토스케일링 시스템에 의해 설정된 필드와 구분된다.&lt;/li&gt;
&lt;li&gt;빌드, 릴리스, 또는 타임 스탬프, 릴리스 ID, git 브랜치, PR 번호, 이미지 해시 및 레지스트리 주소와 같은 이미지 정보.&lt;/li&gt;
&lt;li&gt;로깅, 모니터링, 분석 또는 감사 리포지터리에 대한 포인터.&lt;/li&gt;
&lt;li&gt;디버깅 목적으로 사용될 수 있는 클라이언트 라이브러리 또는 도구 정보: 예를 들면, 이름, 버전, 그리고 빌드 정보.&lt;/li&gt;
&lt;li&gt;다른 생태계 구성 요소의 관련 오브젝트 URL과 같은 사용자 또는 도구/시스템 출처 정보.&lt;/li&gt;
&lt;li&gt;경량 롤아웃 도구 메타데이터. 예: 구성 또는 체크포인트&lt;/li&gt;
&lt;li&gt;책임자의 전화번호 또는 호출기 번호, 또는 팀 웹 사이트 같은 해당 정보를 찾을 수 있는 디렉터리 진입점.&lt;/li&gt;
&lt;li&gt;행동을 수정하거나 비표준 기능을 수행하기 위한 최종 사용자의 지시 사항.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션을 사용하는 대신, 이 유형의 정보를 외부 데이터베이스 또는 디렉터리에 저장할 수 있지만, 이는 배포, 관리, 인트로스펙션(introspection) 등을 위한 공유 클라이언트 라이브러리와 도구 생성을 훨씬 더 어렵게 만들 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명령형 커맨드&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;kubectl annotate pods &amp;lt;파드_이름&amp;gt; Key=Value  # 어노테이션 추가
 
kubectl annotate pods &amp;lt;파드_이름&amp;gt; Key=Value --overwrite # 어노테이션 덮어 쓰기

kubectl annotate pods &amp;lt;파드_이름&amp;gt; Key-  # 어노테이션 삭제
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;YAML&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb
  labels:
    APP: apache
    ENV: dev
  annotations:
    Created-by: Choi
spec:
  containers:
    - name: myweb
      image: httpd
      ports:
      - containerPort: 80
        name: myweb
        protocol: TCP
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/242</guid>
      <comments>https://ssunw.tistory.com/entry/NameSpace-Label-Annotation#entry242comment</comments>
      <pubDate>Mon, 23 May 2022 12:52:25 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] POD</title>
      <link>https://ssunw.tistory.com/entry/POD</link>
      <description>&lt;h1&gt;Pod&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/workloads/pods/&quot;&gt;https://kubernetes.io/ko/docs/concepts/workloads/pods/&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파드는 컨테이너의 모음이다. 하나의 파드에 한 개 이상의 컨테이너가 있을 수 있다.&lt;/li&gt;
&lt;li&gt;쿠버네티스는 컨테이너를 직접 관리하지 않는다. 쿠버네티스에서 관리하는 가장 작은 워크로드는 파드이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;kubelet 이 서비스인 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubelet 은 api-server, cm, 스케줄러 등 쿠버네티스의 필수 불가결한 리소스들을 실행시켜주는 역할을 하기 때문에 파드가 아닌 서비스로서 동작한다. 만약 kubelet 까지 파드로 구성되어 있다면 kubernetes 를 띄울 녀석이 존재하지가 않기 때문에 서비스로 동작을 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정적 파드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 파드&amp;nbsp;는&amp;nbsp;API 서버가 관찰하는 대신 특정 노드의 &lt;b&gt;kubelet 데몬에 의해 직접 관리&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 파드는 컨트롤 플레인(예를 들어,&amp;nbsp;디플로이먼트)에 의해 관리되고, 정적 파드의 경우, kubelet이 각 정적 파드를 직접 감독한다(실패하면 다시 시작한다).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 파드는 항상 특정 노드의&amp;nbsp;Kubelet&amp;nbsp;하나에 바인딩된다. 정적 파드의 주요 용도는 자체 호스팅 컨트롤 플레인을 실행하는 것이다. 즉, kubelet을 사용하여 개별&amp;nbsp;컨트롤 플레인 컴포넌트를 감독한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubelet은 자동으로 각 정적 파드에 대한 쿠버네티스 API 서버에서&amp;nbsp;미러 파드를 생성하려고 한다. 즉, 노드에서 실행되는 파드는 API 서버에서 보이지만, 여기에서 제어할 수는 없다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/etc/kubernetes/manifest 에서 yml 파일로 pod 를 작성한 후 실행하면 정적 파드가 생성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kubectl 명령의 서브 명령&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl 에서 자주 사용하는 서브 명령어&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;create&lt;/li&gt;
&lt;li&gt;get&lt;/li&gt;
&lt;li&gt;describe&lt;/li&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;delete&lt;/li&gt;
&lt;li&gt;replace&lt;/li&gt;
&lt;li&gt;patch&lt;/li&gt;
&lt;li&gt;apply&lt;/li&gt;
&lt;li&gt;diff&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명령형 커맨드로 파드 생성&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl run myweb --image httpd

kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
myweb   1/1     Running   0          26s

kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE     IP             NODE     NOMINATED NODE   READINESS GATES
myweb   1/1     Running   0          2m21s   10.233.109.1   node-3   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파드 상세 정보&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl get pods -o yaml 를 사용해서 yaml 형태로 etcd 에서 원본 데이터를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-o wide, -o json 등으로 원하는 포맷팅으로 가져올 수 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;kubectl get pods -o yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Pod
  metadata:
    annotations:
      cni.projectcalico.org/containerID: ba98ccabd6b2b011e9654d76181414a90e7eec9729e207ceb6391d513bccfab2
      cni.projectcalico.org/podIP: 10.233.109.1/32
      cni.projectcalico.org/podIPs: 10.233.109.1/32
    creationTimestamp: &quot;2022-05-17T01:38:18Z&quot;
    labels:
      run: myweb
    name: myweb
    namespace: default
    resourceVersion: &quot;18433&quot;
    uid: 4741205a-6581-4ab6-bae5-dc0f6ac35dbc
  spec:
    containers:
    - image: httpd
      imagePullPolicy: Always
      name: myweb
      resources: {}
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: File
      volumeMounts:
      - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
        name: kube-api-access-ff6tf
        readOnly: true
    dnsPolicy: ClusterFirst
    enableServiceLinks: true
    nodeName: node-3
    preemptionPolicy: PreemptLowerPriority
    priority: 0
    restartPolicy: Always
    schedulerName: default-scheduler
    securityContext: {}
    serviceAccount: default
    serviceAccountName: default
    terminationGracePeriodSeconds: 30
    tolerations:
    - effect: NoExecute
      key: node.kubernetes.io/not-ready
      operator: Exists
      tolerationSeconds: 300
    - effect: NoExecute
      key: node.kubernetes.io/unreachable
      operator: Exists
      tolerationSeconds: 300
    volumes:
    - name: kube-api-access-ff6tf
      projected:
        defaultMode: 420
        sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
        - configMap:
            items:
            - key: ca.crt
              path: ca.crt
            name: kube-root-ca.crt
        - downwardAPI:
            items:
            - fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
              path: namespace
  status:
    conditions:
    - lastProbeTime: null
      lastTransitionTime: &quot;2022-05-17T01:38:19Z&quot;
      status: &quot;True&quot;
      type: Initialized
    - lastProbeTime: null
      lastTransitionTime: &quot;2022-05-17T01:38:38Z&quot;
      status: &quot;True&quot;
      type: Ready
    - lastProbeTime: null
      lastTransitionTime: &quot;2022-05-17T01:38:38Z&quot;
      status: &quot;True&quot;
      type: ContainersReady
    - lastProbeTime: null
      lastTransitionTime: &quot;2022-05-17T01:38:19Z&quot;
      status: &quot;True&quot;
      type: PodScheduled
    containerStatuses:
    - containerID: containerd://8129401885bc9f1e85e5051c514be51183a784134ad58c2581461e1a2d46e6f7
      image: docker.io/library/httpd:latest
      imageID: docker.io/library/httpd@sha256:2d1f8839d6127e400ac5f65481d8a0f17ac46a3b91de40b01e649c9a0324dea0
      lastState: {}
      name: myweb
      ready: true
      restartCount: 0
      started: true
      state:
        running:
          startedAt: &quot;2022-05-17T01:38:38Z&quot;
    hostIP: 192.168.100.105
    phase: Running
    podIP: 10.233.109.1
    podIPs:
    - ip: 10.233.109.1
    qosClass: BestEffort
    startTime: &quot;2022-05-17T01:38:19Z&quot;
kind: List
metadata:
  resourceVersion: &quot;&quot;
  selfLink: &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;describe&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;kubectl describe pods myweb
Name:         myweb
Namespace:    default
Priority:     0
Node:         node-3/192.168.100.105
Start Time:   Tue, 17 May 2022 01:38:19 +0000
Labels:       run=myweb
Annotations:  cni.projectcalico.org/containerID: ba98ccabd6b2b011e9654d76181414a90e7eec9729e207ceb6391d513bccfab2
              cni.projectcalico.org/podIP: 10.233.109.1/32
              cni.projectcalico.org/podIPs: 10.233.109.1/32
Status:       Running
IP:           10.233.109.1
IPs:
  IP:  10.233.109.1
Containers:
  myweb:
    Container ID:   containerd://8129401885bc9f1e85e5051c514be51183a784134ad58c2581461e1a2d46e6f7
    Image:          httpd
    Image ID:       docker.io/library/httpd@sha256:2d1f8839d6127e400ac5f65481d8a0f17ac46a3b91de40b01e649c9a0324dea0
    Port:           &amp;lt;none&amp;gt;
    Host Port:      &amp;lt;none&amp;gt;
    State:          Running
      Started:      Tue, 17 May 2022 01:38:38 +0000
    Ready:          True
    Restart Count:  0
    Environment:    &amp;lt;none&amp;gt;
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-ff6tf (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  kube-api-access-ff6tf:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       &amp;lt;nil&amp;gt;
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              &amp;lt;none&amp;gt;
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  6m22s  default-scheduler  Successfully assigned default/myweb to node-3
  Normal  Pulling    6m21s  kubelet            Pulling image &quot;httpd&quot;
  Normal  Pulled     6m9s   kubelet            Successfully pulled image &quot;httpd&quot; in 12.366300856s
  Normal  Created    6m4s   kubelet            Created container myweb
  Normal  Started    6m3s   kubelet            Started container myweb
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;event 는 myweb pod 의 라이프 사이클이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 처음 디폴트 스케줄러에 의해 이벤트가 발생한다. 노드-3 에 파드를 배치했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubelet 이 httpd 이미지를 풀링한다. httpd 폴링이 완료되고 kubelet 이 myweb 컨테이너를 생성하고 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드를 생성했을 때 컨테이너가 제대로 작동하지 않을 경우 kubectl describe pods 로 파드 자체의 로그를 확인하고 그 후에 애플리케이션의 로그를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션의 로그를 확인하기 위해서는 kubectl logs &amp;lt;파드_이름&amp;gt; 명령어로 확인 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파드 삭제&lt;/h3&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;kubectl delete pods &amp;lt;파드_이름&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;YAML 파일로 파드 정의&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;kubectl explain pods
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myweb.yml&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: myweb
spec:
  containers:
    - name: myweb
      image: httpd
      ports:
      - containerPort: 80
        name: myweb
        protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Workload resources for managing pods&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 싱글톤(singleton) 파드를 포함하여 파드를 직접 만들 필요가 없다. 대신,&amp;nbsp;디플로이먼트(Deployment)&amp;nbsp;또는&amp;nbsp;잡(Job)과 같은 워크로드 리소스를 사용하여 생성한다. 파드가 상태를 추적해야 한다면,&amp;nbsp;스테이트풀셋(StatefulSet)&amp;nbsp;리소스를 고려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 클러스터의 파드는 두 가지 주요 방식으로 사용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단일 컨테이너를 실행하는 파드&lt;/b&gt;. &quot;파드 당 하나의 컨테이너&quot; 모델은 가장 일반적인 쿠버네티스 유스케이스이다. 이 경우, 파드를 단일 컨테이너를 둘러싼 래퍼(wrapper)로 생각할 수 있다. 쿠버네티스는 컨테이너를 직접 관리하는 대신 파드를 관리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;함께 작동해야 하는 여러 컨테이너를 실행하는 파드&lt;/b&gt;. 파드는 밀접하게 결합되어 있고 리소스를 공유해야 하는 함께 배치된 여러 개의 컨테이너로 구성된 애플리케이션을 캡슐화할 수 있다. 이런 함께 배치된 컨테이너는 하나의 결합된 서비스 단위를 형성한다. 예를 들어, 하나의 컨테이너는 공유 볼륨에 저장된 데이터를 퍼블릭에 제공하는 반면, 별도의&amp;nbsp;사이드카&amp;nbsp;컨테이너는 해당 파일을 새로 고치거나 업데이트한다. 파드는 이러한 컨테이너, 스토리지 리소스, 임시 네트워크 ID를 단일 단위로 함께 래핑한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 파드에는 반드시 하나의 메인 어플리케이션만 있어야 한다. 파드에 여러 컨테이너가 있을 경우에는 메인 어플리케이션 + 메인 어플리케이션을 서포팅하는 다른 컨테이너여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 파드에 메인 애플리케이션이 여러개 띄워지면 안된다. -&amp;gt; 안티 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 파드안에 메인이 되는 웹서버가 있고 해당 웹서버로 코드를 가져오거나 정적인 파일을 가져오는 메인 앱을 서포팅하는 앱과 웹서버에서 보내준 로그를 로그 서버로 보내는 메인 앱을 서포팅하는 앱으로 파드가 구성될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 파드는 하나의 호스트(노드)에 배치된다. 즉, 파드 안여 여러 대의 컨테이너가 있어도 하나의 호스트(노드)에 모두 배치된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파드 디자인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드카 패턴: &lt;a href=&quot;https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/&quot;&gt;https://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 컨테이너: 가장 일반적인 형태, 대부분의 파드는 단일 컨테이너로 이루어져 있다.&lt;/li&gt;
&lt;li&gt;멀티 컨테이너: 메인 애플리케이션이 존재하고 메인 애플리케이션 기능을 확장하기 위한 컨테이너를 배치
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sidecar: 기능의 확장&lt;/li&gt;
&lt;li&gt;ambassador: proxy/LB&lt;/li&gt;
&lt;li&gt;adaptor: 출력의 표준&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드는 응집력있는 서비스 단위를 형성하는 여러 협력 프로세스(컨테이너)를 지원하도록 설계되었다. 파드의 컨테이너는 클러스터의 동일한 물리 또는 가상 머신에서 자동으로 같은 위치에 배치되고 함께 스케줄된다. 컨테이너는 리소스와 의존성을 공유하고, 서로 통신하고, 종료 시기와 방법을 조정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 다음 다이어그램에서와 같이 공유 볼륨의 파일에 대한 웹 서버 역할을 하는 컨테이너와, 원격 소스에서 해당 파일을 업데이트하는 별도의 &quot;사이드카&quot; 컨테이너가 있을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vyZOh/btrCVRRWBTl/uVMqjpj3ApGW3ZtAb1mzhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vyZOh/btrCVRRWBTl/uVMqjpj3ApGW3ZtAb1mzhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vyZOh/btrCVRRWBTl/uVMqjpj3ApGW3ZtAb1mzhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvyZOh%2FbtrCVRRWBTl%2FuVMqjpj3ApGW3ZtAb1mzhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;402&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 파드에는&amp;nbsp;앱 컨테이너&amp;nbsp;뿐만 아니라&amp;nbsp;초기화 컨테이너를 갖고 있다. 초기화 컨테이너는 앱 컨테이너가 시작되기 전에 실행되고 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드는 기본적으로 파드에 속한 컨테이너에&amp;nbsp;네트워킹과&amp;nbsp;스토리지라는 두 가지 종류의 공유 리소스를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드는 딱 하나의 IP 주소를 부여 받는다. 즉, 파드 안에 몇 개의 컨테이너가 있든 간에 모두 같은 IP 주소를 사용한다. &amp;rarr; 공유 네트워크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드에 볼륨을 설정해주면 해당하는 볼륨을 파드 안의 모든 컨테이너가 접근할 수 있고 사용할 수 있다. &amp;rarr; 공유 스토리지&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;포트 및 포트 포워딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 &amp;amp; 디버깅 목적이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl port-forward pods/&amp;lt;파드_이름&amp;gt; 8080:80
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/241</guid>
      <comments>https://ssunw.tistory.com/entry/POD#entry241comment</comments>
      <pubDate>Mon, 23 May 2022 12:51:18 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Kubernetes Objects</title>
      <link>https://ssunw.tistory.com/entry/Kubernetes-Objects</link>
      <description>&lt;h1&gt;Kubernetes Objects&lt;/h1&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;kubectl api-resources
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Label / LabelSelector: 오브젝트와 오브젝트를 연결할 때 사용하는 개념으로 오브젝트와 오브젝트의 관계를 설명할 때 사용한다. AWS 태그같은 개념으로 생각하면 된다.&lt;/li&gt;
&lt;li&gt;Workload
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod: 제일 핵심, Pod 는 컨테이너라고 볼 수 있다.&lt;/li&gt;
&lt;li&gt;Controller: 파드를 제어하는 컨트롤러
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ReplicationController: ReplicaSets 가 나온 이후로 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;ReplicaSets&lt;/li&gt;
&lt;li&gt;DaemonSets&lt;/li&gt;
&lt;li&gt;Jobs: 배치 작업&lt;/li&gt;
&lt;li&gt;CronJobs: 배치 작업&lt;/li&gt;
&lt;li&gt;Deployments: 배포, stateless 를 관리, 쿠버네티스의 핵심!&lt;/li&gt;
&lt;li&gt;StatefulSets: stateless 와 상반&lt;/li&gt;
&lt;li&gt;HorizontalPodAutoscaler(HPA): 파드의 오토 스케일링을 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Network
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;service: L4 LB&lt;/li&gt;
&lt;li&gt;Endpoints: 로드 밸런서의 백엔드&lt;/li&gt;
&lt;li&gt;Ingress: L7 LB, Add-on 으로 설치해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Storage
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PersistentVolume(PV)&lt;/li&gt;
&lt;li&gt;PersistentVolumeClaim(PVC): PV 를 요청&lt;/li&gt;
&lt;li&gt;ConfigMap: 파드에 데이터를 제공하기 위한 Key Value 스토리지&lt;/li&gt;
&lt;li&gt;Secret: 파드에 데이터를 제공하기 위한 Key Value 스토리지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Authentication: ~/.kube/config 파일과 관련된 내용들이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ServiceAccount(SA): 계정에 관련된 개념&lt;/li&gt;
&lt;li&gt;RBAC: 역할 기반의 접근 제어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Role&lt;/li&gt;
&lt;li&gt;ClusterRole&lt;/li&gt;
&lt;li&gt;RoleBinding: (Binding)계정과 역할을 연결할 때 사용&lt;/li&gt;
&lt;li&gt;ClusterRoleBinding: (Binding)계정과 역할을 연결할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Resource Isolation
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Namespaces: 리소스를 분리하기 위해 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Resource Limits: 파드 리소스를 제한
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Limits&lt;/li&gt;
&lt;li&gt;Requests&lt;/li&gt;
&lt;li&gt;ResourceQuota&lt;/li&gt;
&lt;li&gt;LimitRange&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Schedulling
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NodeName&lt;/li&gt;
&lt;li&gt;NodeSelector&lt;/li&gt;
&lt;li&gt;Affinity(선호도)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node Affinity&lt;/li&gt;
&lt;li&gt;Pod Affinity&lt;/li&gt;
&lt;li&gt;Pod Anti Affinity&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Taints/Tolerations&lt;/li&gt;
&lt;li&gt;Drani/Cordon: Drain &amp;rarr; 배출, Cordon &amp;rarr; 스케줄링을 하지 않게 막아버린다. Drain 을 할 경우 자동으로 Cordon 이 작동된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;쿠버네티스 오브젝트 이해하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어로 쿠버네티스 오브젝트를 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;kubectl api-resources 
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/kubernetes-objects/&quot;&gt;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/kubernetes-objects/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 api 공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/reference/&quot;&gt;https://kubernetes.io/ko/docs/reference/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 오브젝트&amp;nbsp;는 쿠버네티스 시스템에서 영속성을 가지는 오브젝트이다. 쿠버네티스는 클러스터의 상태를 나타내기 위해 이 오브젝트를 이용한다. 구체적으로 말하자면, 다음같이 기술할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 컨테이너화된 애플리케이션이 동작 중인지 (그리고 어느 노드에서 동작 중인지)&lt;/li&gt;
&lt;li&gt;그 애플리케이션이 이용할 수 있는 리소스&lt;/li&gt;
&lt;li&gt;그 애플리케이션이 어떻게 재구동 정책, 업그레이드, 그리고 내고장성과 같은 것에 동작해야 하는지에 대한 정책&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 오브젝트는 하나의 &quot;의도를 담은 레코드&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트를 생성하게 되면, 쿠버네티스 시스템은 그 오브젝트 생성을 보장하기 위해 지속적으로 작동할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트를 생성함으로써, 여러분이 클러스터의 워크로드를 어떤 형태로 보이고자 하는지에 대해 효과적으로 쿠버네티스 시스템에 전한다. 이것이 바로 여러분의 클러스터에 대해 의도한 상태가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성이든, 수정이든, 또는 삭제든 쿠버네티스 오브젝트를 동작시키려면,&amp;nbsp;쿠버네티스 API를 이용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, kubectl 커맨드-라인 인터페이스를 이용할 때, CLI는 여러분 대신 필요한 쿠버네티스 API를 호출해 준다. 또한, 클라이언트 라이브러리 중 하나를 이용하여 내 프로그램에서 쿠버네티스 API를 직접 이용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트를 YAML 혹은 명령어로 생성할 것인지 정해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오브젝트 명세(spec)와 상태(status)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 모든 쿠버네티스 오브젝트는 오브젝트의 구성을 결정해주는 두 개의 중첩된 오브젝트 필드를 포함하는데 오브젝트&amp;nbsp;spec&amp;nbsp;과 오브젝트&amp;nbsp;status&amp;nbsp;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec을 가진 오브젝트는 오브젝트를 생성할 때 리소스에 원하는 특징(의도한 상태)에 대한 설명을 제공해서 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;status&amp;nbsp;는 쿠버네티스 시스템과 컴포넌트에 의해 제공되고 업데이트된 오브젝트의&amp;nbsp;현재 상태&amp;nbsp;를 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스&amp;nbsp;컨트롤 플레인은 모든 오브젝트의 실제 상태를 사용자가 의도한 상태와 일치시키기 위해 끊임없이 그리고 능동적으로 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 보통 spec 을 작성한다. 오브젝트의 종류에 따라 spec 이 없는 경우도 있지만 극히 드물다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예제를 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apiVersion, kind, metadata, spec 은 모든 리소스에 항상 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트에 따라서 지원하는 apiVersion 이 모두 다르다. 즉, apiVersion 은 오브젝트의 종류에 따라 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kind 는 오브젝트의 종류를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl api-resources 명령어를 입력했을 때 맨 오른쪽에 있는 KIND 란에 있는 녀석과 똑같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metadata 는 오브젝트를 설명하기 위한 데이터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스의 이름(name), 리소스의 라벨(label), 주석(annotation) 키워드 등을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec 은 kind 에 따라서 달라지게 된다. 즉, 어떤 종류의 오브젝트를 정의하냐에 따라 달라진다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;apiVersion&amp;nbsp;- 이 오브젝트를 생성하기 위해 사용하고 있는 쿠버네티스 API 버전이 어떤 것인지&lt;/li&gt;
&lt;li&gt;kind&amp;nbsp;- 어떤 종류의 오브젝트를 생성하고자 하는지&lt;/li&gt;
&lt;li&gt;metadata&amp;nbsp;-&amp;nbsp;이름&amp;nbsp;문자열,&amp;nbsp;UID, 그리고 선택적인&amp;nbsp;네임스페이스를 포함하여 오브젝트를 유일하게 구분지어 줄 데이터&lt;/li&gt;
&lt;li&gt;spec&amp;nbsp;- 오브젝트에 대해 어떤 상태를 의도하는지, 선언형&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;kubectl explain &amp;lt;오브젝트_이름&amp;gt;
# 해당 필드에서 사용할 수 있는 모든 필드들을 확인할 수 있다.
kubectl explain &amp;lt;오브젝트_이름&amp;gt;.&amp;lt;필드명&amp;gt; --recursive 
kubectl explain &amp;lt;오브젝트_이름&amp;gt;.&amp;lt;필드명&amp;gt;.&amp;lt;필드명&amp;gt;.....

kubectl explain pods
kubectl explain pods.spec
kubectl explain pods.spec.containers
kubectl explain pods.spec.containers.ports
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오브젝트의 버전&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/reference/using-api/#api-그룹&quot;&gt;https://kubernetes.io/ko/docs/reference/using-api/#api-그룹&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 쿠버네티스 버전에서 지원되는 api 버전들을 아래 명령어로 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;	kubectl api-versions
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stable
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vX&lt;/li&gt;
&lt;li&gt;v1, v2&lt;/li&gt;
&lt;li&gt;안정화 된 버전&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Beta
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;v1betaX, v2betaX&lt;/li&gt;
&lt;li&gt;충분히 검증, 오류가 거의 없다.&lt;/li&gt;
&lt;li&gt;버전이 올라가면 기능 변경이 있을 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;downtime 발생할 수도 있다. 즉, 특정 기능을 사용하기 위해 재시작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mission Critical&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Alpha
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;v1alphaX, v2alphaX&lt;/li&gt;
&lt;li&gt;기본적으로 비활성화&lt;/li&gt;
&lt;li&gt;개발 중인 API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dev &amp;rarr; Alpha &amp;rarr; Beta &amp;rarr; Stable&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오브젝트 관리 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서: &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/object-management/&quot;&gt;https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/object-management/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AVUJq/btrCMNbXZB4/5zKcX0uaRYIwz4iRcG3np0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AVUJq/btrCMNbXZB4/5zKcX0uaRYIwz4iRcG3np0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AVUJq/btrCMNbXZB4/5zKcX0uaRYIwz4iRcG3np0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAVUJq%2FbtrCMNbXZB4%2F5zKcX0uaRYIwz4iRcG3np0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;230&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명령형 커맨드: kubectl 명령어로만 구성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl create&lt;/li&gt;
&lt;li&gt;kubctl run&lt;/li&gt;
&lt;li&gt;kubctl expose&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;명령형 오브젝트 구성: YAML 파일 작성, 명령형==절차형. 오브젝트를 구성해 놓은 YAML 파일 여러개가 있을 때 이 파일을 순서대로 하나씩 실행한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl create -f a.yml&lt;/li&gt;
&lt;li&gt;kubectl apply -f a.yml&lt;/li&gt;
&lt;li&gt;kubectl replace -f a.yml&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;선언형 오브젝트 구성: YAML 파일 작성, YAML 파일이 있는 디렉토리를 한 번에 실행한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubectl create -f resources&lt;/li&gt;
&lt;li&gt;kubectl apply -f resoureces&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 명령형, 선언형 오브젝트 구성을 사용한다.&lt;/p&gt;</description>
      <category>kubernetes</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/240</guid>
      <comments>https://ssunw.tistory.com/entry/Kubernetes-Objects#entry240comment</comments>
      <pubDate>Mon, 23 May 2022 12:48:11 +0900</pubDate>
    </item>
    <item>
      <title>docker compose</title>
      <link>https://ssunw.tistory.com/entry/docker-compose</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Docker Compose&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서 : &lt;a href=&quot;https://docs.docker.com/compose/compose-file/compose-file-v3/&quot;&gt;https://docs.docker.com/compose/compose-file/compose-file-v3/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스를 사용하게 될 경우 도커 컴포즈를 사용할 일은 거의 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컴포즈는 도커 IaC 라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컴포즈의 버전은 도커 엔진에 따라 다르므로 공식 문서를 확인하고 필요한 도커 컴포즈 버전을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose &amp;lt;커맨드&amp;gt; 로 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml 또는 docker-compose.yaml 에 코드를 작성해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 1&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;version: '3'

services:
  web:
    image: httpd
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;version: &amp;lsquo;3&amp;rsquo; 은 3의 가장 최신 버전을 사용, service 는 컨테이너이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose run 과 up 은 다르다. up 은 도커 컴포즈를 생성할 때 사용하고 run 은 이미 띄워진 녀석을 다시 실행할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 목록 확인&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;docker compose ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 목록(컨테이너 목록)&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;docker compose ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종료&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 2&lt;/h3&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;version: '3'

services:
  web:
    image: httpd
    restart: always
    ports:
      - 80:80
    environment:
      MSG: hello world
    volumes:
      - web-contents:/var/www/html
    networks:
      - web-net
  web2:
    image: nginx
    networks:
      - web-net

volumes:
  web-contents:

networks:
  web-net:
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 내부에 작성되는 networks 는 네트워크를 만드는 것이 아니라 이 컨테이너의 네트워크를 어디에 연결할 것인지 작성하는 곳이고 가장 바깥에 있는 networks 가 docker compose 가 사용할 네트워크의 이름을 지정하는 곳이다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;$ docker compose up
$ docker compose exec web bash
# apt update; apt install curl
# curl web2 
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방식처럼 web 컨테이너에 접속을 한 후 curl 로 web2 에 바로 접근이 가능하다. 즉, link 를 해줄 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컴포즈에서는 컨테이너들끼리는 아이피를 알 필요 없이 이름으로 통신이 서로 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 3&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;version: '3'

services:  
  myflask:    
    build: ./hello-flask  
  mydjango:    
    build: ./hello-django
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hello-flask 와 hello-django 디렉토리에 도커 파일이 있고 build 에 의해 자동으로 도커 파일을 빌드하여 컨테이너를 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/Users/csw/tmp/docker/jenkins_home/workspace&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 예제 2의 방법을 많이 사용하고 현업에서는 도커 컴포즈 방식을 사용하지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도커 컴포즈로 워드프레스 띄우기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컴포즈로 컨테이너를 띄울 경우 어떤 녀석이 먼저 생성될지 모르기 때문에 depends_on 키워드를 사용해서 db 컨테이너가 생성된 후에 워드프레스 컨테이너가 생성되도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 환경 변수들은 도커 공식 이미지에 나와있는 환경 변수들이다.&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;version: '3'

services:
   db:
     image: mysql:5.7
     volumes:
       - ./mysql:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: 1234
       MYSQL_DATABASE: wordpress
       MYSQL_USER: admin
       MYSQL_PASSWORD: 1234
   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - &quot;8000:80&quot;
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: admin
       WORDPRESS_DB_PASSWORD: 1234
       WORDPRESS_DB_NAME: wordpress
     volumes:
       - ./html:/var/www/html
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Docker</category>
      <author>ssunw</author>
      <guid isPermaLink="true">https://ssunw.tistory.com/239</guid>
      <comments>https://ssunw.tistory.com/entry/docker-compose#entry239comment</comments>
      <pubDate>Mon, 16 May 2022 20:06:37 +0900</pubDate>
    </item>
  </channel>
</rss>