<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>턴태의 밑바닥부터 시작하는 de-vlog</title>
    <link>https://dev-scratch.tistory.com/</link>
    <description>import { Dream } from &amp;quot;future&amp;quot;;</description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 13:37:30 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>턴태</managingEditor>
    <image>
      <title>턴태의 밑바닥부터 시작하는 de-vlog</title>
      <url>https://tistory1.daumcdn.net/tistory/5236506/attach/ced56778783f4073ad75d112c5e422e1</url>
      <link>https://dev-scratch.tistory.com</link>
    </image>
    <item>
      <title>redis의 pubsub과 sharded pubsub</title>
      <link>https://dev-scratch.tistory.com/190</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sharded Pub/Sub 의 등장&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;From Redis 7.0, sharded Pub/Sub is introduced in which shard channels are assigned to slots by the same algorithm used to assign keys to slots. A shard message must be sent to a node that owns the slot the shard channel is hashed to. The cluster makes sure the published shard messages are forwarded to all nodes in the shard, so clients can subscribe to a shard channel by connecting to either the master responsible for the slot, or to any of its replicas. SSUBSCRIBE, SUNSUBSCRIBE and&amp;nbsp;SPUBLISH&amp;nbsp;are used to implement sharded Pub/Sub.&lt;br /&gt;&lt;br /&gt;https://redis.io/docs/latest/develop/pubsub/#sharded-pubsub&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;레디스 7.0 버전부터 sharded Pub/Sub이라는 기능이 도입되었습니다. 기존의 Redis Pub/Sub이 레디스 클러스터 환경에서 불필요한 리소스 사용과 네트워크 부하를 초래한다는 문제가 존재하므로, 이를 해결하기 위해 레디스는 클러스터 환경에서 효율적으로 사용할 수 있는 sharded Pub/Sub 기능을 출시하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러면 왜 클러스터 환경에서는 Redis Pub/Sub이 문제가 될까요?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클러스터 환경에서의 Redis Pub/Sub의 문제점&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2404&quot; data-origin-height=&quot;1284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkbY8V/dJMcagYtTfP/k0DnSK3XQ1DRm3kbkQ9jYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkbY8V/dJMcagYtTfP/k0DnSK3XQ1DRm3kbkQ9jYK/img.png&quot; data-alt=&quot;클러스터 환경에서 pub/sub의 동작 방식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkbY8V/dJMcagYtTfP/k0DnSK3XQ1DRm3kbkQ9jYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkbY8V%2FdJMcagYtTfP%2Fk0DnSK3XQ1DRm3kbkQ9jYK%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;609&quot; height=&quot;325&quot; data-origin-width=&quot;2404&quot; data-origin-height=&quot;1284&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;클러스터 환경에서 pub/sub의 동작 방식&lt;/figcaption&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;size18&quot;&gt;일반 Redis Pub/Sub에서 메시지를 발행하면, 메시지는 모든 노드에 전파됩니다. 그래서 Subscribe하고 있는 서버는 아무 레디스 노드에 subscribe하여도 메시지를 수신할 수 있게 됩니다. 하지만, 이렇게 모든 노드에 데이터를 전파하는 것은 클러스터를 효율적으로 사용하지 못하는 구조이며, 불필요하게 리소스를 사용하고 네트워크 부하도 발생할 수 있습니다. 실제로 AWS의 ElastiCache 사용자 가이드에서도 cluster 환경에서 pub/sub을 사용하는 것은 EngineCPUUtilization이 높아지는 결과를 초래할 수 있다고 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Use cluster-mode enabled configurations &amp;ndash; Cluster-mode enabled allows the cache to scale horizontally to achieve higher storage and throughput than a cluster-mode disabled configuration. ElastiCache serverless is only available in a cluster-mode enabled configuration.&lt;br /&gt;&lt;br /&gt;https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/WorkingWithRedis.html&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;레디스를 스케일 아웃하여 CPU, 메모리, 대역폭 등을 늘리는 것이 레디스 클러스터의 목적인데, 그 목적성과 맞지 않는 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sharded Pub/Sub의 동작 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이와 달리 sharded pub/sub은 각 채널을 슬롯에 매핑합니다. 레디스 클러스터에서는 데이터를 샤딩하기 위해 16,384개의 해시 슬롯을 사용하며, 각 슬롯들은 노드가 나눠서 가져가게 됩니다. 각 키들은 해시함수를 거쳐 특정 해시 슬롯에 할당되며, 이 해시 슬롯을 담당하는 노드가 해당 키를 저장하는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;user:1000 키 -&amp;gt; CRC 16 해시 -&amp;gt; 슬롯 12539 -&amp;gt; Node C가 담당&lt;/li&gt;
&lt;li&gt;order:5678 키 -&amp;gt; CRC 16 해시 -&amp;gt; 슬롯 3921 -&amp;gt; Node A가 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이와 같이 해시 슬롯을 담당하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Sharded Pub/Sub에서는 채널이 슬롯에 할당됩니다. 특정 채널을 해시화하여 슬롯을 할당받고, 해당 슬롯을 담당하는 노드에 메시지를 발행/구독처리하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;orders 채널에 publish -&amp;gt; CRC 16 해시 -&amp;gt; 슬롯 7259 -&amp;gt; Node B가 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이와 같이 해시 슬롯을 담당하여 Pub/Sub을 처리합니다. Node A나 Node C가 불필요하게 관여하지 않는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그런데 레디스 채널을 구독하려는 Subscriber는 내가 구독할 채널이 어떤 노드에 할당되었는지 어떻게 알 수 있을까요? 일반적으로 redis-cli를 사용한다면 cluster 모드를 사용했을 때, 슬롯이 해당된 노드로 알아서 subscribe 처리를 해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG5gHI/dJMcagqEOae/eb867wAgV0bHeuWBycWwkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG5gHI/dJMcagqEOae/eb867wAgV0bHeuWBycWwkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG5gHI/dJMcagqEOae/eb867wAgV0bHeuWBycWwkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG5gHI%2FdJMcagqEOae%2Feb867wAgV0bHeuWBycWwkK%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;2136&quot; height=&quot;1070&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;로컬 환경에서 레디스 클러스터를 구성하고 위와 같이 SSUBSCRIBE 요청을 보냈을 때, 알아서 슬롯이 해당된 노드로 리다이렉트되는 모습을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이처럼 Sharded Pub/Sub을 사용하면 굳이 모든 노드에 데이터를 전파할 필요 없이, 필요한 노드만 해당 데이터를 저장하여 조금 더 효율적으로 클러스터 환경에서 Pub/Sub 기능을 사용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재 회사에서 레디스 Pub/Sub을 활용하는 기능에서 네트워크 송신 트래픽을 개선한 사례가 있었습니다. 하지만, 제가 예측한 만큼 네트워크 트래픽이 개선되지 않았고 그 문제를 딥다이브 하다가 레디스 클러스터 환경에서 Pub/Sub을 사용하는 것의 문제점을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;무엇을 개발하든 그 기술의 동작 방식을 이해하고 탐구하는 것이 중요하다는 것을 깨달은 순간입니다. node 진영에서 sharded Pub/Sub을 지원하는 라이브러리가 많지 않고 있어도 아직 레퍼런스가 적어서 조금 더 시간이 지난 후에 도입해보면 좋을 만한 기능인 것 같습니다. :)&lt;/p&gt;</description>
      <category>백엔드</category>
      <category>elasticache</category>
      <category>Pubsub</category>
      <category>Redis</category>
      <category>sharded pubsub</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/190</guid>
      <comments>https://dev-scratch.tistory.com/190#entry190comment</comments>
      <pubDate>Wed, 7 Jan 2026 20:46:25 +0900</pubDate>
    </item>
    <item>
      <title>BullMQ로 중복된 이벤트 제거하기</title>
      <link>https://dev-scratch.tistory.com/189</link>
      <description>&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;blob&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5qTGB/btsMgSgFRsx/sce1ctoWuZWeobKHpGKMZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5qTGB/btsMgSgFRsx/sce1ctoWuZWeobKHpGKMZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5qTGB/btsMgSgFRsx/sce1ctoWuZWeobKHpGKMZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5qTGB%2FbtsMgSgFRsx%2Fsce1ctoWuZWeobKHpGKMZ0%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;1029&quot; height=&quot;416&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;최근에 현업에서 업무를 진행하면서 특정 시간마다 DB에 부하가 심하게 발생하여 해당 클러스터를 사용하는 전체 서비스에 장애가 발생하고 있었습니다. 중복된 이벤트가 발생하여 수많은 콜백 로직이 동시다발적으로 수행되어 DB에 수많은 쿼리가 한 번에 요청되는 것이 문제였습니다. 과도한 Write 요청으로 인해 락을 획득하지 못하는 작업이 대기열에 쌓이고 Timout에 도달하여 오류가 발생했습니다. Unable to acquire ticket with mode '2' within a max lock request timeout of '5ms' milliseconds. 와 같은 에러 메시지에서도 볼 수 있듯이, 락 모드 '2'인 배타적 락을 획득하기 위한 티켓을 획득하지 못하여 문제가 발생하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 원인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 레디스 Pub/Sub&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때, DB에 문제를 가하는 중복된 이벤트가 발생한 이유는 Redis Pub/Sub 구조가 모든 구독자에게 메시지를 푸시하기 때문입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Messages sent by other clients to these channels will be pushed by Redis to all the subscribed clients. Subscribers receive the messages in the order that the messages are published.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://redis.io/docs/latest/develop/interact/pubsub/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://redis.io/docs/latest/develop/interact/pubsub/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739372479991&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Redis Pub/Sub&quot; data-og-description=&quot;How to use pub/sub channels in Redis&quot; data-og-host=&quot;redis.io&quot; data-og-source-url=&quot;https://redis.io/docs/latest/develop/interact/pubsub/&quot; data-og-url=&quot;https://redis.io/docs/latest/develop/interact/pubsub/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://redis.io/docs/latest/develop/interact/pubsub/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://redis.io/docs/latest/develop/interact/pubsub/&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;Redis Pub/Sub&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How to use pub/sub channels in Redis&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;redis.io&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;size18&quot;&gt;broadcast 방식으로 구독자에게 메시지를 발행하므로 redis에서 발행한 이벤트를 모든 k8s 파드에서 전달받아 파드 개수만큼 메시지가 중복되고, 1번만 수행해야 할 후속 로직이 N배로 증가하여 부하가 심하게 발생합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WDxn5/btsMhTeRFjz/Ci34fBTy5zdkXG1NeUVvF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WDxn5/btsMhTeRFjz/Ci34fBTy5zdkXG1NeUVvF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WDxn5/btsMhTeRFjz/Ci34fBTy5zdkXG1NeUVvF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWDxn5%2FbtsMhTeRFjz%2FCi34fBTy5zdkXG1NeUVvF1%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;699&quot; height=&quot;524&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이뿐만 아니라, 각 메시지를 한 번의 쿼리로 수행하지 않고 각각 쿼리로 요청하기 때문에 그만큼 DB 통신 횟수가 증가하는 것도 문제 중 하나였습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2️⃣ MongoDB 과부하&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;MongoDB는 동시성 제어를 위해 읽기/쓰기 티켓 시스템을 제공합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기본적으로 MongoDB는 128개의 쓰기 티켓을 제공하며, 쓰기 작업 하나 당 1개의 티켓을 점유하고, 티켓이 모두 소진되면 후속 작업은 대기열에 들어갑니다. 대기열에 들어간 시간이 maxLockRequestTime을 초과하면 오류가 발생합니다. 너무 과한 쓰기 작업을 요청하면 티켓이 부족하여 실패할 가능성이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;커넥션 개수가 과하게 증가하면 DB 리소스에 부하가 발생합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;커넥션은 각각 스레드를 점유하여 컨텍스트 스위칭 비용이 증가하고 자연스레 CPU에 부담을 주게 됩니다. CPU에 부담을 주므로 대기열에 쌓이는 작업을 빠르게 해소하지 못해 메모리에도 부담을 줍니다. 또한, 커넥션 자체가 많아지므로 병목이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;결론적으로 단기간에 과도한 요청이 몰려 DB에 부하를 주었고, 트랜잭션이 락을 위한 티켓을 획득하지 못하여 오류가 발생하는 것으로 판단하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 이를 해결하고자 파드별로 수신한 메시지들의 중복을 없애고, 메시지 수신 후 후속 로직 작업을 Batch로 묶어서 쿼리하는 것을 중점적으로 개발하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 해결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 중복된 이벤트 제거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. BullMQ에 job을 추가하며 중복된 job을 제거&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4800&quot; data-origin-height=&quot;2560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE9nHB/btsMgi1uwVx/1KKxENUSdWZGwxa4q9ESXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE9nHB/btsMgi1uwVx/1KKxENUSdWZGwxa4q9ESXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE9nHB/btsMgi1uwVx/1KKxENUSdWZGwxa4q9ESXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcE9nHB%2FbtsMgi1uwVx%2F1KKxENUSdWZGwxa4q9ESXK%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;4800&quot; height=&quot;2560&quot; data-origin-width=&quot;4800&quot; data-origin-height=&quot;2560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;BullMQ는 JobId를 명시하여 큐에 메시지를 적재할 수 있습니다. 이때, job id는 메시지 별로 유니크하기 때문에 동일한 job id를 가진 메시지가 들어온다면 중복된 메시지로 판단하여 작업을 수행하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어 아래 빨간 박스처럼 여러 개의 메시지를 적재할 때, 두 파드에서 같은 키의 이벤트를 발행하였지만 내부적으로 duplicated 처리되어 작업을 수행할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;BullMQ는 job을 처리할 때, added -&amp;gt; waiting -&amp;gt; active 순으로 작업을 완료하기 때문에 active 상태가 되기 전에 동일한 jobId의 요청이 발생한다면, duplicated 처리되어 작업을 더 이상 수행하지 않게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;1306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgbPxF/btsMnPRCvb3/DJEOBPKcVJIy1fV9dIYAP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgbPxF/btsMnPRCvb3/DJEOBPKcVJIy1fV9dIYAP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgbPxF/btsMnPRCvb3/DJEOBPKcVJIy1fV9dIYAP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgbPxF%2FbtsMnPRCvb3%2FDJEOBPKcVJIy1fV9dIYAP1%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;1880&quot; height=&quot;1306&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;1306&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;size18&quot;&gt;자세한 코드는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1739885173194&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class EventQueueService implements OnModuleInit {
	private readonly logger = new Logger(EventQueueService.name);

	constructor(
		@Inject(REDIS_READ_CLIENT) private redisClient: Redis, // 레디스 클라이언트를 주입받습니다.
		@InjectQueue('event-queue') private readonly eventQueue: Queue,
	) {}
	
	async onModuleInit() {
		this.redisClient.subscribe(&quot;__keyevent@0__:expired&quot;, (err, count) =&amp;gt; {
			if (err) {
				this.logger.error(&quot;구독 실패:&quot;, err);
			} else {
				this.logger.log(`구독 성공! 현재 구독된 채널 수: ${count}`);
			}
		});

		this.redisClient.on(&quot;message&quot;, (channel, message) =&amp;gt; {
			await this.addToQueue(message);
		});

		this.eventQueue.trimEvents(1000); // stream 자료형에 쌓이는 bullmq 이벤트 개수를 1000개까지 보존
	}
	
	private async addToQueue(key: string) {
		await this.eventQueue.add(
			'event',
			{ key },
			{
				jobId: key, // 키를 jobId로 사용하여 중복 방지
				attempts: 3, // 실패 시 3번까지 재시도
				backoff: {
					type: 'exponential',
					delay: 1000, // 초기 지연 시간 1초
				},
				removeOnComplete: true, // 성공적으로 완료된 작업 제거
				removeOnFail: false, // 실패한 작업은 유지하여 조사 가능
			}
		);
	}
}&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;size18&quot;&gt;먼저 레디스를 구독하여 큐에 메시지를 적재할 객체를 선언합니다. 레디스 채널을 구독하여 메시지 이벤트가 발생하면 큐에 작업을 적재합니다. 이 과정에서 jobId가 중복으로 사용된 경우 중복 요청은 에러 없이 단순히 무시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;큐에 쌓은 작업을 이후 처리하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1739885514262&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Processor(&quot;event-queue&quot;)
export class EventProcessor extends WorkerHost {
	private readonly logger = new Logger(EventProcessor.name);

	async process(job: Job&amp;lt;any, any, string&amp;gt;): Promise&amp;lt;any&amp;gt; {
		switch (job.name) {
			case &quot;event&quot;: // job 이름
				try {
					await this.processEvent(job.data.key);
				} catch (error) {
					this.logger.error(`Error processing key ${job.data.key}:`, error);
					throw error; // 재시도를 위해 에러를 던지기
				}
				return;
		}
	}

	/** 후속 작업 수행 */
	private async processEvent(key: string) { /* 수행할 작업 */ }
}&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;size18&quot;&gt;@Processor 데코레이터를 붙인 후, 특정 큐 이름을 넣으면 이 객체는 큐에 쌓인 작업을 처리하는 객체가 됩니다. WorkerHost 추상 클래스를 상속하여 process 메서드를 구현하여 원하는 작업 이름에 따라 후처리를 진행하면 됩니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2️⃣ 여러 개의 쿼리를 하나의 쿼리로 묶어서 요청&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 해결방안은 쿼리를 묶어 Bulk 요청을 수행하도록 하는 것이고 구현 방법이 다양하므로, 별도로 구현한 내용을 서술하지 않겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&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;처리한 이벤트 개수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1분 40초간 총 14000건의 이벤트를 처리하였습니다.&lt;/li&gt;
&lt;li&gt;&amp;rarr; 기존 방식은 72개의 파드에 레디스가 만료된 메시지 이벤트를 모두 브로드캐스팅하므로 11300 * 72 = 813,600 건의 DB Write 요청이 발생했을 것으로 추정됩니다(동시성 이슈로 2700 건의 중복 이벤트가 적재되긴 했습니다 -&amp;gt; 14000 - 11300 = 2700).&lt;/li&gt;
&lt;li&gt;즉, DB 쿼리 수를 약 98.6%로 줄일 수 있었고, 자정에 발생하던 DB 부하 문제도 해결할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB 메트릭
&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;개선 이전&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-filename=&quot;blob&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;1692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4mhds/btsMnKitQnT/XlXQKwXF3rvb6NwYNKbIP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4mhds/btsMnKitQnT/XlXQKwXF3rvb6NwYNKbIP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4mhds/btsMnKitQnT/XlXQKwXF3rvb6NwYNKbIP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4mhds%2FbtsMnKitQnT%2FXlXQKwXF3rvb6NwYNKbIP0%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;1974&quot; height=&quot;1692&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;1692&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;개선 이후&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;2048&quot; data-origin-height=&quot;1025&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5utKC/btsMnj6NJHg/pTE5S828UgKl4G7Nxa6znK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5utKC/btsMnj6NJHg/pTE5S828UgKl4G7Nxa6znK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5utKC/btsMnj6NJHg/pTE5S828UgKl4G7Nxa6znK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5utKC%2FbtsMnj6NJHg%2FpTE5S828UgKl4G7Nxa6znK%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;2048&quot; height=&quot;1025&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1025&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;BullMQ는 레디스를 기반으로 하는 메시지 큐입니다. 즉, 레디스에 부하가 발생할 수 있는 것입니다. DB의 부하는 줄일 수 있었으나, 레디스의 부하는 줄일 수 없으므로 어디에든 큰 부하가 발생한다는 것이 한계점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만, 당장 급선무였던 DB 부하를 개선할 수 있었으며, 레디스의 성능에도 무리가 없는 작업이었으므로 향후 레디스에 문제가 발생할 때까지 유지해도 괜찮다는 판단을 내렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;앞으로 중복 작업을 제거하거나, 카프카보다 간단하게 메시지 큐를 사용해야 한다면 BullMQ를 자주 사용하게 될 것 같습니다.&lt;/p&gt;</description>
      <category>백엔드</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/189</guid>
      <comments>https://dev-scratch.tistory.com/189#entry189comment</comments>
      <pubDate>Tue, 18 Feb 2025 22:49:24 +0900</pubDate>
    </item>
    <item>
      <title>[ElasticSearch] 엘라스틱 서치 도입과 함께 맞닥뜨린 문제점</title>
      <link>https://dev-scratch.tistory.com/188</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;회사에서 기능 요구 사항으로 아이템 검색 기능을 추가해달라는 요구를 받았었습니다. Mongodb의 Atlas Search와 ElasticSearch 중에서 어떤 도구를 선택해야 좋을지 고민이 됐었는데 결론적으로 Elastic Search를 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 도구를 선택한 이유는 쿼리를 정말 다양하게 사용할 수 있다는 점과, atlas search에 비해서 참고할 자료가 많았던 점, DB와 함께 사용하면서 복잡한 쿼리 혹은 데이터 색인 과정에서 리소스를 어느 정도 필요로 한다는 점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만, 마냥 장점만 있던 것은 아니었습니다. 사이드 이펙트가 꽤 단순하지 않아서 골머리를 앓았던 경험이 떠오르네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 이번에는 엘라스틱 서치를 도입하면서 어떤 문제점을 맞닥뜨렸는지 공유드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DB - ES 간 동기화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;가장 큰 문제는 DB와 ES 간 데이터가 완전히 동기화가 되지 않는다는 것입니다. DB에 데이터가 추가되면 ES에도 데이터를 넣어줘야 합니다. 가장 쉬운 방법은 Connector를 추가하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Monstache&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://rwynn.github.io/monstache-site/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://rwynn.github.io/monstache-site/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1727944302611&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;Monstache&quot; data-og-description=&quot;Copyright &amp;copy; 2016 Ryan Wynn&quot; data-og-host=&quot;rwynn.github.io&quot; data-og-source-url=&quot;https://rwynn.github.io/monstache-site/&quot; data-og-url=&quot;https://rwynn.github.io/monstache-site/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://rwynn.github.io/monstache-site/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://rwynn.github.io/monstache-site/&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;Monstache&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Copyright &amp;copy; 2016 Ryan Wynn&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;rwynn.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/rwynn/monstache&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/rwynn/monstache&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1727944367120&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 - rwynn/monstache: a go daemon that syncs MongoDB to Elasticsearch in realtime. you know, for search.&quot; data-og-description=&quot;a go daemon that syncs MongoDB to Elasticsearch in realtime. you know, for search. - rwynn/monstache&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/rwynn/monstache&quot; data-og-url=&quot;https://github.com/rwynn/monstache&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OmJhU/hyXeab0vLM/H8WPmaX4cq9PWpKt9Y691k/img.png?width=1200&amp;amp;height=600&amp;amp;face=941_135_1055_260,https://scrap.kakaocdn.net/dn/MicbN/hyXau4ifNx/X4Bd41GRLPRfTnCpuijnKK/img.png?width=1200&amp;amp;height=600&amp;amp;face=941_135_1055_260&quot;&gt;&lt;a href=&quot;https://github.com/rwynn/monstache&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/rwynn/monstache&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OmJhU/hyXeab0vLM/H8WPmaX4cq9PWpKt9Y691k/img.png?width=1200&amp;amp;height=600&amp;amp;face=941_135_1055_260,https://scrap.kakaocdn.net/dn/MicbN/hyXau4ifNx/X4Bd41GRLPRfTnCpuijnKK/img.png?width=1200&amp;amp;height=600&amp;amp;face=941_135_1055_260');&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 - rwynn/monstache: a go daemon that syncs MongoDB to Elasticsearch in realtime. you know, for search.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;a go daemon that syncs MongoDB to Elasticsearch in realtime. you know, for search. - rwynn/monstache&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Monstache는 mongodb 데이터를 elasticsearch와 연동시키기 위한 도구 중 하나입니다. mongodb에 저장되는 데이터를 바로 elasticsearch에 색인해주는 서비스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는 외부 오픈소스이고 데이터가 중요한 상용환경에서 사용하는 것이 우려되어 선택하지는 않았습니다. 또한, 계속 연동을 시켜주기 위해 프로세스를 중단시키면 안되므로 추가 리소스가 필요하며, 이를 위한 엔지니어링 리소스를 사용하고 싶지 않았기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Elastic Connectors&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/enterprise-search/current/connectors.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.elastic.co/guide/en/enterprise-search/current/connectors.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1727944511710&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;Elastic connectors | Enterprise Search documentation [8.15] | Elastic&quot; data-og-description=&quot;A connector is a type of Elastic integration that syncs data from an original data source to an Elasticsearch index. Connectors enable you to create searchable, read-only replicas of your data sources. Connectors extract the original files, records, or obj&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/guide/en/enterprise-search/current/connectors.html&quot; data-og-url=&quot;https://www.elastic.co/guide/en/enterprise-search/current/connectors.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bTksks/hyXefxCMCq/bBPsMUAdKB2JwV0vxkWRKK/img.png?width=2340&amp;amp;height=1268&amp;amp;face=0_0_2340_1268&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/enterprise-search/current/connectors.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/guide/en/enterprise-search/current/connectors.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bTksks/hyXefxCMCq/bBPsMUAdKB2JwV0vxkWRKK/img.png?width=2340&amp;amp;height=1268&amp;amp;face=0_0_2340_1268');&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;Elastic connectors | Enterprise Search documentation [8.15] | Elastic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A connector is a type of Elastic integration that syncs data from an original data source to an Elasticsearch index. Connectors enable you to create searchable, read-only replicas of your data sources. Connectors extract the original files, records, or obj&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&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;size18&quot;&gt;elastic에서 제공해주는 연동 도구로 자체적으로 제공하는 커넥터들이 존재하므로 신용할 수 있다는 점이 좋았습니다. 하지만 enterprise search를 사용해야 한다는 점이 약간의 부담으로 다가왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, 매핑을 설정하는 과정이 복잡하고 문서가 자세하지 않아 원하는 매핑을 설정할 수 없다는 점이 가장 아쉬웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 서버 애플리케이션 코드 + 배치 작업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;서버에서 아이템을 DB에 저장할 때, 이벤트를 사용하여 특정 아이템이 추가되었을 때 ES로 색인 데이터를 저장합니다. 하지만 모종의 이유로 DB에는 데이터가 저장되었더라도, ES에 데이터가 추가되지 않을 수도 있으므로 주기적으로 배치 작업을 수행해 데이터를 추가해주는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이것도 물론 엔지니어링 리소스를 사용하지만, 가장 단순하게 외부 인적 자원을 사용하지 않고 처리할 수 있다는 점에서 긍정적인 부분이었습니다. 또한, 현재 기능이 검색 데이터가 무조건 DB 데이터와 매칭될 필요도 없었기 때문에 이 방법을 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;색인 데이터 중복&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이게 가장 큰 문제점이었습니다. 엘라스틱 서치에 데이터를 추가하더라도 색인 과정을 거쳐 저장이 되기 때문에 바로 조회하더라도 데이터를 확인하지 못할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 이유 때문에 upsert 요청을 서버에서 구현하여 사용하면 데이터가 중복으로 들어갈 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어, A 아이템을 DB에 넣고 ES에 데이터를 추가합니다. 그리고 직후 A 아이템을 수정하여 ES에 upsert를 수행합니다. 여기서 upsert는 유니크한 identifier로 data를 찾고 해당 데이터의 _id를 통해 update하는 연산을 사용했습니다. 하지만, 여기서 DB에는 데이터가 존재하더라도 ES에는 데이터가 아직 반영되지 않았을 수 있습니다. 혹은 모종의 이유로 es에서 찾지 못한 경우도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 때문에 upsert를 Elastic search에 사용한다면, 충분히 데이터 중복 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같은 문제점이 존재하였지만, 개인적으로 검색의 자유도가 매우 높다는 점과 높은 성능을 보여주었기에 이를 도입한 후 아쉬움보다는 만족감이 더 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아직 배워나갈 점이 많다고 느끼기 때문에 앞으로 배워나가는 내용들을 기재하도록 하겠습니다  &lt;/p&gt;</description>
      <category>백엔드/검색</category>
      <category>Elastic Search</category>
      <category>Es</category>
      <category>검색</category>
      <category>엘라스틱서치</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/188</guid>
      <comments>https://dev-scratch.tistory.com/188#entry188comment</comments>
      <pubDate>Thu, 3 Oct 2024 17:46:39 +0900</pubDate>
    </item>
    <item>
      <title>[ElasticSearch] 인덱스 매핑 수정하기</title>
      <link>https://dev-scratch.tistory.com/187</link>
      <description>&lt;p data-ke-size=&quot;size18&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;size18&quot;&gt;기본적으로 동적 매핑을 true로 설정하면 내부적으로 알아서 입력하는 데이터에 맞춰 매핑을 설정하지만, 이렇게 설정되는 매핑이 내가 원하는 설정대로 되지 않는 경우도 종종 발생합니다. 예를 들어 단순 정수값을 데이터로 넣었을 때, 작은 값이더라도 long 타입으로 매핑하기 때문에 비효율적입니다. date 타입 또한 마찬가지로 ISO Date에 맞추지 않은 date 값이 존재하므로 text 타입으로 매핑될 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 매핑을 수정해야 하는 경우도 이따금씩 발생합니다. 예를 들어, 커스텀 분석기를 수정하거나, 기존 필드에 새로운 서브 필드를 추가해야 하는 경우가 그러합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만, 엘라스틱 서치의 매핑은 한 번 설정했으면 그 다음부터 수정이 불가합니다. 이미 설정한 필드는 그 매핑을 유지하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 인덱스를 매핑하기 위해서는 &lt;b&gt;새로운 인덱스를 생성&lt;/b&gt;하고 해당 인덱스로 &lt;b&gt;데이터를 옮긴 후&lt;/b&gt;에 &lt;b&gt;alias(별칭)을 바꾸는 방법&lt;/b&gt;을 통해 인덱스 매핑을 수정해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;향후 예제들은 Node.js 환경에서 @elastic/elasticsearch 패키지를 사용했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 인덱스 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;인덱스 매핑을 수정한 새로운 인덱스를 생성합니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1727510887590&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const client = new Client({
  cloud: {
    id: &quot;cloudId&quot;,
  },
  auth: {
    apiKey: &quot;apiKey&quot;,
  },
});

async function run() {
    await client.indices.create({
    index: &quot;store_240927&quot;,
    settings: {
      max_ngram_diff: 15,
      analysis: {
        analyzer: {
          kr_nori: {
            type: &quot;nori&quot;,
          },
          jp_kuromoji: {
            type: &quot;kuromoji&quot;,
          },
          en_english: {
            type: &quot;english&quot;,
          },
          name_ngram_tokenizer: {
            type: &quot;custom&quot;,
            tokenizer: &quot;name_ngram_tokenizer&quot;,
          },
          tag_ngram_tokenizer: {
            type: &quot;custom&quot;,
            tokenizer: &quot;tag_ngram_tokenizer&quot;,
          },
        },
        tokenizer: {
          name_ngram_tokenizer: {
            type: &quot;ngram&quot;,
            min_gram: 1,
            max_gram: 12,
          },
          tag_ngram_tokenizer: {
            type: &quot;ngram&quot;,
            min_gram: 1,
            max_gram: 15,
          },
        },
      },
    },
    mappings: {
      properties: {
        createdAt: {
          type: &quot;date&quot;,
          format: &quot;strict_date_time || epoch_millis&quot;,
        },
        description: {
          type: &quot;text&quot;,
          fields: {
            kr: {
              type: &quot;text&quot;,
              analyzer: &quot;kr_nori&quot;,
            },
            jp: {
              type: &quot;text&quot;,
              analyzer: &quot;jp_kuromoji&quot;,
            },
            en: {
              type: &quot;text&quot;,
              analyzer: &quot;en_english&quot;,
            },
          },
        },
        name: {
          type: &quot;text&quot;,
          analyzer: &quot;name_ngram_tokenizer&quot;,
          fields: {
            completion: {
              type: &quot;completion&quot;,
            },
          },
        },
        updatedAt: {
          type: &quot;date&quot;,
          format: &quot;strict_date_time || epoch_millis&quot;,
        },
      },
    },
  });
}

run().catch(console.log);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어 위처럼 name 필드를 text 타입으로 설정하였는데, keyword 서브 필드를 추가해야 하는 상황이라고 한다면, 새로운 인덱스를 먼저 생성합니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1727510895549&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;await client.indices.create({
    index: &quot;store_240928&quot;,
    settings: {
      max_ngram_diff: 15,
      analysis: {
        analyzer: {
          kr_nori: {
            type: &quot;nori&quot;,
          },
          jp_kuromoji: {
            type: &quot;kuromoji&quot;,
          },
          en_english: {
            type: &quot;english&quot;,
          },
          name_ngram_tokenizer: {
            type: &quot;custom&quot;,
            tokenizer: &quot;name_ngram_tokenizer&quot;,
          },
          tag_ngram_tokenizer: {
            type: &quot;custom&quot;,
            tokenizer: &quot;tag_ngram_tokenizer&quot;,
          },
        },
        tokenizer: {
          name_ngram_tokenizer: {
            type: &quot;ngram&quot;,
            min_gram: 1,
            max_gram: 12,
          },
          tag_ngram_tokenizer: {
            type: &quot;ngram&quot;,
            min_gram: 1,
            max_gram: 15,
          },
        },
      },
    },
    mappings: {
      properties: {
        createdAt: {
          type: &quot;date&quot;,
          format: &quot;strict_date_time || epoch_millis&quot;,
        },
        description: {
          type: &quot;text&quot;,
          fields: {
            kr: {
              type: &quot;text&quot;,
              analyzer: &quot;kr_nori&quot;,
            },
            jp: {
              type: &quot;text&quot;,
              analyzer: &quot;jp_kuromoji&quot;,
            },
            en: {
              type: &quot;text&quot;,
              analyzer: &quot;en_english&quot;,
            },
          },
        },
        name: {
          type: &quot;text&quot;,
          analyzer: &quot;name_ngram_tokenizer&quot;,
          fields: {
            completion: {
              type: &quot;completion&quot;,
            },
            keyword: {
              type: &quot;keyword&quot;,
            },
          },
        },
        updatedAt: {
          type: &quot;date&quot;,
          format: &quot;strict_date_time || epoch_millis&quot;,
        },
      },
    },
  });&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 리인덱싱&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;특정 인덱스에 존재하는 데이터를 다른 인덱스로 복사하고 색인하는 과정을 리인덱싱이라고 부릅니다. 복잡한 과정 없이 데이터를 일괄 수행할 수 있어 매우 유용한 api입니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1727511028616&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  await client.reindex({
    source: {
      index: &quot;store_240927&quot;,
    },
    dest: {
      index: &quot;store_240928&quot;,
    },
  });&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;source에는 색인된 데이터가 저장된 인덱스를, dest에는 해당 데이터를 옮길 인덱스를 넣으면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그런데 리인덱싱 과정은 모든 데이터를 옮겨야 하므로 작은 작업이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서 timeout이 발생할 수 있습니다. 이러한 경우에는 따로 timeout 설정을 조정하거나, 비동기로 실행해주어야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) timeout 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;timeout은 elastic search client와 연결할 때 설정하는 requestTimeout과, reindex API의 timeout이 있습니다. 저의 경우에는 reindex 시간이 1분을 약간 넘어서 기본값을 넘어가므로 별도로 timeout을 설정해주어야 했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727594150593&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const client = new Client({
  cloud: {
    id: &quot;clientId&quot;,
  },
  auth: {
    apiKey: &quot;apiKey&quot;,
  },
  requestTimeout: &quot;5m&quot;,
});&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;size18&quot;&gt;마이그레이션을 위한 작업이므로 timeout을 길게 가져갔지만, 그 외에 실무에서 사용하는 것은 좋지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 비동기 수행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;리인덱싱을 기다리지 않고 비동기적으로 수행하는 방법도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727594373220&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await client.reindex({
  source: {
    index: &quot;store_240927&quot;,
  },
  dest: {
    index: &quot;store_240928&quot;,
  },
  wait_for_completion: false
});&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;size18&quot;&gt;이렇게 wait_for_completion을 false로 하면 리인덱스를 비동기적으로 수행할 수 있습니다. 이때의 반환값은 task의 id가 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727594495575&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;tasks&quot; : {
  &quot;oTUltX4IQMOUUVeiohTt8A:124&quot; : {
    &quot;node&quot; : &quot;oTUltX4IQMOUUVeiohTt8A&quot;,
    &quot;id&quot; : 124,
    &quot;type&quot; : &quot;direct&quot;,
    &quot;action&quot; : &quot;cluster:monitor/tasks/lists[n]&quot;,
    &quot;start_time_in_millis&quot; : 1458585884904,
    &quot;running_time_in_nanos&quot; : 47402,
    &quot;cancellable&quot; : false,
    &quot;parent_task_id&quot; : &quot;oTUltX4IQMOUUVeiohTt8A:123&quot;
  },
}&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;size18&quot;&gt;이때 task의 현재 상황을 파악하기 위해 get task API를 통해 조회할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727594583217&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET _tasks/oTUltX4IQMOUUVeiohTt8A:12345&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1727594629869&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await client.tasks.get({
  id: &quot;store_240928:1&quot;,
})&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;size18&quot;&gt;그리고 get task API도 해당 task가 끝날 때까지 대기할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727594671206&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X GET &quot;localhost:9200/_tasks/oTUltX4IQMOUUVeiohTt8A:12345?wait_for_completion=true&amp;amp;timeout=10s&amp;amp;pretty&quot;&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;size18&quot;&gt;혹은 리인덱싱 태스크를 모두 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727594709339&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X GET &quot;localhost:9200/_tasks?actions=*reindex&amp;amp;wait_for_completion=true&amp;amp;timeout=10s&amp;amp;pretty&quot;&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;size18&quot;&gt;개인적으로 굳이 비동기적으로 가져갈 필요가 없어서 timeout을 늘려서 리인덱스를 완료했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 인덱스 alias(별칭) 변경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;마지막으로 인덱스의 별칭을 변경해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;엘라스틱서치 API를 사용할 때는 인덱스를 명시하거나 혹은 해당 인덱스의 별칭을 사용하여 API를 사용합니다. 그래서 단순히 별칭을 스와핑하여 원래 사용하던 인덱스에서 다른 인덱스로 별칭을 전환하여 중단 없이 API 사용이 가능해집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727594919642&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await client.indices.updateAliases({
  actions: [
    { remove: { index: &quot;store_240927&quot;, alias: &quot;store&quot; } },
    { add: { index: &quot;store_240928&quot;, alias: &quot;store&quot; } }
  ],
});&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;size18&quot;&gt;이렇게 alias를 한 번에 업데이트하면 원래 store_240927 인덱스로 흘러가던 요청이 store_240928 인덱스로 흘러갈 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 이때 문제가 하나 있는데, &lt;u&gt;&lt;b&gt;alias로 사용하려는 텍스트와 같은 텍스트를 사용하는 index가 있으면 안된다는 것입니다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어, 위에서 store라고 alias를 설정하였는데 이미 &quot;store&quot; 인덱스가 있는 경우. alias를 추가하거나 삭제할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이를 해결하기 위해서는 해당 인덱스를 삭제해주어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727595109722&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await client.delete({
  index: &quot;store&quot;,
})

await client.indices.updateAliases({
  actions: [
    { add: { index: &quot;store_240928&quot;, alias: &quot;store&quot; } }],
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리인덱스 사용 시 고려하면 좋은 것들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;리인덱싱을 사용할 때 고려할 점은 적지 않은 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저 reindex는 검색 후 bulk로 추가되는 구조이므로, 해당 bulk의 크기를 어떻게 가져갈 것인지가 성능에 영향을 미칠 수 있습니다. 이때는 리인덱스 API의 파라미터를 조정하여 bulk 사이즈를 증감시켜 최적의 파라미터를 찾는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 리인덱스 중간에는 해당 인덱스로 검색 요청이 들어올 일이 없으므로, 색인을 위해 refresh하는 간격을 없애는 것도 좋습니다. 그래서 refresh_interval은 -1로 설정하면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;다나와 반윤성님의 Elasticsearch 3TB의 인덱스를 reindex 하는 방법 -&amp;nbsp; &lt;a href=&quot;https://danawalab.github.io/elastic/2023/05/16/elasticReindex.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://danawalab.github.io/elastic/2023/05/16/elasticReindex.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1727595585775&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 3TB의 인덱스를 reindex 하는 방법&quot; data-og-description=&quot;대용량, 대규모의 인덱스를 재색인하는 방법을 알아봅니다&quot; data-og-host=&quot;danawalab.github.io&quot; data-og-source-url=&quot;https://danawalab.github.io/elastic/2023/05/16/elasticReindex.html&quot; data-og-url=&quot;https://danawalab.github.io/elastic/2023/05/16/elasticReindex.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/clPGI1/hyXaFjj09N/Kyv0O9KnMV10MMXB7t2yGk/img.png?width=1047&amp;amp;height=557&amp;amp;face=0_0_1047_557,https://scrap.kakaocdn.net/dn/lzlVL/hyXazKaBYD/avs3WGmv2C4N5SIrDuZyP1/img.png?width=837&amp;amp;height=615&amp;amp;face=0_0_837_615,https://scrap.kakaocdn.net/dn/CRigb/hyW6J1Y3lf/CeSOTZEqOvh3eGGYww3wB0/img.png?width=614&amp;amp;height=503&amp;amp;face=0_0_614_503&quot;&gt;&lt;a href=&quot;https://danawalab.github.io/elastic/2023/05/16/elasticReindex.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://danawalab.github.io/elastic/2023/05/16/elasticReindex.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/clPGI1/hyXaFjj09N/Kyv0O9KnMV10MMXB7t2yGk/img.png?width=1047&amp;amp;height=557&amp;amp;face=0_0_1047_557,https://scrap.kakaocdn.net/dn/lzlVL/hyXazKaBYD/avs3WGmv2C4N5SIrDuZyP1/img.png?width=837&amp;amp;height=615&amp;amp;face=0_0_837_615,https://scrap.kakaocdn.net/dn/CRigb/hyW6J1Y3lf/CeSOTZEqOvh3eGGYww3wB0/img.png?width=614&amp;amp;height=503&amp;amp;face=0_0_614_503');&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 3TB의 인덱스를 reindex 하는 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;대용량, 대규모의 인덱스를 재색인하는 방법을 알아봅니다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;danawalab.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;es 공식문서 task management API - &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html#tasks-api-response-codes&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html#tasks-api-response-codes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1727595608878&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;Task management API | Elasticsearch Guide [8.15] | Elastic&quot; data-og-description=&quot;_tasks requests with detailed may also return a status. This is a report of the internal status of the task. As such its format varies from task to task. While we try to keep the status for a particular task consistent from version to version this isn&amp;rsquo;t &quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html#tasks-api-response-codes&quot; data-og-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html#tasks-api-response-codes&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html#tasks-api-response-codes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html#tasks-api-response-codes&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;Task management API | Elasticsearch Guide [8.15] | Elastic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;_tasks requests with detailed may also return a status. This is a report of the internal status of the task. As such its format varies from task to task. While we try to keep the status for a particular task consistent from version to version this isn&amp;rsquo;t&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&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;</description>
      <category>백엔드/검색</category>
      <category>검색</category>
      <category>리인덱스</category>
      <category>무중단</category>
      <category>엘라스틱서치</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/187</guid>
      <comments>https://dev-scratch.tistory.com/187#entry187comment</comments>
      <pubDate>Sun, 29 Sep 2024 16:41:02 +0900</pubDate>
    </item>
    <item>
      <title>시켜줘 뤼튼 명예 소방관, 부적절한 이미지 필터링 도입기</title>
      <link>https://dev-scratch.tistory.com/186</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wrtn.io/%ec%8b%9c%ec%bc%9c%ec%a4%98-%eb%a4%bc%ed%8a%bc-%eb%aa%85%ec%98%88-%ec%86%8c%eb%b0%a9%ea%b4%80-%eb%b6%80%ec%a0%81%ec%a0%88%ed%95%9c-%ec%9d%b4%eb%af%b8%ec%a7%80-%ed%95%84%ed%84%b0%eb%a7%81-%eb%8f%84-2/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wrtn.io/%ec%8b%9c%ec%bc%9c%ec%a4%98-%eb%a4%bc%ed%8a%bc-%eb%aa%85%ec%98%88-%ec%86%8c%eb%b0%a9%ea%b4%80-%eb%b6%80%ec%a0%81%ec%a0%88%ed%95%9c-%ec%9d%b4%eb%af%b8%ec%a7%80-%ed%95%84%ed%84%b0%eb%a7%81-%eb%8f%84-2/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714548757391&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;뤼튼테크놀로지스&quot; data-og-description=&quot;Your First AI, Agent : 뤼튼은 누구나 제약 없이 일상에서 쉽고 간편하게 AGI를 활용할 수 있는 AI 플랫폼&quot; data-og-host=&quot;wrtn.io&quot; data-og-source-url=&quot;https://wrtn.io/%ec%8b%9c%ec%bc%9c%ec%a4%98-%eb%a4%bc%ed%8a%bc-%eb%aa%85%ec%98%88-%ec%86%8c%eb%b0%a9%ea%b4%80-%eb%b6%80%ec%a0%81%ec%a0%88%ed%95%9c-%ec%9d%b4%eb%af%b8%ec%a7%80-%ed%95%84%ed%84%b0%eb%a7%81-%eb%8f%84-2/&quot; data-og-url=&quot;https://wrtn.io&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Vup0J/hyVZhX1bCx/qI92JzwUSWSuGmctDbcdj1/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/gA0nd/hyVZeGYJ4x/KPweXa5PYcVSYC7lfJcK8k/img.png?width=2000&amp;amp;height=1126&amp;amp;face=0_0_2000_1126,https://scrap.kakaocdn.net/dn/bQmXq2/hyVVDIpCaZ/858CjFGZo7SaMkNAe45US1/img.jpg?width=1600&amp;amp;height=759&amp;amp;face=0_0_1600_759&quot;&gt;&lt;a href=&quot;https://wrtn.io/%ec%8b%9c%ec%bc%9c%ec%a4%98-%eb%a4%bc%ed%8a%bc-%eb%aa%85%ec%98%88-%ec%86%8c%eb%b0%a9%ea%b4%80-%eb%b6%80%ec%a0%81%ec%a0%88%ed%95%9c-%ec%9d%b4%eb%af%b8%ec%a7%80-%ed%95%84%ed%84%b0%eb%a7%81-%eb%8f%84-2/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wrtn.io/%ec%8b%9c%ec%bc%9c%ec%a4%98-%eb%a4%bc%ed%8a%bc-%eb%aa%85%ec%98%88-%ec%86%8c%eb%b0%a9%ea%b4%80-%eb%b6%80%ec%a0%81%ec%a0%88%ed%95%9c-%ec%9d%b4%eb%af%b8%ec%a7%80-%ed%95%84%ed%84%b0%eb%a7%81-%eb%8f%84-2/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Vup0J/hyVZhX1bCx/qI92JzwUSWSuGmctDbcdj1/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/gA0nd/hyVZeGYJ4x/KPweXa5PYcVSYC7lfJcK8k/img.png?width=2000&amp;amp;height=1126&amp;amp;face=0_0_2000_1126,https://scrap.kakaocdn.net/dn/bQmXq2/hyVVDIpCaZ/858CjFGZo7SaMkNAe45US1/img.jpg?width=1600&amp;amp;height=759&amp;amp;face=0_0_1600_759');&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;뤼튼테크놀로지스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Your First AI, Agent : 뤼튼은 누구나 제약 없이 일상에서 쉽고 간편하게 AGI를 활용할 수 있는 AI 플랫폼&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wrtn.io&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;회사 테크블로그에 &lt;b&gt;부적절한 이미지 필터링&lt;/b&gt; 관련한 게시물을 작성했습니다.&lt;/p&gt;</description>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/186</guid>
      <comments>https://dev-scratch.tistory.com/186#entry186comment</comments>
      <pubDate>Wed, 1 May 2024 16:32:57 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 훑어보기</title>
      <link>https://dev-scratch.tistory.com/185</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;인프라 구조마저 코드로 조작하고 있는 현재, 가장 많이 쓰이는 도구는 테라폼과 앤서블이 있습니다. DevOps Loadmap에서도 두 가지 방법을 가장 권장하고 있네요!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bitPU0/btsCxGdFt6J/jQeutEqzpafQqSThwGXli1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bitPU0/btsCxGdFt6J/jQeutEqzpafQqSThwGXli1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bitPU0/btsCxGdFt6J/jQeutEqzpafQqSThwGXli1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbitPU0%2FbtsCxGdFt6J%2FjQeutEqzpafQqSThwGXli1%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;423&quot; height=&quot;363&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그도 그럴 것이, 클라우드 인프라의 경우 GUI로 클릭하면서 조작하거나 혹은 각각이 제공하는 sdk를 사용해 인프라를 설정하지만, 이렇게 하면 서로 어떻게 인프라를 설정했는지 공유가 어렵고 숙련도에 따라 설정이 확연히 달라지는 경우가 종종 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런 문제들을 해결하고자 HashiCorp에서 Terraform이라는 IaC 도구를 만들어 세상에 공개했습니다. Terraform과 성격이 비슷한 도구가 Ansible인데, Terraform과 Ansible의 차이는 간단하게 생각해서 아예 뜯어 고칠 것인지, 일부 수정할 것인지의 차이가 있어서 비슷하면서도 목적이 다릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oQpVC/btsCBKsbHaf/22PWf1qZpo6GN0WAi4a8tk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oQpVC/btsCBKsbHaf/22PWf1qZpo6GN0WAi4a8tk/img.jpg&quot; data-alt=&quot;출처: 레드햇, itdaily(http://www.itdaily.kr/news/articleView.html?idxno=204606)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oQpVC/btsCBKsbHaf/22PWf1qZpo6GN0WAi4a8tk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoQpVC%2FbtsCBKsbHaf%2F22PWf1qZpo6GN0WAi4a8tk%2Fimg.jpg&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;294&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 레드햇, itdaily(http://www.itdaily.kr/news/articleView.html?idxno=204606)&lt;/figcaption&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;size18&quot;&gt;그렇지만 요즘은 또, 앤서블과 테라폼을 혼용해서 사용하는 방법론이 괜찮아 보이는 것 같습니다. 테라폼으로 전반적인 프로비저닝을 진행하고, 앤서블을 통해 자동화 프로세스를 진행하는 식으로 응용할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 한 번 테라폼을 슥 훑어보면서 사용해봤을 때, &lt;b&gt;선언적&lt;/b&gt;으로 인프라를 구성할 수 있고 &lt;b&gt;편리하게 수정&lt;/b&gt;이 가능하며 &lt;b&gt;공유에 용이&lt;/b&gt;하다는 점이 가장 큰 장점으로 다가왔습니다. 이번에 그 경험과 느낀점을 자세히 공유해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테라폼 언어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;yaml을 사용하는 앤서블과 달리 &lt;b&gt;HCL&lt;/b&gt;이라는 언어를 사용합니다. 사실 이런 점때문에 단점으로 느껴질 수 있을 것 같았는데, 문법의 총량이 많지 않고 유연하게 재사용하며 인프라를 구성할 수 있다는 점이 장점으로도 다가왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;언어 자체도 복잡하지 않아서 자바스크립트와 코틀린을 섞어서 사용하는 그런 느낌도 들었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703494766647&quot; class=&quot;tf routeros&quot; data-ke-language=&quot;tf&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;provider &quot;aws&quot; {
  region = var.region
  default_tags {
    tags = {
      Project = &quot;Coffee-Mug-Cake&quot;
      Owner   = &quot;jerry &amp;amp; tom&quot;
    }
  }
}

resource &quot;aws_vpc&quot; &quot;hashicat&quot; {
  cidr_block           = var.address_space
  enable_dns_hostnames = true

  tags = {
    name        = &quot;${var.prefix}-vpc-${var.region}&quot;
    environment = &quot;Production&quot;
  }
}&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;size18&quot;&gt;대강 봐도 '아 AWS를 사용하는구나~ 리전은 객체같은 변수에서 꺼내서 쓰고, vpc를 생성하고 태그도 같이 넣어줬구나' 하는 생각이 들 수 있을 것 같아요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수 &amp;amp; 조건 &amp;amp; 반복&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;함수를 직접 생성할 수는 없지만, 내장 함수들이 있어서 간단하게 카운팅을 하거나 자료형을 변환하는 등의 기능을 편하게 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703495167595&quot; class=&quot;terraform nix&quot; data-ke-language=&quot;terraform&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;null_resource&quot; &quot;configure-cat-app&quot; {
  depends_on = [aws_eip_association.hashicat]

  triggers = {
    build_number = timestamp() # timestamp 함수
  }

  provisioner &quot;file&quot; {
    source      = &quot;files/&quot;
    destination = &quot;/home/ubuntu/&quot;
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;조건문의 경우도 타 언어에도 존재하는 &lt;b&gt;삼항 연산자&lt;/b&gt;를 사용해 동적으로 인프라를 조작할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아래 블럭에서 count 속성에 값을 넣게 되는데, 워크스페이스에 따라서 aws 인스턴스의 개수를 다르게 하는 것입니다. 만약 기본 default 워크스페이스라면 인스턴스가 5개가 생기고 아니라면 1개만 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, count 속성에 값을 부여해서 반복을 수행할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703495264358&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;web&quot; {
    count = &quot;${terraform.workspace == &quot;default&quot; ? 5 : 1}&quot;
    ami = data.aws_ami.amzn2.id

    subnet_id = &quot;subnet-0cf877129235db9c0&quot;
    instance_type = &quot;t2.micro&quot;

    tags = {
        Name = &quot;HelloWorld&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사실 반복문이 블럭으로 존재하는 것은 아니고 속성에 부여하거나, 변수에 반복할 수 있는 자료형을 사용하여 for_each 속성에 부여하는 등으로 사용하기에 타 프로그래밍 언어처럼 명확하게 반복을 한다는 것이 느껴지지는 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;당장 위의 count 만 보더라도 그렇네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;혹은 리스트를 초기화할 때도 파이썬의 리스트 컴프리헨션과 같이 응용할 수도 있습니다. 아래 코드는 테라폼으로 인프라를 조작한 후에 출력할 값들을 의미하며, ec2 seoul이라는 테라폼의 모듈 단위의 행위에서 private_ip를 리스트로 담은 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703495517844&quot; class=&quot;terraform ceylon&quot; data-ke-language=&quot;terraform&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output &quot;module_output&quot; {
    value = [
        for k in module.module.ec2-seoul: k.private_ip
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테라폼 CLI&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;terraform validate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CLI가 부가적인 기능이 많아서 편리하게 느껴졌습니다. 예를 들어 아래처럼 코드 상에 실수가 있거나 하면, terraform validate를 통해서 디렉터리 내 모든 테라폼 파일에 유효성 검사가 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;1886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQLxV5/btsCy6ixyvv/akNT0dv4DkEkS8nmpSjkaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQLxV5/btsCy6ixyvv/akNT0dv4DkEkS8nmpSjkaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQLxV5/btsCy6ixyvv/akNT0dv4DkEkS8nmpSjkaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQLxV5%2FbtsCy6ixyvv%2FakNT0dv4DkEkS8nmpSjkaK%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;354&quot; height=&quot;555&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;1886&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;h3 data-ke-size=&quot;size23&quot;&gt;terraform plan&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;terraform plan은 인프라가 생성되는 과정을 확인하거나, 파일을 생성해 바로 적용할 수 있도록 준비해주는 등의 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사실 다른 것보다 terraform plan과 terraform apply 명령어가 가장 중요한데, terraform plan이 개발하면서 도움이 많이 될 것 같아요. 우리가 인프라를 생성하거나 수정할 때, 어떤 과정을 거치게 될지와 내가 설정한 변수가 어떻게 설정될지 혹은 output으로 받을 값들이 어떻게 초기화될지를 파악하기 좋습니다. 물론 생성이 되어야지 알 수 있는 것들은 당장 알 수 없습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;858&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZEx76/btsCyJnsLdI/vYMSoI0HKJj82bqTMKG0G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZEx76/btsCyJnsLdI/vYMSoI0HKJj82bqTMKG0G1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZEx76/btsCyJnsLdI/vYMSoI0HKJj82bqTMKG0G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZEx76%2FbtsCyJnsLdI%2FvYMSoI0HKJj82bqTMKG0G1%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;473&quot; height=&quot;475&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;862&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;h2 data-ke-size=&quot;size26&quot;&gt;다양한 클라우드 사용 가능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;클라우드 벤더사로 가장 유명하고 많이 쓰이는 곳이 AWS인데, 이외에도 다른 목적으로 사용하거나 혹은 처음부터 타 플랫폼으로 사용하는 경우도 많습니다. 모든 클라우드를 추상화할 수는 없겠지만, 많은 클라우드 플랫폼에 대해서 IaC를 지원한다는 게 매우 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS나 GCP, Azure 등의 글로벌 플랫폼 외에도 국내 네이버 NCP도 지원하는데, 이렇게 지원할 수 있는 이유는 프로바이더 레지스트리를 사용하기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2050&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A7GC7/btsCALkvziL/Poo2WLB837elGzTmI6TFCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A7GC7/btsCALkvziL/Poo2WLB837elGzTmI6TFCK/img.png&quot; data-alt=&quot;출처: 테라폼 레지스트리(https://registry.terraform.io/browse/providers?page=5)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A7GC7/btsCALkvziL/Poo2WLB837elGzTmI6TFCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA7GC7%2FbtsCALkvziL%2FPoo2WLB837elGzTmI6TFCK%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;571&quot; height=&quot;255&quot; data-origin-width=&quot;2050&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 테라폼 레지스트리(https://registry.terraform.io/browse/providers?page=5)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모듈화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;테라폼으로 선언한 인프라 설정을 모듈처럼 &lt;b&gt;재사용&lt;/b&gt;할 수 있습니다. 라이브러리나 패키지를 설치해서 필요한 기능을 편하게 재사용하는 것처럼, AWS 등에서 같은 설정인데 리전만 다르게 해야 하는 사소한 경우라도 손쉽게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;내가 생성할 인프라 구조를 간단하게 설정하면 이것을 모듈로서 구비할 수 있고, 향후 다른 파일에서 해당 모듈을 끌어와 사용하고 region의 값으로 다른 값을 넣어서 융통성있게 변화를 줄 수 있는 것이죠.&lt;/p&gt;
&lt;pre id=&quot;code_1703496528978&quot; class=&quot;terraform nix&quot; data-ke-language=&quot;terraform&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;provider &quot;aws&quot; {
    region = &quot;us-west-1&quot;
}

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

module &quot;ec2_california&quot; {
    source = &quot;../modules/terraform-aws-ec2&quot;
}

module &quot;ec2_seoul&quot; {
    source = &quot;../modules/terraform-aws-ec2&quot;
    providers = {
        aws = aws.seoul
    }
    instance_type = &quot;m5.large&quot;
}&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;size18&quot;&gt;위 코드는 aws 인스턴스를 사용하는 웹 애플리케이션을 두 개 설정하는 코드입니다. 여기서 aws provider를 두 개 설정해서 각각 리전을 다르게 두고, 동일한 모듈을 사용하지만 설정을 다르게 해서 한 개의 애플리케이션은 캘리포니아에, 나머지 한 개의 애플리케이션은 서울 리전에 비치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;전반적인 인프라는 동일하지만 일부 설정만 바꿔야 할 때, 코드의 중복을 줄이며 조금 더 가독성 있게 선언할 수 있다는 강점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상태 (State)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;테라폼의 아이덴티티 중에 하나는 바로 상태입니다. 말 그래도 &lt;b&gt;인프라가 어떤 상태인지&lt;/b&gt;를 나타내죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기서 얻을 수 있는 이점이 저는 두 가지 정도 있다고 생각하는데, 첫 번째로는 &lt;b&gt;명확성&lt;/b&gt;이라고 생각합니다. 예를 들어서, 아래 코드는 AWS로 인프라를 구성했을 때, 테라폼에서 생성/사용하는 AWS 인프라의 상태(state)입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703496860090&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;version&quot;: 4,
  &quot;terraform_version&quot;: &quot;1.6.5&quot;,
  &quot;serial&quot;: 8,
  &quot;lineage&quot;: &quot;e6cf4b62-26f0-5152-72e7-2711318d69b7&quot;,
  &quot;outputs&quot;: {},
  &quot;resources&quot;: [
    {
      &quot;mode&quot;: &quot;data&quot;,
      &quot;type&quot;: &quot;aws_ami&quot;,
      &quot;name&quot;: &quot;amzn2&quot;,
      &quot;provider&quot;: &quot;provider[\&quot;registry.terraform.io/hashicorp/aws\&quot;]&quot;,
      &quot;instances&quot;: [
        {
          &quot;schema_version&quot;: 0,
          &quot;attributes&quot;: {
            &quot;architecture&quot;: &quot;x86_64&quot;,
            &quot;arn&quot;: &quot;arn:aws:ec2:ap-northeast-2::image/ami-XXXXXXXXXXXXX&quot;,
            &quot;block_device_mappings&quot;: [
              {
                &quot;device_name&quot;: &quot;/dev/xvda&quot;,
                &quot;ebs&quot;: {
                  &quot;delete_on_termination&quot;: &quot;true&quot;,
                  &quot;encrypted&quot;: &quot;false&quot;,
                  &quot;iops&quot;: &quot;0&quot;,
                  ...&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;size18&quot;&gt;당장에 확인했을 때 이게 무엇인지 유추할 수는 없겠지만, 인프라의 명확한 상태를 정의하기에 조금 더 확실하게 사태를 파악할 수 있다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;두 번째로 이 상태를 다른 사람과 &lt;b&gt;공유하며 작업&lt;/b&gt;할 수 있다는 것이 장점으로 느껴졌습니다. 조금 더 다른 사람과 협업하기에 쉽게 같은 상태를 사용하므로 더 확실하게 해당 인프라에 대해서만 작업할 수도 있습니다. 물론, 서로 동시에 상태를 수정하는 등의 문제가 발생할 수도 있는데, 테라폼은 이런 문제를 &lt;b&gt;공유 state 백엔드 저장소(Terraform Cloud, 이하 TFC)&lt;/b&gt;를 통해 해결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, &lt;b&gt;상태에 관해서 동시성을 제어&lt;/b&gt;할 수 있는 것이죠. 실제로, terraform의 설정을 두 터미널에서 동시에 적용하고자 했을 때, state에 lock이 걸려서 수정이 불가했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TOtFm/btsCF9rrs9s/xFWrM6gz2wm23Hha6k1kbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TOtFm/btsCF9rrs9s/xFWrM6gz2wm23Hha6k1kbk/img.png&quot; data-alt=&quot;출처: 테라폼 클라우드 워크스페이스 State&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TOtFm/btsCF9rrs9s/xFWrM6gz2wm23Hha6k1kbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTOtFm%2FbtsCF9rrs9s%2FxFWrM6gz2wm23Hha6k1kbk%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;426&quot; height=&quot;372&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 테라폼 클라우드 워크스페이스 State&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 사진은 해당 상황을 재현하기 위해 설정한 것인데, 이처럼 동시에 인프라 상태를 수정할 수 없게하여 협업에 있어서 노이즈가 발생하지 않게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;당장에 떠오르는 장점들을 나열해봤는데, &lt;b&gt;명확함&lt;/b&gt;이라는 게 제가 생각하는 테라폼의 가장 큰 장점이라고 느껴집니다. 아직 테라폼을 공부한지 얼마되지 않아 모든 테라폼의 특징/장점을 나열하지는 못했지만, 앞서 언급한 것들만 해도 강력한 도구 중 하나라는 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;조금 더 이론을 숙달하고 향후에 실전에서 활용해보게 되었을 때의 느낀점과 인사이트를 공유하도록 하겠습니다. :)&lt;/p&gt;</description>
      <category>데브옵스 &amp;amp; 인프라/IaC</category>
      <category>DevOps</category>
      <category>IAC</category>
      <category>infrastructure</category>
      <category>terraform</category>
      <category>데브옵스</category>
      <category>인프라</category>
      <category>테라폼</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/185</guid>
      <comments>https://dev-scratch.tistory.com/185#entry185comment</comments>
      <pubDate>Mon, 25 Dec 2023 18:44:59 +0900</pubDate>
    </item>
    <item>
      <title>[DataDog] Datadog 훑어보기</title>
      <link>https://dev-scratch.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;모니터링의 중요성을 체감해보지는 못했지만, 그 중요성만큼은 느껴집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어서, 분산 애플리케이션을 배포하고 이를 오케스트레이션해야 할 때, 가용성을 높이기 위해 오토스케일링 기준을 정해야 합니다. CPU는 어느 정도로 요청하고 제한해야 할지, 그리고 특히 메모리는 CPU와 달리 비압축 리소스이기 때문에 요청과 한계를 설정하는 것이 매우 중요하게 되죠. 이런 간극을 확인하고 설정하기 위해서 모니터링 도구는 메트릭을 집계하고 계속해서 모니터링하여 확인할 수 있으므로 유용하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;비단 위와 같은 예시 뿐만 아니라, 어느 시각에 트래픽이 몰리는지 한 눈에 확인할 수 있고, 어느 호스트가 장애를 겪고 있는지도 모니터링을 통해 손쉽게 확인할 수 있습니다. 여기에 훅을 통해 ChatOps까지 결합시키면 문제를 더욱 더 빠르고 효과적으로 대처할 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모니터링을 위한 도구는 Grafana도 있고 kibana, logstash도 있는데, 단독으로 사용한다기보다는 데이터 수집을 위한 Prometheus와, 스토리지로 사용할 InfluxDB 혹은 ElasticSearch 등이 함께 필요합니다. 그래서 데이터독이 완전한 SaaS 도구로서 상당한 편의성을 제공해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쿠버네티스 모니터링을 위한 모니터링 도구 비교글만 봐도 설치하기 쉽고 가장 PnP에 걸맞다는 내용을 볼 수 있었습니다. 그래서 데이터독에 관해서 간단하게 훑어보면 좋을 것 같습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crjGH8/btsATO5oemM/cTKKHQI17OXKsENgHNOE2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crjGH8/btsATO5oemM/cTKKHQI17OXKsENgHNOE2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crjGH8/btsATO5oemM/cTKKHQI17OXKsENgHNOE2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrjGH8%2FbtsATO5oemM%2FcTKKHQI17OXKsENgHNOE2k%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;1404&quot; height=&quot;864&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;864&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;size18&quot;&gt;데이터독 공식 유튜브 영상을 캡처했는데, 이렇게 모니터링을 커스터마이징해서 사용하는 사례를 보여주네요. 전회사에서도 비슷하게 모니터링을 할 수 있었는데, 데이터독도 상당히 낭만있는 것 같습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;APM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;데이터독을 검색하면 APM 도구라는 설명이 많이 나오는데요. APM은 Application Performance Monitoring의 준말로 애플리케이션 성능 모니터링 도구입니다. 애플리케이션에서 확인할 수 있는 거의 모든 것을 모니터링할 수 있고 심지어 병목을 일으키는 코드 라인도 확인할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Agent&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;데이터독은 원하는 호스트의 메트릭을 가져오기 위해서 각 호스트 별로 Agent를 설치합니다.&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;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5PW9t/btsA4TjfT7N/aBSPMhsw2kiOfrPVXZ3SbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5PW9t/btsA4TjfT7N/aBSPMhsw2kiOfrPVXZ3SbK/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://aws.amazon.com/ko/blogs/apn/how-to-visualize-and-monitor-your-aws-container-fleet-with-datadog/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5PW9t/btsA4TjfT7N/aBSPMhsw2kiOfrPVXZ3SbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5PW9t%2FbtsA4TjfT7N%2FaBSPMhsw2kiOfrPVXZ3SbK%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;942&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://aws.amazon.com/ko/blogs/apn/how-to-visualize-and-monitor-your-aws-container-fleet-with-datadog/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어, 쿠버네티스 환경에서 데이터독을 사용한다면 위와 같이 환경을 설정하게 됩니다. 각 노드 별로 한 개의 에이전트 파드가 필요하므로 DaemonSet 리소스로 배포됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쿠버네티스 말고 다양한 플랫폼을 지원하므로 아래 링크에서 원하는 플랫폼을 확인하여 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/agent/basic_agent_usage/?tab=agentv6v7&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.datadoghq.com/agent/basic_agent_usage/?tab=agentv6v7&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701083028674&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Basic Agent Usage&quot; data-og-description=&quot;Datadog, the leading service for cloud-scale monitoring.&quot; data-og-host=&quot;docs.datadoghq.com&quot; data-og-source-url=&quot;https://docs.datadoghq.com/agent/basic_agent_usage/?tab=agentv6v7&quot; data-og-url=&quot;https://docs.datadoghq.com/agent/basic_agent_usage/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dkgkYy/hyUB7Yfaos/rr3ZZIQs1KKkKcNjsjkRL0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/agent/basic_agent_usage/?tab=agentv6v7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.datadoghq.com/agent/basic_agent_usage/?tab=agentv6v7&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dkgkYy/hyUB7Yfaos/rr3ZZIQs1KKkKcNjsjkRL0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&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;Basic Agent Usage&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Datadog, the leading service for cloud-scale monitoring.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.datadoghq.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;1748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3rBmI/btsATkQ4cN6/jUKwoqkEttGkBcA7q2j3Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3rBmI/btsATkQ4cN6/jUKwoqkEttGkBcA7q2j3Hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3rBmI/btsATkQ4cN6/jUKwoqkEttGkBcA7q2j3Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3rBmI%2FbtsATkQ4cN6%2FjUKwoqkEttGkBcA7q2j3Hk%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;1858&quot; height=&quot;1748&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;1748&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;size18&quot;&gt;Agent 외에도 데이터독과 통합할 수 있는 애플리케이션이나 서비스들도 상당히 많이 즐비되어 있고, 각각 문서화가 잘 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;2434&quot; data-origin-height=&quot;1586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WZ3bM/btsATYz2c2d/DtjkNYT7oLYkvKnCtt44h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WZ3bM/btsATYz2c2d/DtjkNYT7oLYkvKnCtt44h0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WZ3bM/btsATYz2c2d/DtjkNYT7oLYkvKnCtt44h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWZ3bM%2FbtsATYz2c2d%2FDtjkNYT7oLYkvKnCtt44h0%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;2434&quot; height=&quot;1586&quot; data-origin-width=&quot;2434&quot; data-origin-height=&quot;1586&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;size18&quot;&gt;각 Agent가 메트릭과 이벤트를 수집해 Datadog에게 전달하고, 이 전달 받은 메트릭과 이벤트를 사용자에게 제공하는 방식으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어서, Nodejs 백엔드 환경에서 로그를 수집하려면 아래와 같이 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저 Logger를 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701084390115&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { createLogger, format, transports } = require('winston');

const logger = createLogger({
  level: 'info',
  exitOnError: false,
  format: format.json(),
  transports: [
    new transports.File({ filename: `${appRoot}/logs/&amp;lt;FILE_NAME&amp;gt;.log` }),
  ],
});

module.exports = logger;

// Example logs
logger.log('info', 'Hello simple log!');
logger.info('Hello log with metas',{color: 'blue' });&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;size18&quot;&gt;그 후, conf.d/ 디렉터리에 nodejs.d/ 디렉터리를 생성한 다음, conf.yaml 파일을 생성해 아래와 같이 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701084496716&quot; class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;init_config:

instances:

##Log section
logs:

  - type: file
    path: &quot;&amp;lt;FILE_NAME_PATH&amp;gt;.log&quot;
    service: &amp;lt;SERVICE_NAME&amp;gt;
    source: nodejs
    sourcecategory: sourcecode&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;size18&quot;&gt;혹은, 로거를 자동으로 연결하거나, 직접 로거를 만들어서 직접 APM과 연결할 수도 있습니다. 나중에 직접 테스트해보면 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Infrastructure&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hostmap&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z96iI/btsA26jgre5/zKvbkswNevKnAXXPL3sko1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z96iI/btsA26jgre5/zKvbkswNevKnAXXPL3sko1/img.png&quot; data-alt=&quot;Hostmap, 출처: Datadog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z96iI/btsA26jgre5/zKvbkswNevKnAXXPL3sko1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz96iI%2FbtsA26jgre5%2FzKvbkswNevKnAXXPL3sko1%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;1272&quot; height=&quot;683&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Hostmap, 출처: Datadog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모니터링 서비스 중 Infrastructure 서비스가 있는데, Host Map과 Infrastructure List를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Host Map에서는 모니터링하고 있는 호스트들을 한눈에 시각적으로 확인할 수 있습니다. CPU 가용률에 따라 음영이 다르게 표시됩니다.&lt;/p&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;939&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vVtSv/btsA3TxcsO3/LFb52Lu1jwcboCKZsAcP5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vVtSv/btsA3TxcsO3/LFb52Lu1jwcboCKZsAcP5K/img.png&quot; data-alt=&quot;Hostmap, 출처: Datadog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vVtSv/btsA3TxcsO3/LFb52Lu1jwcboCKZsAcP5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvVtSv%2FbtsA3TxcsO3%2FLFb52Lu1jwcboCKZsAcP5K%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;633&quot; height=&quot;580&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;939&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Hostmap, 출처: Datadog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;호스트 블럭을 자세히 확인할 수도 있는데, 각 호스트 별로 수집하고 있는 메트릭을 카테고리 별로 확인 가능합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Infrastructure List&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1435&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clBnhf/btsAUKuOrQV/GDLurMLabV8WOo91WKSoXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clBnhf/btsAUKuOrQV/GDLurMLabV8WOo91WKSoXk/img.png&quot; data-alt=&quot;Infrastructure List, 출처: datadog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clBnhf/btsAUKuOrQV/GDLurMLabV8WOo91WKSoXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclBnhf%2FbtsAUKuOrQV%2FGDLurMLabV8WOo91WKSoXk%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;1435&quot; height=&quot;569&quot; data-origin-width=&quot;1435&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Infrastructure List, 출처: datadog&lt;/figcaption&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;size18&quot;&gt;Infrastructure List에서 더 자세하게 확인할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Container View, Process View&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TcczI/btsAUKavvs4/klJIKIRv7zMd9ytMfASuN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TcczI/btsAUKavvs4/klJIKIRv7zMd9ytMfASuN0/img.png&quot; data-origin-width=&quot;2730&quot; data-origin-height=&quot;1370&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.9419%; margin-right: 10px;&quot; data-widthpercent=&quot;51.54&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TcczI/btsAUKavvs4/klJIKIRv7zMd9ytMfASuN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTcczI%2FbtsAUKavvs4%2FklJIKIRv7zMd9ytMfASuN0%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;2730&quot; height=&quot;1370&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8wK7o/btsAYIiXUKI/3wZZHzoTwO6kOoAACmq5l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8wK7o/btsAYIiXUKI/3wZZHzoTwO6kOoAACmq5l0/img.png&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.8953%;&quot; data-widthpercent=&quot;48.46&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8wK7o/btsAYIiXUKI/3wZZHzoTwO6kOoAACmq5l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8wK7o%2FbtsAYIiXUKI%2F3wZZHzoTwO6kOoAACmq5l0%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;2548&quot; height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;좌: Container View, 우: Process View (출처: Datadog)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;컨테이너 뷰나 프로세스 뷰도 제공합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Metrics&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Explorer&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HkXza/btsAUmOkMWY/vMI3B1khfZE2VWyOtTY6g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HkXza/btsAUmOkMWY/vMI3B1khfZE2VWyOtTY6g1/img.png&quot; data-alt=&quot;Metrics Explorer, 출처: Datadog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HkXza/btsAUmOkMWY/vMI3B1khfZE2VWyOtTY6g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHkXza%2FbtsAUmOkMWY%2FvMI3B1khfZE2VWyOtTY6g1%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;2034&quot; height=&quot;1278&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;1278&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Metrics Explorer, 출처: Datadog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;Grafana나 Prometheus에서 PromQL을 사용해 원하는 데이터만 긁어와서 메트릭을 확인하는 것처럼, 데이터독도 원하는 메트릭만 가져와 자유롭게 커스터마이징할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBny1I/btsAYEt5StE/64KUQudsgBRbM3ErSQQvQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBny1I/btsAYEt5StE/64KUQudsgBRbM3ErSQQvQ1/img.png&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;1050&quot; data-is-animation=&quot;false&quot; style=&quot;width: 70.6477%; margin-right: 10px;&quot; data-widthpercent=&quot;71.48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBny1I/btsAYEt5StE/64KUQudsgBRbM3ErSQQvQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBny1I%2FbtsAYEt5StE%2F64KUQudsgBRbM3ErSQQvQ1%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;1910&quot; height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7VdKm/btsA30bRL4i/RMKbMaLTxj9Sx65wS4k7Kk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7VdKm/btsA30bRL4i/RMKbMaLTxj9Sx65wS4k7Kk/img.jpg&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;1510&quot; data-is-animation=&quot;false&quot; style=&quot;width: 28.1895%;&quot; data-widthpercent=&quot;28.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7VdKm/btsA30bRL4i/RMKbMaLTxj9Sx65wS4k7Kk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7VdKm%2FbtsA30bRL4i%2FRMKbMaLTxj9Sx65wS4k7Kk%2Fimg.jpg&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;1096&quot; height=&quot;1510&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Metric Summary, 출처: Datadog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;혹은 Metrics를 종합적으로 확인할 수 있습니다. 여기서 좋은 점은 메트릭마다 description을 추가할 수 있어서 협업하기에도 좋아 보이네요. 또한, unit이나 metric type, 간격도 바꿀 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zBjzs/btsA4e8WRTX/5lXtkzkxTkd1BhyrNYIJe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zBjzs/btsA4e8WRTX/5lXtkzkxTkd1BhyrNYIJe1/img.png&quot; data-alt=&quot;Metric query, 출처: Datadog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zBjzs/btsA4e8WRTX/5lXtkzkxTkd1BhyrNYIJe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzBjzs%2FbtsA4e8WRTX%2F5lXtkzkxTkd1BhyrNYIJe1%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;1648&quot; height=&quot;844&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Metric query, 출처: Datadog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;메트릭을 쿼리를 사용하여 가져올 수도 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Monitors&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;685&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csH2yW/btsA24sgjWt/AQGW0qnpZklb3K8uLWK8c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csH2yW/btsA24sgjWt/AQGW0qnpZklb3K8uLWK8c1/img.png&quot; data-alt=&quot;Alerting, 출처: Datadog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csH2yW/btsA24sgjWt/AQGW0qnpZklb3K8uLWK8c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsH2yW%2FbtsA24sgjWt%2FAQGW0qnpZklb3K8uLWK8c1%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;1456&quot; height=&quot;685&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;685&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Alerting, 출처: Datadog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;중요한 변경 사항을 확인하기 위해 모니터링을 생성할 수 있습니다. 메트릭을 쿼리를 통해 정의하고, 특정 Threshold를 설정하여 알림을 받을 수 있습니다.&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;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XlWzG/btsAYHYDlzx/QfQgbVKED2qrKC8NwHz5V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XlWzG/btsAYHYDlzx/QfQgbVKED2qrKC8NwHz5V0/img.png&quot; data-alt=&quot;Notify, 출처: Datadog&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XlWzG/btsAYHYDlzx/QfQgbVKED2qrKC8NwHz5V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXlWzG%2FbtsAYHYDlzx%2FQfQgbVKED2qrKC8NwHz5V0%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;401&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Notify, 출처: Datadog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;1694&quot; data-origin-height=&quot;1077&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6TKOM/btsA27vH3BC/qzLz7xjcnMvvEy63mN4uu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6TKOM/btsA27vH3BC/qzLz7xjcnMvvEy63mN4uu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6TKOM/btsA27vH3BC/qzLz7xjcnMvvEy63mN4uu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6TKOM%2FbtsA27vH3BC%2FqzLz7xjcnMvvEy63mN4uu0%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;1694&quot; height=&quot;1077&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;1077&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모든 서비스를 훑어보기에는 시간이 없어서 간단하게 굵직한 서비스만 살펴봤습니다. 아무래도 직접 사용해보면서 익히는 게 확실하게 학습할 수 있을 것 같습니다. 이전에 Prometheus와 Grafana를 통해서 모니터링 애플리케이션을 만들었는 때는 조금 번거로웠는데, Datadog은 확실히 설정하기도 편하고 문서화가 너무 잘 되어 있어서 직접 실습해보기 좋아 보이네요. 가격이 조금 걸리긴 하지만, 그만큼 편의성을 확실히 제공하므로 사용하기 유용할 것 같습니다.&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;a href=&quot;https://medium.com/containerum/4-tools-to-monitor-your-kubernetes-cluster-efficiently-ceaf62818eea&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/containerum/4-tools-to-monitor-your-kubernetes-cluster-efficiently-ceaf62818eea&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701083761715&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;4 tools to monitor your Kubernetes cluster efficiently&quot; data-og-description=&quot;by Nikita Mazur&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/containerum/4-tools-to-monitor-your-kubernetes-cluster-efficiently-ceaf62818eea&quot; data-og-url=&quot;https://medium.com/containerum/4-tools-to-monitor-your-kubernetes-cluster-efficiently-ceaf62818eea&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HrUBe/hyUCa1JzMu/pQxJyXTeDLNgltu2Rj9OnK/img.jpg?width=1200&amp;amp;height=912&amp;amp;face=0_0_1200_912,https://scrap.kakaocdn.net/dn/fAH6f/hyUE2HLIcx/HP9k8mZMX1TlPqy92H384K/img.jpg?width=1358&amp;amp;height=905&amp;amp;face=0_0_1358_905,https://scrap.kakaocdn.net/dn/JTHtC/hyUCaAF6Wz/pQsc5265WzwrpZm4AcBsWk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://medium.com/containerum/4-tools-to-monitor-your-kubernetes-cluster-efficiently-ceaf62818eea&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/containerum/4-tools-to-monitor-your-kubernetes-cluster-efficiently-ceaf62818eea&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HrUBe/hyUCa1JzMu/pQxJyXTeDLNgltu2Rj9OnK/img.jpg?width=1200&amp;amp;height=912&amp;amp;face=0_0_1200_912,https://scrap.kakaocdn.net/dn/fAH6f/hyUE2HLIcx/HP9k8mZMX1TlPqy92H384K/img.jpg?width=1358&amp;amp;height=905&amp;amp;face=0_0_1358_905,https://scrap.kakaocdn.net/dn/JTHtC/hyUCaAF6Wz/pQsc5265WzwrpZm4AcBsWk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&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;4 tools to monitor your Kubernetes cluster efficiently&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;by Nikita Mazur&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701085720311&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Connecting Node.js Logs and Traces&quot; data-og-description=&quot;Connect your Node.js logs and traces to correlate them in Datadog.&quot; data-og-host=&quot;docs.datadoghq.com&quot; data-og-source-url=&quot;https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/&quot; data-og-url=&quot;https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HJEKR/hyUFbkoV6r/jKy2VesKn23KEnJjJE7ry1/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HJEKR/hyUFbkoV6r/jKy2VesKn23KEnJjJE7ry1/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&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;Connecting Node.js Logs and Traces&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Connect your Node.js logs and traces to correlate them in Datadog.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.datadoghq.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/getting_started/application/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.datadoghq.com/getting_started/application/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701085733888&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Getting Started in Datadog&quot; data-og-description=&quot;Datadog, the leading service for cloud-scale monitoring.&quot; data-og-host=&quot;docs.datadoghq.com&quot; data-og-source-url=&quot;https://docs.datadoghq.com/getting_started/application/&quot; data-og-url=&quot;https://docs.datadoghq.com/getting_started/application/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmw3oP/hyUB83XDIh/BJkTlkoYlBmwqskhxrqwJk/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/getting_started/application/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.datadoghq.com/getting_started/application/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmw3oP/hyUB83XDIh/BJkTlkoYlBmwqskhxrqwJk/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&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;Getting Started in Datadog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Datadog, the leading service for cloud-scale monitoring.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.datadoghq.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/dashboards/querying/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.datadoghq.com/dashboards/querying/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701085738208&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Querying&quot; data-og-description=&quot;Query your data to gain insight&quot; data-og-host=&quot;docs.datadoghq.com&quot; data-og-source-url=&quot;https://docs.datadoghq.com/dashboards/querying/&quot; data-og-url=&quot;https://docs.datadoghq.com/dashboards/querying/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cwn0T5/hyUFenTSbL/hfax5Rk1B6eD3xIDVAaDp1/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/dashboards/querying/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.datadoghq.com/dashboards/querying/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cwn0T5/hyUFenTSbL/hfax5Rk1B6eD3xIDVAaDp1/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&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;Querying&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Query your data to gain insight&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.datadoghq.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>데브옵스 &amp;amp; 인프라/Monitoring</category>
      <category>datadog</category>
      <category>DevOps</category>
      <category>log</category>
      <category>데이터독</category>
      <category>모니터링</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/184</guid>
      <comments>https://dev-scratch.tistory.com/184#entry184comment</comments>
      <pubDate>Mon, 27 Nov 2023 20:51:27 +0900</pubDate>
    </item>
    <item>
      <title>[GitLab/CI] GitLab에서 CI 작업하기</title>
      <link>https://dev-scratch.tistory.com/183</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;CI/CD를 사용할 때는 gitlab을 주로 사용한다고 합니다. GitHub와 달리 기본적으로 CI/CD에 집중한 원격 저장소라는 점이 특징이기 때문입니다. GitHub가 많은 사용자와 자유로운 기능이 장점이라면, GitLab은 통합적으로 서비스를 사용할 수 있다는 것이 장점이라 각각 고유의 메리트가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;GitLab 레포지터리에 들어가보면 바로 Set CI/CD 버튼이 보이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;2540&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sXyn2/btsAJ2hEIjb/K5p51KAOkekvQ0G5Toqj91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sXyn2/btsAJ2hEIjb/K5p51KAOkekvQ0G5Toqj91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sXyn2/btsAJ2hEIjb/K5p51KAOkekvQ0G5Toqj91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsXyn2%2FbtsAJ2hEIjb%2FK5p51KAOkekvQ0G5Toqj91%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;2540&quot; height=&quot;474&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;474&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;size18&quot;&gt;이번에는 gitlab ci를 통해 웹서버를 레포지터리에 업로드하고, 원격으로 웹서버를 띄우는 간단한 실습을 진행해보겠습니다. CI 단계만 진행하므로 서버로의 배포는 수동으로 진행합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;준비물&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&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;2350&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bocoSQ/btsAMocWR3K/Dz2zMNTxJhUwBmkH1k41W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bocoSQ/btsAMocWR3K/Dz2zMNTxJhUwBmkH1k41W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bocoSQ/btsAMocWR3K/Dz2zMNTxJhUwBmkH1k41W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbocoSQ%2FbtsAMocWR3K%2FDz2zMNTxJhUwBmkH1k41W0%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;2350&quot; height=&quot;104&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;104&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;size18&quot;&gt;레지스트리는 ECR private 레포지터리를 사용하였으며, 서버는 EC2 t2-micro 인스턴스를 사용했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WvzvD/btsAM5c9wzv/u0KkwDoXikiZMfrFLh5fi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WvzvD/btsAM5c9wzv/u0KkwDoXikiZMfrFLh5fi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WvzvD/btsAM5c9wzv/u0KkwDoXikiZMfrFLh5fi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWvzvD%2FbtsAM5c9wzv%2Fu0KkwDoXikiZMfrFLh5fi1%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;152&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CI 파일 작성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2768&quot; data-origin-height=&quot;1588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beSrYc/btsAJ2IHbqu/bQf6ucr7VRgX67dapvCxm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beSrYc/btsAJ2IHbqu/bQf6ucr7VRgX67dapvCxm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beSrYc/btsAJ2IHbqu/bQf6ucr7VRgX67dapvCxm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeSrYc%2FbtsAJ2IHbqu%2FbQf6ucr7VRgX67dapvCxm0%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;2768&quot; height=&quot;1588&quot; data-origin-width=&quot;2768&quot; data-origin-height=&quot;1588&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;size18&quot;&gt;gitlab은 .gitlab 디렉터리에 파일을 생성해서 ci를 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;가장 심플하게 작성한 ci 상세는 다음과 같습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot;&gt;&lt;code&gt;variables:
  APP_NAME: nodejs-server-gitlab
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: &quot;&quot;

stages:
  - build
  - push

create-env-file:
  only:
    - develop
  stage: build
  script:
    - echo DB_NAME=$DB_NAME &amp;gt;&amp;gt; .env
    - echo DB_USERNAME=$DB_USERNAME &amp;gt;&amp;gt; .env
    - echo DB_PASSWORD=$DB_PASSWORD &amp;gt;&amp;gt; .env
    - echo DB_HOST=$DB_HOST &amp;gt;&amp;gt; .env
    - echo DB_PORT=$DB_PORT &amp;gt;&amp;gt; .env
    - echo REDIS_URL=$REDIS_URL &amp;gt;&amp;gt; .env
    - echo REDIS_USERNAME=$REDIS_USERNAME &amp;gt;&amp;gt; .env
    - echo REDIS_PASSWORD=$REDIS_PASSWORD &amp;gt;&amp;gt; .env
    - echo JWT_SECRET=$JWT_SECRET &amp;gt;&amp;gt; .env
  artifacts:
    paths:
      - .env

docker-build:
  only:
    - develop

  stage: push
  
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - apk add --no-cache curl jq python3 py3-pip
    - pip install awscli
    - aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REPOSITORY
  script:
    - docker build -t $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID .
    - docker push $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  variables&lt;/h3&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot;&gt;&lt;code&gt;variables:
  APP_NAME: nodejs-server-gitlab
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;APP_NAME&lt;/code&gt; - 이미지 이름입니다. 굳이 사용하지 않아도 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DOCKER_HOST: tcp://docker:2375&lt;/code&gt;&amp;nbsp;- Docker가 데몬에 연결하는 데 사용되는 호스트입니다. tcp://docker:2375는 Docker 데몬이 돌아가고 있는 호스트와 포트입니다. gitlab의 CI/CD 파이프라인은 기본적으로 도커 컨테이너 내에서 작동하게 되는데, 각각의 job 단계들이 docker의 image를 사용합니다. 그래서 dind, 도커 안에 도커를 사용하는 방식을 사용해 docker client가 내부에서 도커를 돌릴 수 있게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DOCKER_DRIVER&lt;/code&gt; - &lt;span style=&quot;text-align: left;&quot;&gt;Docker 데몬이 사용하는 저장 드라이버를 설정합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;code&gt;DOCKER_TLS_CERTDIR&lt;/code&gt; - Docker TLS(전송 계층 보안) 연결을 사용할 때 인증서 파일이 저장된 디렉토리를 나타냅니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;DOCKER 관련 변수는 gitlab runner를 사용하지 않는 경우 굳이 변수를 설정하지 않아도 gitlab ci에서 사용이 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;  stages&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot;&gt;&lt;code&gt;stages:
  - build
  - push&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;gitlab ci의 단계입니다. github actions의 job 단계와 비슷합니다. stages를 통해 해당 ci 파이프라인에서 수행할 단계들을 미리 선언할 수 있습니다. 여기서 stages 배열에 넣은 단계 순서대로 작업이 진행됩니다. 위의 경우 build 스테이지 후 push 스테이지가 실행됩니다. stage를 선언하지 않으면 기본적으로 stage 속으로 선언하는 job들이 병렬적으로 실행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  create-env-file&lt;/h3&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot;&gt;&lt;code&gt;create-env-file:
  only:
    - develop
  stage: build
  script:
    - echo DB_NAME=$DB_NAME &amp;gt;&amp;gt; .env
    - echo DB_USERNAME=$DB_USERNAME &amp;gt;&amp;gt; .env
    - echo DB_PASSWORD=$DB_PASSWORD &amp;gt;&amp;gt; .env
    - echo DB_HOST=$DB_HOST &amp;gt;&amp;gt; .env
    - echo DB_PORT=$DB_PORT &amp;gt;&amp;gt; .env
    - echo REDIS_URL=$REDIS_URL &amp;gt;&amp;gt; .env
    - echo REDIS_USERNAME=$REDIS_USERNAME &amp;gt;&amp;gt; .env
    - echo REDIS_PASSWORD=$REDIS_PASSWORD &amp;gt;&amp;gt; .env
    - echo JWT_SECRET=$JWT_SECRET &amp;gt;&amp;gt; .env
  artifacts:
    paths:
      - .env&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;.env 파일을 생성합니다. 위 단계는 github actions에서 secrets를 생성하는 것과 유사한 과정입니다. Gitlab 레포지터리로 들어가 &lt;b&gt;Settings&lt;/b&gt; &amp;gt; &lt;b&gt;CI/CD&lt;/b&gt; &amp;gt; &lt;b&gt;Variables&lt;/b&gt;에서 변수를 설정하면 이 변수를 ci 파이프라인에서 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;1224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDxIwq/btsATA66qRA/HWFcWn7UoMORakCPIHDsh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDxIwq/btsATA66qRA/HWFcWn7UoMORakCPIHDsh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDxIwq/btsATA66qRA/HWFcWn7UoMORakCPIHDsh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDxIwq%2FbtsATA66qRA%2FHWFcWn7UoMORakCPIHDsh1%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;2340&quot; height=&quot;1224&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;1224&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;size18&quot;&gt;여기에 variables가 사용할 수 있는 속성이 3개가 있습니다. 이걸 잘 활용하는 게 중요합니다. 아무 설명도 안 읽고 하루 종일 ci를 시도했었는데 속성을 제대로 설정하지 않은 게 문제였습니다.  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Protected - protected 브랜치나 protected 태그에서만 사용할 수 있습니다. &lt;b&gt;Settings &amp;gt; Repository &amp;gt; Protected branches, Protected tags &lt;/b&gt;에서 추가할 수 있습니다. 만약 protected branch가 아닌 브랜치에서 ci를 수행하면 읽어오지 못합니다.&lt;/li&gt;
&lt;li&gt;Masked - ci job 로그에서 가려집니다.&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;1716&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ3yJP/btsAYEOcX27/CnipEAvPhSH1juDtDZqHOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ3yJP/btsAYEOcX27/CnipEAvPhSH1juDtDZqHOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ3yJP/btsAYEOcX27/CnipEAvPhSH1juDtDZqHOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ3yJP%2FbtsAYEOcX27%2FCnipEAvPhSH1juDtDZqHOK%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;678&quot; height=&quot;52&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;132&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;Expanded - 이 속성은 자세한 설명이 없는데, 변수 확장을 위해 쓰이는 것으로 보입니다. 예를 들어 VAR1=Hello 라면, VAR2=$VAL1_World로 사용했을 때 VAR2의 값은 Hello_World로 할당되는 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;신기한 점은 ci 상세에서 사용한 변수가 덮어써지는 경우가 있다는 것입니다. 예를 들어서,&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REPOSITORY&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 리전을 하드코딩하여 저장했고, CI/CD variables에 AWS_DEFAULT_REGION 변수를 저장하고 Masked 속성을 부여했는데 job log를 보니까 덮어써지는 것을 볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;보통 .env 파일을 생성할 때 github actions와 비슷한 방법으로 echo를 표준 입력으로 사용해 .env 파일에 저장합니다. 이때, &lt;b&gt;.env 파일을 artifacts로 저장해 두어야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot;&gt;&lt;code&gt;create-env-file:
  artifacts:
    paths:
      - .env&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;artifacts로 저장하지 않으면 다른 스테이지 작업을 수행할 때, 만들어둔 파일이 스테이지에서 사용되지 않습니다. 이것 때문에 또 시간을 많이 잡아먹었습니다.  &lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot;&gt;&lt;code&gt;only:
  - develop&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;only 필드의 값에 브랜치를 설정하면, 해당 브랜치에서만 stage가 동작하게 설정할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  docker-build&lt;/h3&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot;&gt;&lt;code&gt;docker-build:
  only:
    - develop

  stage: push

  image: docker:latest
  services:
    - docker:dind
  before_script:
    - apk add --no-cache curl jq python3 py3-pip
    - pip install awscli
    - aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REPOSITORY
  script:
    - docker build -t $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID .
    - docker push $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;마지막 docker 단계입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;image&lt;/b&gt;는 docker를 사용하며, &lt;b&gt;services&lt;/b&gt;를 통해 추가 이미지를 설정할 수 있습니다. docker 컨테이너 안에서 다시 docker 클라이언트를 사용해야 하므로, dind 태그 이미지를 추가로 지정해주었습니다. services 필드의 예시는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701072962033&quot; class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;job:
  services:
    - php:7
    - node:latest
    - golang:1.10
  image: alpine:3.7
  script:
    - php -v
    - node -v
    - go version&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;size18&quot;&gt;예를 들어, php, node, go를 사용하는 Job이라면 services에 php, node, golang 이미지를 추가로 지정할 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot;&gt;&lt;code&gt;before_script:
  - apk add --no-cache curl jq python3 py3-pip
  - pip install awscli
  - aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REPOSITORY&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;본격적으로 script를 수행하기 전에 다른 스크립트를 수행할 수 있습니다. aws ecr을 사용하기 때문에 aws ecr 레포지터리에 로그인이 필요합니다. awscli를 설치한 다음, ecr 패스워드를 획득해주도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기서 aws 설정이 필요합니다. Gitlab CI/CD variables에 미리 access key와 secret access key를 등록해두면 됩니다. oidc를 사용한 접근은 제대로 성공하지 못해서 access key와 secret access key를 등록하여 자격증명을 마쳤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bztG90/btsA4ciQm7E/9kxDH8UiCkcSGadPT2nlvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bztG90/btsA4ciQm7E/9kxDH8UiCkcSGadPT2nlvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bztG90/btsA4ciQm7E/9kxDH8UiCkcSGadPT2nlvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbztG90%2FbtsA4ciQm7E%2F9kxDH8UiCkcSGadPT2nlvK%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;346&quot; height=&quot;415&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기서 사용할 유저는 ecr에 로그인하고 image까지 Push 할 권한이 필요할 겁니다. 그렇기 때문에 User 권한으로 아래의 권한들을 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701073682107&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;ecr:GetAuthorizationToken&quot;
&quot;ecr:BatchCheckLayerAvailability&quot;
&quot;ecr:CompleteLayerUpload&quot;
&quot;ecr:InitiateLayerUpload&quot;
&quot;ecr:PutImage&quot;
&quot;ecr:UploadLayerPart&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 본격적으로 이미지를 빌드하고 푸시합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701073696491&quot; class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  script:
    - docker build -t $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID .
    - docker push $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID&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;size18&quot;&gt;여기서 사전에 설정해두지 않은 CI_PIPELINE_IID가 등장하는데요, 이건 Gitlab에서 미리 만들어둔 변수들입니다. 아래 링크에서 모든 변수들을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701073860375&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;Predefined variables reference | GitLab&quot; data-og-description=&quot;GitLab product documentation.&quot; data-og-host=&quot;docs.gitlab.com&quot; data-og-source-url=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&quot; data-og-url=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.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;Predefined variables reference | GitLab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GitLab product documentation.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gitlab.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;size18&quot;&gt;태그는 원하는 태그명을 사용하면 되므로 자유롭게 설정하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 push나 merge 이벤트를 발생시킨 다음&amp;nbsp;&lt;b&gt;Build &amp;gt; Pipelines&lt;/b&gt;에 들어가서 파이프라인을 확인하면 현재 진행중인 ci 단계를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6bpod/btsA3x8IIWP/Nv85y7UAImBkYD7P72Sn6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6bpod/btsA3x8IIWP/Nv85y7UAImBkYD7P72Sn6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6bpod/btsA3x8IIWP/Nv85y7UAImBkYD7P72Sn6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6bpod%2FbtsA3x8IIWP%2FNv85y7UAImBkYD7P72Sn6K%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;2672&quot; height=&quot;806&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;806&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;1338&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tZp2p/btsAUp5e6nJ/68vA5TorKkJ389hiZKjDs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tZp2p/btsAUp5e6nJ/68vA5TorKkJ389hiZKjDs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tZp2p/btsAUp5e6nJ/68vA5TorKkJ389hiZKjDs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtZp2p%2FbtsAUp5e6nJ%2F68vA5TorKkJ389hiZKjDs1%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;1338&quot; height=&quot;750&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;750&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;size18&quot;&gt;모든 단계가 성공적으로 수행돼, 이미지가 push 된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 push 된 이미지를 ec2 에서 pull 받아 컨테이너를 실행시키면 끝입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;1210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU9Vh3/btsATfPIKeH/OzBMkKv6AUf34mK56g4luk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU9Vh3/btsATfPIKeH/OzBMkKv6AUf34mK56g4luk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU9Vh3/btsATfPIKeH/OzBMkKv6AUf34mK56g4luk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU9Vh3%2FbtsATfPIKeH%2FOzBMkKv6AUf34mK56g4luk%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;1788&quot; height=&quot;1210&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;1210&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;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;1226&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CHgX8/btsAXiEC8ip/m97DHNotOLf7o7zr0qretk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CHgX8/btsAXiEC8ip/m97DHNotOLf7o7zr0qretk/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://aws.amazon.com/ko/blogs/devops/unlock-the-power-of-ec2-graviton-with-gitlab-ci-cd-and-eks-runners/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CHgX8/btsAXiEC8ip/m97DHNotOLf7o7zr0qretk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCHgX8%2FbtsAXiEC8ip%2Fm97DHNotOLf7o7zr0qretk%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;1226&quot; height=&quot;1360&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://aws.amazon.com/ko/blogs/devops/unlock-the-power-of-ec2-graviton-with-gitlab-ci-cd-and-eks-runners/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아직은 gitlab에 관해 확실하게 학습하지 못해서 간단한 ci 파이프라인만 생성했는데, ci/cd까지 수행할 수 있도록 숙련하면 좋을 것 같습니다. Gitlab의 runner를 사용하면 외부에서 ci 파이프라인을 동작시킬 수 있습니다. 예를 들어, ec2 인스턴스에 gitlab runner를 설치해 에이전트로 등록을 해두면, gitlab ci가 runner를 트리거하여 ec2 인스턴스 속 runner가 해당 파이프라인을 수행하여 배포까지 할 수 있기 때문입니다. 특히 kubernetes로의 배포까지 통합적으로 수행할 수 있는 게 아주 강력한 장점이라고 생각이 드네요.&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;a href=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701074593314&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;Use Docker to build Docker images | GitLab&quot; data-og-description=&quot;GitLab product documentation.&quot; data-og-host=&quot;docs.gitlab.com&quot; data-og-source-url=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&quot; data-og-url=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&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;Use Docker to build Docker images | GitLab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GitLab product documentation.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gitlab.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/services/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gitlab.com/ee/ci/services/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701074601789&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;Services | GitLab&quot; data-og-description=&quot;GitLab product documentation.&quot; data-og-host=&quot;docs.gitlab.com&quot; data-og-source-url=&quot;https://docs.gitlab.com/ee/ci/services/&quot; data-og-url=&quot;https://docs.gitlab.com/ee/ci/services/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/services/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gitlab.com/ee/ci/services/&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;Services | GitLab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GitLab product documentation.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gitlab.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701074610014&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;Use Docker to build Docker images | GitLab&quot; data-og-description=&quot;GitLab product documentation.&quot; data-og-host=&quot;docs.gitlab.com&quot; data-og-source-url=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&quot; data-og-url=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker&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;Use Docker to build Docker images | GitLab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GitLab product documentation.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gitlab.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701074616291&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;Predefined variables reference | GitLab&quot; data-og-description=&quot;GitLab product documentation.&quot; data-og-host=&quot;docs.gitlab.com&quot; data-og-source-url=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&quot; data-og-url=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gitlab.com/ee/ci/variables/predefined_variables.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;Predefined variables reference | GitLab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GitLab product documentation.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gitlab.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;</description>
      <category>데브옵스 &amp;amp; 인프라/Docker</category>
      <category>CD</category>
      <category>CI</category>
      <category>DevOps</category>
      <category>GitLab</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/183</guid>
      <comments>https://dev-scratch.tistory.com/183#entry183comment</comments>
      <pubDate>Mon, 27 Nov 2023 17:46:26 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes/RBAC] AWS IAM처럼 쿠버네티스 파드에 역할 부여하기</title>
      <link>https://dev-scratch.tistory.com/182</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;쿠버네티스는 API 서버를 사용하여 리소스들의 생성을 감독합니다. 실제로 API 서버를 사용하는 리소스들을 살펴보면 아래와 같습니다. etcd를 제외하고서 거의 대부분의 중요한 리소스들이 API 서버를 의존하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3088&quot; data-origin-height=&quot;1976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdtBdL/btsAPVhrXO0/gn7RBWTVHjR2UXBGbewoK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdtBdL/btsAPVhrXO0/gn7RBWTVHjR2UXBGbewoK0/img.png&quot; data-alt=&quot;쿠버네티스 인 액션 참고하여 제작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdtBdL/btsAPVhrXO0/gn7RBWTVHjR2UXBGbewoK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdtBdL%2FbtsAPVhrXO0%2Fgn7RBWTVHjR2UXBGbewoK0%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;3088&quot; height=&quot;1976&quot; data-origin-width=&quot;3088&quot; data-origin-height=&quot;1976&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;API 서버는 그 이름처럼 API를 받아서 이를 처리하는 리소스인데, 외부 클라리언트가 kubectl 등으로 요청을 전송하면, RESTful API로 변경하여 클러스터를 조작합니다. API 서버 내부에서는 그 요청을 보낸 주체가 누구인지 &lt;b&gt;인증 플러그인&lt;/b&gt;을 거치고, &lt;b&gt;권한 승인 플러그인&lt;/b&gt;으로 리소스에 대해 요청을 수행할 수 있는지 결정한 다음, &lt;b&gt;승인 제어 플러그인&lt;/b&gt;을 통해 리소스를 수정/삭제 혹은 거절합니다. 최종적으로 리소스의 상태와 데이터 등을 etcd에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;API 서버가 직접 리소스를 생성하거나 추가하는 것은 아니고, API 서버가 리소스의 생성 및 배포를 위해 리소스 객체들을 담아두고, 이를 컨트롤러 매니저가 감시하여 필요한 리소스들을 생성하며 스케쥴러가 노드에 파드를 할당하여 각 노드의 kubelet에 전달하여 생성합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서비스 어카운트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때, API 서버에 접속할 수 있는 클라이언트는 외부 사용자도 있지만, 파드 내부의 애플리케이션이 될 수도 있습니다. 이때, 파드는 서비스 어카운트(Service account)를 통해 접근하게 됩니다. 앞서 API 서버에는 인증 플러그인을 거친다고 언급했는데, 파드에서는 /var/run/secrets/kubernetes.io/serviceaccount에 secret 볼륨이 마운트 되어 있는데 /token 디렉터리에 토큰이 있어서 이 토큰으로 파드를 인증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;서비스 어카운트라는 자격을 토큰이라는 자격증으로 인증하는 느낌으로 바라볼 수도 있겠네요. 이 서비스 어카운트는 그 네임스페이스에 속한 리소스만이 소지할 수 있습니다. 예를 들어, foo 라는 네임스페이스에 있는 파드는 foo 네임스페이스에 속한 서비스 어카운트만 사용 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;파드는 자신이 가진 서비스 어카운트라는 자격증을 사용해서 원하는 작업을 요청할 수 있습니다. 자격증에서 특정 자격을 가지지 않고 있다면 그 자격을 요구하는 작업은 불가능하게 되겠죠? 이렇게 토큰을 인증하더라도, 승인이 허가되거나 불허가되기도 합니다. API 서버는 승인 플러그인을 사용할 수 있는데, 이중의 하나가 &lt;b&gt;역할 기반 제어 플러그인(RBAC)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;서비스 어카운트는 시크릿을 마운트하여 사용하는데 이는 이미지 풀 시크릿도 포함됩니다. 따라서, 서비스 어카운트에 사용할 이미지 풀 시크릿을 지정해 놓으면 파드에 서비스 어카운트를 지정함으로써 이미지 풀 시크릿을 지정하지 않더라도 해당하는 파드에 추가할 필요가 없습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ServiceAccount
metadata:
  name: erc-service-account
imagePullSecrets:
  - name: erc-cred&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: curl-custom-sa
spec:
  serviceAccountName: foo
  containers:
    - name: main
      image: curlimages/curl
      command: [&quot;sleep&quot;, &quot;9999999&quot;]
    - name: ambassador
      image: stae1102/kubectl-proxy:1.0.3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2098&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eg4TKY/btsASrnCl5m/vydUIpzF0sCh3S3VdSHPt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eg4TKY/btsASrnCl5m/vydUIpzF0sCh3S3VdSHPt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eg4TKY/btsASrnCl5m/vydUIpzF0sCh3S3VdSHPt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feg4TKY%2FbtsASrnCl5m%2FvydUIpzF0sCh3S3VdSHPt0%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;2098&quot; height=&quot;1194&quot; data-origin-width=&quot;2098&quot; data-origin-height=&quot;1194&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;size18&quot;&gt;API 서버와 통신하기 위해 서비스 어카운트의 토큰을 사용할 수 있습니다. 하지만 지금은 별도의 권한을 설정하지 않은 서비스 어카운트이므로 획득할 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2098&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1HH8e/btsASePuQyW/QKAGCiq3dlGIgxqgK035n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1HH8e/btsASePuQyW/QKAGCiq3dlGIgxqgK035n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1HH8e/btsASePuQyW/QKAGCiq3dlGIgxqgK035n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1HH8e%2FbtsASePuQyW%2FQKAGCiq3dlGIgxqgK035n1%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;2098&quot; height=&quot;1194&quot; data-origin-width=&quot;2098&quot; data-origin-height=&quot;1194&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;h2 data-ke-size=&quot;size26&quot;&gt;RBAC 인증 플러그인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;RBAC는 권한을 제공하여 사용자가 클러스터 관련 작업을 가능케 합니다. AWS IAM 사용자에게 정책을 추가하여 역할을 달리하는 것과 같은 느낌입니다. API 서버는 REST 인터페이스를 통해 HTTP 요청을 서버로 전송해 작업을 수행하므로, 요청과 함께 자격증명을 포함해 인증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, 수행할 종류도 제한할 수 있습니다. get, create, update 등의 작업이 HTTP 메서드의 GET, POST, PUT에 매핑됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RBAC 리소스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;RBAC는 네 가지 리소스로 구성됩니다. &lt;b&gt;롤&lt;/b&gt;과 &lt;b&gt;롤바인딩&lt;/b&gt;, &lt;b&gt;클러스터롤&lt;/b&gt;과 &lt;b&gt;클러스터롤바인딩&lt;/b&gt;이며 리소스 접근 수준이 네임스페이스인지 클러스터인지의 차이뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;롤&lt;/b&gt;은 역할을 만들고, &lt;b&gt;롤바인딩&lt;/b&gt;은 역할을 주체에 지정해줍니다. 서비스 어카운트에 역할을 지정해주기 위해서는 미리 역할을 만들고, 이 역할을 사용할 서비스 어카운트에 역할을 이어줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  롤, 롤바인딩 실습&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;RBAC를 직접 테스트해보는 방법이 가장 이해하기 쉬울 것 같습니다. 롤과 클러스터롤의 차이를 확인하기 위해 네임스페이스와 파드를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700812441250&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create ns foo
kubectl run test --image=stae1102/kubectl-proxy:1.0.3 -n foo
kubectl create ns bar
kubectl run test --image=stae1102/kubectl-proxy:1.0.3 -n bar&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;size18&quot;&gt;여기서 kubectl proxy는 kubectl의 proxy 명령어로 API 서버로 요청을 보내기 위한 작업을 수행하는 이미지입니다. 각, 운영체제마다 kubectl 바이너리가 다르므로 직접 이미지를 생성하는 것이 좋습니다. 위 이미지는 arm64를 사용하는 호스트 OS를 기준으로 생성한 이미지입니다. 따라서, cpu 아키텍처가 다르다면 다른 버전을 지정해주어야 합니다. 아래는 Dockerfile과 해당 도커파일에서 사용하는 스크립트입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM alpine
RUN apk update &amp;amp;&amp;amp; apk add curl &amp;amp;&amp;amp; curl -LO &quot;https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl&quot; &amp;amp;&amp;amp; chmod 700 kubectl &amp;amp;&amp;amp; mv kubectl /
ADD kubectl-proxy.sh /kubectl-proxy.sh
RUN chmod 700 kubectl-proxy.sh
ENTRYPOINT /kubectl-proxy.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#!/bin/sh

API_SERVER=&quot;https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT&quot;
CA_CRT=&quot;/var/run/secrets/kubernetes.io/serviceaccount/ca.crt&quot;
TOKEN=&quot;$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)&quot;

./kubectl proxy --server=&quot;$API_SERVER&quot; --certificate-authority=&quot;$CA_CRT&quot; --token=&quot;$TOKEN&quot; --accept-paths='^.*'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;kubectl 버전은 여기서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700812699304&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Install and Set Up kubectl on Linux&quot; data-og-description=&quot;Before you begin You must use a kubectl version that is within one minor version difference of your cluster. For example, a v1.28 client can communicate with v1.27, v1.28, and v1.29 control planes. Using the latest compatible version of kubectl helps avoid&quot; data-og-host=&quot;kubernetes.io&quot; data-og-source-url=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&quot; data-og-url=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cUzDzm/hyUB7iSQ1G/Q1XFBepWbZoUi4qaVNjNBk/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373,https://scrap.kakaocdn.net/dn/C50AO/hyUCeI3v99/D5kauXFbKWjkLQVCKjXgzk/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cUzDzm/hyUB7iSQ1G/Q1XFBepWbZoUi4qaVNjNBk/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373,https://scrap.kakaocdn.net/dn/C50AO/hyUCeI3v99/D5kauXFbKWjkLQVCKjXgzk/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&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;Install and Set Up kubectl on Linux&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Before you begin You must use a kubectl version that is within one minor version difference of your cluster. For example, a v1.28 client can communicate with v1.27, v1.28, and v1.29 control planes. Using the latest compatible version of kubectl helps avoid&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kubernetes.io&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;size18&quot;&gt;이제 kubectl proxy를 통해 서비스 목록을 나열하면 아래와 같은 결과를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2550&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daMGPd/btsASHcIa6g/eVERt9SaOyXI0Le2zD6pc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daMGPd/btsASHcIa6g/eVERt9SaOyXI0Le2zD6pc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daMGPd/btsASHcIa6g/eVERt9SaOyXI0Le2zD6pc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaMGPd%2FbtsASHcIa6g%2FeVERt9SaOyXI0Le2zD6pc1%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;2550&quot; height=&quot;524&quot; data-origin-width=&quot;2550&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;디폴트로 생성된 서비스 어카운트로는 서비스 목록을 나열할 수가 없군요. 그러면 이제 역할을 만들어줄 차례입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: foo
  name: service-reader
rules:
  - apiGroups: [&quot;&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;]
    resources: [&quot;services&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위는 foo namespace의 service 리소스에 get과 list를 허용하는 역할을 의미합니다. apiGroups는 서비스의 apiGroup으로 core apiGroup은 공백으로 표기합니다. 그림으로 보자면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3204&quot; data-origin-height=&quot;1604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqF5k/btsAOUY6zCC/oHyFBeIKfe0zN9Zs3nemL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqF5k/btsAOUY6zCC/oHyFBeIKfe0zN9Zs3nemL1/img.png&quot; data-alt=&quot;쿠버네티스 인 액션을 참고하여 제작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqF5k/btsAOUY6zCC/oHyFBeIKfe0zN9Zs3nemL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnqF5k%2FbtsAOUY6zCC%2FoHyFBeIKfe0zN9Zs3nemL1%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;3204&quot; height=&quot;1604&quot; data-origin-width=&quot;3204&quot; data-origin-height=&quot;1604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;쿠버네티스 인 액션을 참고하여 제작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;foo 네임스페이스에 생성한 롤은 foo 네임스페이스에 있는 서비스에만 역할을 부여하는 것이죠. 당연히 명령어로도 간단하게 롤을 생성할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700814546411&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create role service-reader --verb=get,list --resource=services -n bar&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;size18&quot;&gt;bar 네임스페이스에 서비스를 가져오고 나열할 수 있는 롤을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;롤을 생성했다면, 이 롤을 사용할 수 있도록 서비스 어카운트(혹은 유저, 그룹)와 이어줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700814653105&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create rolebinding test --role=service-reader --serviceaccount=foo:default -n foo&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;size18&quot;&gt;test 롤바인딩을 만드는데 역할은 이전에 만든 service-reader를 고르고, 바인딩할 서비스 어카운트는 foo 네임스페이스의 default 서비스 어카운트를 선택합니다. 아래는 현재의 RBAC 구조입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3204&quot; data-origin-height=&quot;1604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qYkaO/btsAO8QxtXM/BgxeE7cIiSL4pKQoOYmypk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qYkaO/btsAO8QxtXM/BgxeE7cIiSL4pKQoOYmypk/img.png&quot; data-alt=&quot;쿠버네티스 인 액션을 참고하여 제작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qYkaO/btsAO8QxtXM/BgxeE7cIiSL4pKQoOYmypk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqYkaO%2FbtsAO8QxtXM%2FBgxeE7cIiSL4pKQoOYmypk%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;3204&quot; height=&quot;1604&quot; data-origin-width=&quot;3204&quot; data-origin-height=&quot;1604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;쿠버네티스 인 액션을 참고하여 제작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;롤바인딩은 서비스 어카운트 외에도 여러 개의 주체들에 롤을 바인딩할 수 있습니다. 이제 foo 네임스페이스의 service에 조회할 권한이 생겼으므로 확인해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1700815142954&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/ # curl localhost:8001/api/v1/namespaces/foo/services&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;936&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRPiPp/btsASUXt55b/cr2zIvaKleCRyi7NpQHD3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRPiPp/btsASUXt55b/cr2zIvaKleCRyi7NpQHD3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRPiPp/btsASUXt55b/cr2zIvaKleCRyi7NpQHD3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRPiPp%2FbtsASUXt55b%2Fcr2zIvaKleCRyi7NpQHD3K%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;936&quot; height=&quot;272&quot; data-origin-width=&quot;936&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;foo 네임스페이스에 있는 파드 컨테이너에 접속하여 API 서버로 service 조회가 가능합니다. bar 네임스페이스에도 권한을 부여합니다. kubectl edit 명령어를 사용합니다. &lt;b&gt;다른 네임스페이스에 있더라도 다른 포드의 서비스 어카운트를 추가할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700815346122&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl edit rolebinding test -n foo&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1700815383854&quot; class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;subjects:
  - kind: ServiceAccount
    name: default
    namespace: foo
  - kind: ServiceAccount
    name: default
    namespace: bar&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;size18&quot;&gt;이렇게 하면 롤 바인딩은 foo 네임스페이스에 있지만, bar 네임스페이스의 default 서비스 어카운트에서 foo 네임스페이스의 서비스에 get, list할 수 있는 역할을 바인딩받습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4512&quot; data-origin-height=&quot;1472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHgD5p/btsAPwXW95I/wfIGRQFUTRxu417QBZKRN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHgD5p/btsAPwXW95I/wfIGRQFUTRxu417QBZKRN0/img.png&quot; data-alt=&quot;쿠버네티스 인 액션 참고 제작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHgD5p/btsAPwXW95I/wfIGRQFUTRxu417QBZKRN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHgD5p%2FbtsAPwXW95I%2FwfIGRQFUTRxu417QBZKRN0%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;4512&quot; height=&quot;1472&quot; data-origin-width=&quot;4512&quot; data-origin-height=&quot;1472&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;쿠버네티스 인 액션 참고 제작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  클러스터롤, 클러스터롤바인딩 실습&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;롤과 클러스터롤의 차이는 클러스터 범위의 리소스에 역할을 가지는지의 차이입니다. 롤은 네임스페이스 스코프의 리소스에 역할을 가지며, 클러스터롤은 클러스터 스코프의 리소스에 역할을 가집니다. 그래서 둘의 사용법은 크게 다르지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번에는 클러스터에서 PersistentVolume을 나열하는 역할을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700815877805&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create clusterrole pv-reader --verb=get,list --resource=persistentvolumes&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;size18&quot;&gt;위 클러스터롤로 pv들을 가져오고 나열할 수 있는 권한을 생성했으므로, 이 권한을 실제로 사용할 수 있도록 바인딩해줍시다.&lt;/p&gt;
&lt;pre id=&quot;code_1700815971225&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create rolebinding pv-test --clusterrole=pv-reader --serviceaccount=foo:default -n foo&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;size18&quot;&gt;일반 롤바인딩을 생성하여 클러스터롤을 foo 네임스페이스의 default 서비스 어카운트에 바인딩했습니다. 클러스터롤과 롤바인딩의 조합은 여전히 네임스페이스 수준의 리소스에만 접근할 수 있습니다. 클러스터롤을 참조하는 롤바인딩은 클러스터 수준의 리소스에 대한 접근 권한을 부여하지 않기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3288&quot; data-origin-height=&quot;1604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rH6YN/btsARV3N2r3/UXweGjq5smggj1kVSOq6IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rH6YN/btsARV3N2r3/UXweGjq5smggj1kVSOq6IK/img.png&quot; data-alt=&quot;쿠버네티스 인 액션 참고 제작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rH6YN/btsARV3N2r3/UXweGjq5smggj1kVSOq6IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrH6YN%2FbtsARV3N2r3%2FUXweGjq5smggj1kVSOq6IK%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;3288&quot; height=&quot;1604&quot; data-origin-width=&quot;3288&quot; data-origin-height=&quot;1604&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, 클러스터 수준의 리소스에 접근하고자 한다면 클러스터롤과 클러스터롤바인딩을 사용해야만 합니다. 이전 롤바인딩은 삭제하고 클러스터롤바인딩을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700816678408&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create clusterrolebinding pv-test --clusterrole=pv-reader --serviceaccount=foo:default&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;886&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qJste/btsARjw0Rqr/qoEW3UwZ4KLXc0UUu6r1xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qJste/btsARjw0Rqr/qoEW3UwZ4KLXc0UUu6r1xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qJste/btsARjw0Rqr/qoEW3UwZ4KLXc0UUu6r1xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqJste%2FbtsARjw0Rqr%2FqoEW3UwZ4KLXc0UUu6r1xk%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;886&quot; height=&quot;416&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;416&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;size18&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;3288&quot; data-origin-height=&quot;1604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz33R1/btsAPLOhuxh/q0ofclop8uDNeKcdTap5Jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz33R1/btsAPLOhuxh/q0ofclop8uDNeKcdTap5Jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz33R1/btsAPLOhuxh/q0ofclop8uDNeKcdTap5Jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz33R1%2FbtsAPLOhuxh%2Fq0ofclop8uDNeKcdTap5Jk%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;401&quot; data-origin-width=&quot;3288&quot; data-origin-height=&quot;1604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;즉, 조합에 따라서 리소스에 접근할 수 있는 수준이 달라집니다. 클러스터 수준의 리소스에 접근해야 하거나 여러 네임스페이스에 있는 네임스페이스에 접근해야 한다면 클러스터롤-클러스터롤바인딩을, 특정 네임스페이스 리소스에 접근하기 위해 역할을 재사용해야 한다면 클러스터롤-롤바인딩을, 특정 네임스페이스 리소스에 접근하기 위해 역할을 한 번 사용한다면 롤-롤바인딩을 사용하면 좋습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;클러스터롤은 기본적으로 다양하게 생성되므로 필요한 역할에 따라서 롤바인딩, 클러스터롤바인딩을 사용해야 합니다. 너무 과도한 권한은 사용하지 않는 게 좋습니다. 대표적으로 view, edit, admin, cluster-admin 클러스터롤이 포드의 서비스 어카운트에 사용되는 역할이며, 포드에 적절한 클러스터롤을 서비스 어카운트에 바인딩 해주어야 합니다. 예를 들어, view 클러스터롤에는 시크릿을 읽을 권한이 없습니다. 시크릿에서 view 클러스터롤보다 더 많은 권한을 가지는 토큰을 가져와 사용할 수 있기 때문입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;너무 과한 역할은 쿠버네티스 보안에 좋지 않습니다. AWS에서 IAM 유저를 만들고 역할을 지정하여 사용하는 것을 권장하는 것처럼 각 포드가 적절한 권한의 서비스 어카운트를 부여하여 보안을 높이는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;원래는 RBAC에 대해 이해하기 좀 어려웠는데, AWS IAM과 비교하며 공부하니까 이해가 잘 돼서 글을 작성하게 됐습니다. 글을 정리하면서 한 번 더 공부하니까 더 머리에 잘 들어오네요.&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;a href=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&quot;&gt;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700817874909&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Install and Set Up kubectl on Linux&quot; data-og-description=&quot;Before you begin You must use a kubectl version that is within one minor version difference of your cluster. For example, a v1.28 client can communicate with v1.27, v1.28, and v1.29 control planes. Using the latest compatible version of kubectl helps avoid&quot; data-og-host=&quot;kubernetes.io&quot; data-og-source-url=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&quot; data-og-url=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cUzDzm/hyUB7iSQ1G/Q1XFBepWbZoUi4qaVNjNBk/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373,https://scrap.kakaocdn.net/dn/C50AO/hyUCeI3v99/D5kauXFbKWjkLQVCKjXgzk/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cUzDzm/hyUB7iSQ1G/Q1XFBepWbZoUi4qaVNjNBk/img.png?width=1727&amp;amp;height=373&amp;amp;face=0_0_1727_373,https://scrap.kakaocdn.net/dn/C50AO/hyUCeI3v99/D5kauXFbKWjkLQVCKjXgzk/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&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;Install and Set Up kubectl on Linux&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Before you begin You must use a kubectl version that is within one minor version difference of your cluster. For example, a v1.28 client can communicate with v1.27, v1.28, and v1.29 control planes. Using the latest compatible version of kubectl helps avoid&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kubernetes.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 인 액션, 마르코 룩샤, 에이콘 출판, 12장&lt;/p&gt;</description>
      <category>데브옵스 &amp;amp; 인프라/Kubernetes</category>
      <category>DevOps</category>
      <category>k8s</category>
      <category>Kubernetes</category>
      <category>RBAC</category>
      <category>쿠버네티스</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/182</guid>
      <comments>https://dev-scratch.tistory.com/182#entry182comment</comments>
      <pubDate>Fri, 24 Nov 2023 18:26:01 +0900</pubDate>
    </item>
    <item>
      <title>[k8s/argocd] Helm과 ArgoCD, Github Actions, ECR를 활용한 배포 파이프라인 구축 (minikube)</title>
      <link>https://dev-scratch.tistory.com/181</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;오늘은 자동화의 꽃인 파이프라인을 구현해보도록 하겠습니다. 배포 자동화는 다양한 방법으로 구현할 수 있습니다. 예를 들어서, github actions로 beanstalk에 패키지를 전송하여 배포할 수도 있으며, code pipeline을 통해 빌드와 배포를 자동화할 수도 있고, github actions로 빌드한 파일을 code deploy에 전달하는 방법도 있습니다. 파이프라인을 추상적으로 보자면, &lt;b&gt;빌드 -&amp;gt; (테스트) -&amp;gt; 배포&lt;/b&gt;의 과정을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번에는 kubernetes를 활용하는 환경에서 github action과 argocd를 활용해 빌드 및 배포를 자동화하는 방법을 구현합니다. 나름대로 구조를 구체화했을 때, 아래와 같은 형태를 따릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20231113183950.png&quot; data-origin-width=&quot;5004&quot; data-origin-height=&quot;3076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdF9L5/btsAm23tY7j/746Kw3WKf4lGZx7VAJiQE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdF9L5/btsAm23tY7j/746Kw3WKf4lGZx7VAJiQE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdF9L5/btsAm23tY7j/746Kw3WKf4lGZx7VAJiQE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdF9L5%2FbtsAm23tY7j%2F746Kw3WKf4lGZx7VAJiQE1%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;5004&quot; height=&quot;3076&quot; data-filename=&quot;Pasted image 20231113183950.png&quot; data-origin-width=&quot;5004&quot; data-origin-height=&quot;3076&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;과정은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;5004&quot; data-origin-height=&quot;3076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxMc2n/btsAmDpqYvZ/vepQXLUMvHjRWcJIW7QiU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxMc2n/btsAmDpqYvZ/vepQXLUMvHjRWcJIW7QiU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxMc2n/btsAmDpqYvZ/vepQXLUMvHjRWcJIW7QiU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxMc2n%2FbtsAmDpqYvZ%2FvepQXLUMvHjRWcJIW7QiU1%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;5004&quot; height=&quot;3076&quot; data-origin-width=&quot;5004&quot; data-origin-height=&quot;3076&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;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;CI 도구 트리거: 레포지터리의 변경사항을 자동으로 감지하거나, 특정 조건을 부과하여 CI 도구가 원하는 동작을 수행할 수 있도록 합니다. 본 예시에서는 github actions를 사용하므로 github actions가 특정 branch에 push가 발생했을 시에 기재한 동작을 수행할 수 있도록 했습니다.&lt;/li&gt;
&lt;li&gt;이미지 Push: kubernetes 환경은 컨테이너로 동작하므로 이미지가 필수적입니다. 여기서 원하는 레지스트리에 빌드한 이미지를 push합니다. 예를 들어서, docker hub나 ecr registry 등이 있습니다.&lt;/li&gt;
&lt;li&gt;Manifest 수정: Manifest는 kubernetes 오브젝트 상세를 kubernetes에 적용시킬 때 필요한 파일입니다. 기본적으로 YAML 형태의 파일들이 있으며, 이를 지원해주는 Kustomize나 Helm을 사용하여 오브젝트를 적용시킬 수 있습니다. 애플리케이션 레포지터리에서 변경된 내용을 토대로 manifest 속 내용을 수정합니다. 예를 들어, 이미지 태그를 수정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Manifest 변경 사항 감지: argocd는  레포지터리의 변경사항을 감지합니다. 수동 혹은 자동으로 변경된 사항을 동기화할 수 있습니다. continuous delivery 도구지만, 자동으로 동기화하는 auto-sync 기능이 있어서 continuous deployment 도구의 성격도 가집니다. Helm을 사용한다면 Helm charts를 토대로 패키지를 적용시킵니다.&lt;/li&gt;
&lt;li&gt;이미지 Pull: 마지막으로 이미지를 pull 받아 디플로이먼트를 생성합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;돈이 없고 m1을 사용하고 있어서 가상환경을 만들기 어려우므로 minikube를 사용하겠습니다.&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;그러면 GitHub 커밋 푸시부터 차근차근 전달드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ github actions workflow 작성 - CI&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번에는 CI로 사용할 도구로 github actions를 선택했습니다. 따로 설치할 필요도 없으며, 가장 간단하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저 workflow는 다음과 같습니다. 깃허브 액션 명세는 .github/workflows 디렉터리에 yaml 확장자로 생성합니다.&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;우리가 수행해야 할 작업들은 jobs 이하에 정리되어 있으며, ci라는 필드 아래에 통합되어 있습니다. 띵스플로우 블로그를 참고하였으며, 버전들과 일부 설정들을 수정했습니다. &lt;a href=&quot;https://thingsflow.com/blog/143&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://thingsflow.com/blog/143&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;name: Build Image And Push an Image to ECR(CI)

on:
  push:
    branches: [ main ]

permissions:
  id-token: write
  contents: write

jobs:
  ci:
    runs-on: ubuntu-latest
    outputs:
      IMAGE_TAG: ${{ steps.set-var.outputs.IMAGE_TAG }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ap-northeast-2
          role-to-assume: ${{ secrets.ARN_ECR_PUSH_ROLE }}
          role-session-name: ecrPrivatePushRole

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Set var
        id: set-var
        run: |
          echo ::set-output name=ECR_REGISTRY::${{ steps.login-ecr.outputs.registry }}
          echo ::set-output name=ECR_REPOSITORY::nodejs-server
          echo ::set-output name=IMAGE_TAG::$(cat VERSION)

      - name: Docker image Build
        id: build-image
        run: |
          docker build \
            -f Dockerfile \
            -t ${{ steps.set-var.outputs.ECR_REGISTRY }}/${{ steps.set-var.outputs.ECR_REPOSITORY }}:${{ steps.set-var.outputs.IMAGE_TAG }} .

      - name: Docker image Push
        id: push-image
        run: |
          docker push ${{ steps.set-var.outputs.ECR_REGISTRY }}/${{ steps.set-var.outputs.ECR_REPOSITORY }}:${{ steps.set-var.outputs.IMAGE_TAG }}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;한 단계 씩 확인해보면 다음과 같습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;runs-on: ubuntu-latest
outputs:
  IMAGE_TAG: ${{ steps.set-var.outputs.IMAGE_TAG }}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Github actions가 작업을 수행할 os로는 ubuntu 최신 버전을 사용합니다. outputs는 변수를 저장하기 위해 생성한 필드입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;- name: Checkout
  uses: actions/checkout@v4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CI를 수행하기 위해 깃허브 레포지터리의 브랜치로 이동한 다음 코드 내용을 받습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-region: ap-northeast-2
    role-to-assume: arn:aws:iam::213360237800:role/Github@ECR_login_and_push_image
    role-session-name: ecrPrivatePushRole&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;aws credential을 저장합니다. 이후에 사용할 aws cli를 위해 필요합니다. IAM 계정의 access key와 secret key를 사용하는 방법도 있지만, 현재는 OIDC를 사용하는 방법을 권장하고 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OIDC&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;OIDC는 OpenID Connect의 약자로 OpenID를 사용하는 프로토콜을 의미합니다. 간단하게 사용자의 신원을 확인할 수 있는 토큰을 발급받는 것입니다. 깃허브 액션에서 사용하는 aws 기밀 정보들은 그 액션 당시에만 잠깐 사용되는 값입니다. 따라서 굳이 access key와 secret 키를 사용할 필요 없이 단기간에 유효한 토큰을 사용하는 것이 더 효율적인 선택이 될 것입니다. 그래서 &lt;span style=&quot;background-color: #ffffff; color: #292c32; text-align: start;&quot;&gt;단기간의 AWS 자격 증명을 얻으려면 GitHub의 OIDC provider를 사용하는 것이 좋습니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32; text-align: start;&quot;&gt;OIDC를 사용하는 경우 이 작업은 워크플로 실행에 고유한 JWT를 만들고 이 JWT를 사용하여 역할을 맡게 됩니다. 여기서 JWT를 만들려면 workflow에 id 토큰에 대한 쓰기 권한이 있어야 합니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;permissions:
  id-token: write
  contents: write&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음으로는 Github OIDC provider를 설정해야 합니다. AWS 콘솔의 IAM 에서 자격 증명 공급자 탭으로 이동하여 공급자 추가를 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3244&quot; data-origin-height=&quot;1042&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bospo4/btsAmATOREe/H5gEO9BCEF2KknyQhJJTO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bospo4/btsAmATOREe/H5gEO9BCEF2KknyQhJJTO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bospo4/btsAmATOREe/H5gEO9BCEF2KknyQhJJTO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbospo4%2FbtsAmATOREe%2FH5gEO9BCEF2KknyQhJJTO0%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;3244&quot; height=&quot;1042&quot; data-origin-width=&quot;3244&quot; data-origin-height=&quot;1042&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;size18&quot;&gt;그 다음 공급자로 Github OIDC 공급자를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lnQBf/btsAmG0J3sN/DOmPzji9IXXyOCBee8eFV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lnQBf/btsAmG0J3sN/DOmPzji9IXXyOCBee8eFV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lnQBf/btsAmG0J3sN/DOmPzji9IXXyOCBee8eFV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlnQBf%2FbtsAmG0J3sN%2FDOmPzji9IXXyOCBee8eFV0%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;1700&quot; height=&quot;1598&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1598&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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공급자 url에는 &lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;https://token.actions.githubusercontent.com&lt;/span&gt;&lt;/b&gt;&amp;nbsp;를 입력하고 대상에는 &lt;span style=&quot;text-align: left;&quot;&gt;&lt;b&gt;sts.amazonaws.com&lt;/b&gt; 를 입력합니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;AWS 정책 생성&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;정책을 생성하여 권한을 지정해줍니다. 저는 ecr에 로그인한 다음, push까지 할 예정이므로, 아래의 권한들이 필요합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700042820596&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;ecr:GetAuthorizationToken&quot;
&quot;ecr:BatchCheckLayerAvailability&quot;
&quot;ecr:CompleteLayerUpload&quot;
&quot;ecr:InitiateLayerUpload&quot;
&quot;ecr:PutImage&quot;
&quot;ecr:UploadLayerPart&quot;&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;size18&quot;&gt;어떻게 알았냐면, market place에서 확인했습니다. &lt;a href=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700042858083&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;Amazon ECR &amp;quot;Login&amp;quot; Action for GitHub Actions - GitHub Marketplace&quot; data-og-description=&quot;Logs in the local Docker client to one or more Amazon ECR Private registries or an Amazon ECR Public registry&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&quot; data-og-url=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/br9H4s/hyUyl8yxZA/WFCpvh1oaR8mLj5rcvE7aK/img.png?width=210&amp;amp;height=210&amp;amp;face=0_0_210_210&quot;&gt;&lt;a href=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/br9H4s/hyUyl8yxZA/WFCpvh1oaR8mLj5rcvE7aK/img.png?width=210&amp;amp;height=210&amp;amp;face=0_0_210_210');&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 ECR &quot;Login&quot; Action for GitHub Actions - GitHub Marketplace&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Logs in the local Docker client to one or more Amazon ECR Private registries or an Amazon ECR Public registry&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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;마켓 플레이스에는 이런 유용한 정보들이 있답니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;서비스로는 Elastic Container Registry를 선택해줍니다.&lt;/span&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;2530&quot; data-origin-height=&quot;1606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qNu26/btsAnyIaszc/YfqpyQLMo4h9bH0FsqIQ9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qNu26/btsAnyIaszc/YfqpyQLMo4h9bH0FsqIQ9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qNu26/btsAnyIaszc/YfqpyQLMo4h9bH0FsqIQ9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqNu26%2FbtsAnyIaszc%2FYfqpyQLMo4h9bH0FsqIQ9K%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;2530&quot; height=&quot;1606&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;역할들을 각각 고르고 리소스는 간단하게 하기 위해 모두를 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;마지막으로 정책 이름과 설명을 작성해줍니다. 저는 &lt;span style=&quot;color: #000000;&quot;&gt;ECR_Login_and_Image_Push_for_Github_Actions로 적었습니다.&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;1958&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BIsVk/btsAorvhnxU/UaWU93tgDeBdHB8ltOJF60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BIsVk/btsAorvhnxU/UaWU93tgDeBdHB8ltOJF60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BIsVk/btsAorvhnxU/UaWU93tgDeBdHB8ltOJF60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBIsVk%2FbtsAorvhnxU%2FUaWU93tgDeBdHB8ltOJF60%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;1958&quot; height=&quot;822&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;AWS 역할 생성&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;다음으로는 역할을 생성합니다. IAM &amp;gt; 역할 &amp;gt; 역할 생성으로 이동합니다. 그 후 아래와 같이 필요한 정보를 작성합니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&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;2594&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qgpBw/btsAm6EW7VI/IGQdQGuqXjFPfbVnhY5kO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qgpBw/btsAm6EW7VI/IGQdQGuqXjFPfbVnhY5kO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qgpBw/btsAm6EW7VI/IGQdQGuqXjFPfbVnhY5kO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqgpBw%2FbtsAm6EW7VI%2FIGQdQGuqXjFPfbVnhY5kO0%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;2594&quot; height=&quot;1624&quot; data-origin-width=&quot;2594&quot; data-origin-height=&quot;1624&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;size18&quot;&gt;자격 증명 공급자와 Audience는 Github OIDC provider를 등록했던 것과 똑같이 입력해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;앞서 생성한 정책을 지정해줍니다. 아니면 AmazonEC2ContainerRegistryPowerUser 정책을 선택해도 앞서 필요했던 역할을 포함하고 있으니 이걸 선택해도 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2184&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9Cbl9/btsAqwCQnf0/hp2r2FMKxtRcQSqbAlA7TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9Cbl9/btsAqwCQnf0/hp2r2FMKxtRcQSqbAlA7TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9Cbl9/btsAqwCQnf0/hp2r2FMKxtRcQSqbAlA7TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9Cbl9%2FbtsAqwCQnf0%2Fhp2r2FMKxtRcQSqbAlA7TK%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;2184&quot; height=&quot;758&quot; data-origin-width=&quot;2184&quot; data-origin-height=&quot;758&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;size18&quot;&gt;역할 이름과 설명을 작성하고, 신뢰 정책까지 검토한 다음 역할 생성을 클릭합니다. 저는 알아보기 쉽게 Github@ECR_login_and_push_image로 지정했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2184&quot; data-origin-height=&quot;1160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chFuvs/btsAm41qbM8/seiZH9LokCBCqYpKiJjLeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chFuvs/btsAm41qbM8/seiZH9LokCBCqYpKiJjLeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chFuvs/btsAm41qbM8/seiZH9LokCBCqYpKiJjLeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchFuvs%2FbtsAm41qbM8%2FseiZH9LokCBCqYpKiJjLeK%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;2184&quot; height=&quot;1160&quot; data-origin-width=&quot;2184&quot; data-origin-height=&quot;1160&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;size18&quot;&gt;여기서 생성한 arn을 복사하여 github repository의 secret 으로 등록해줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RTzoa/btsAqxofqA2/gKTilIrSmSdtsdkhk9i8C0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RTzoa/btsAqxofqA2/gKTilIrSmSdtsdkhk9i8C0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RTzoa/btsAqxofqA2/gKTilIrSmSdtsdkhk9i8C0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRTzoa%2FbtsAqxofqA2%2FgKTilIrSmSdtsdkhk9i8C0%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;2668&quot; height=&quot;534&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;534&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;1622&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctCSxp/btsAnzAkzg5/1JHHJrCPyBTVQ2oil20FSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctCSxp/btsAnzAkzg5/1JHHJrCPyBTVQ2oil20FSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctCSxp/btsAnzAkzg5/1JHHJrCPyBTVQ2oil20FSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctCSxp%2FbtsAnzAkzg5%2F1JHHJrCPyBTVQ2oil20FSk%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;1622&quot; height=&quot;836&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;836&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;size18&quot;&gt;그래서 aws 자격증명 step은 아래와 같이 정리가 됩니다. role-seesion-name은 원하는대로 지정해주면 됩니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-region: ap-northeast-2
    role-to-assume: ${{ secrets.ARN_ECR_PUSH_ROLE }}
    role-session-name: ecrPrivatePushRole&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ECR에 로그인합니다. 로컬에서 도커 허브로 login 하는 것과 같습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;- name: Login to Amazon ECR
  id: login-ecr
  uses: aws-actions/amazon-ecr-login@v2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;변수를 설정합니다. 레지스트리를 찾고, 그 다음 우리가 생성한 레포지터리를 지정한 다음, 이미지 태그를 설정합니다. VERSION은 단순히 SemVer2 버전을 명시한 파일이므로, 원하는 대로 설정해주시면 됩니다. ex) 1.0.0 등&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;- name: Set var
  id: set-var
  run: |
    echo ::set-output name=ECR_REGISTRY::${{ steps.login-ecr.outputs.registry }}
    echo ::set-output name=ECR_REPOSITORY::nodejs-server
    echo ::set-output name=IMAGE_TAG::$(cat VERSION)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;도커 이미지를 빌드합니다. set-var step의 결과물들을 사용해 이미지 태그를 작성해줍니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;- name: Docker image Build
  id: build-image
  run: |
    docker build \
      -f Dockerfile \
      -t ${{ steps.set-var.outputs.ECR_REGISTRY }}/${{ steps.set-var.outputs.ECR_REPOSITORY }}:${{ steps.set-var.outputs.IMAGE_TAG }} .&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이미지를 레지스트리로 push합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;- name: Docker image Push
  id: push-image
  run: |
    docker push ${{ steps.set-var.outputs.ECR_REGISTRY }}/${{ steps.set-var.outputs.ECR_REPOSITORY }}:${{ steps.set-var.outputs.IMAGE_TAG }}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 CI 과정이 대부분 마무리 되었습니다. 여기서 manifest를 수정하는 과정만 추가하면 추가되면 마무리 될 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ Helm 리소스 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev-scratch.tistory.com/177&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev-scratch.tistory.com/177&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700046181444&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Kubernetes/helm] 쿠버네티스의 패키지 관리자 Helm 알아보기&quot; data-og-description=&quot;헬름은 쿠버네티스를 위한 패키지 관리자다. 헬름을 이용하면 여러 유용한 패키지들을 손쉽게 설치할 수 있다. 예를 들어서, metalLB를 helm로도 설치할 수 있다. Kustomize와 비슷한 격이다. 공식 문&quot; data-og-host=&quot;dev-scratch.tistory.com&quot; data-og-source-url=&quot;https://dev-scratch.tistory.com/177&quot; data-og-url=&quot;https://dev-scratch.tistory.com/177&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JIBNk/hyUyx2eoQS/l8NDnOC9lFFv47LTQCS4v0/img.jpg?width=304&amp;amp;height=351&amp;amp;face=0_0_304_351,https://scrap.kakaocdn.net/dn/fiDaI/hyUuX2yM1b/7XkEGEQyBnlb8i8TH4QHlK/img.jpg?width=304&amp;amp;height=351&amp;amp;face=0_0_304_351,https://scrap.kakaocdn.net/dn/cAs3po/hyUyl1Md18/IN4z1yFOxUzm8cvOFxTMs1/img.png?width=1632&amp;amp;height=942&amp;amp;face=0_0_1632_942&quot;&gt;&lt;a href=&quot;https://dev-scratch.tistory.com/177&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev-scratch.tistory.com/177&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JIBNk/hyUyx2eoQS/l8NDnOC9lFFv47LTQCS4v0/img.jpg?width=304&amp;amp;height=351&amp;amp;face=0_0_304_351,https://scrap.kakaocdn.net/dn/fiDaI/hyUuX2yM1b/7XkEGEQyBnlb8i8TH4QHlK/img.jpg?width=304&amp;amp;height=351&amp;amp;face=0_0_304_351,https://scrap.kakaocdn.net/dn/cAs3po/hyUyl1Md18/IN4z1yFOxUzm8cvOFxTMs1/img.png?width=1632&amp;amp;height=942&amp;amp;face=0_0_1632_942');&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;[Kubernetes/helm] 쿠버네티스의 패키지 관리자 Helm 알아보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;헬름은 쿠버네티스를 위한 패키지 관리자다. 헬름을 이용하면 여러 유용한 패키지들을 손쉽게 설치할 수 있다. 예를 들어서, metalLB를 helm로도 설치할 수 있다. Kustomize와 비슷한 격이다. 공식 문&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev-scratch.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;헬름에 대한 내용은 위 링크를 참고해 주세요~&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AppProject 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저 헬름 앱 프로젝트를 생성합니다. 여기서 생성할 프로젝트에 지정할 레포지터리는 미리 생성해 두어야 합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: nodejs
  namespace: argocd
  # Finalizer that ensures that project is not deleted until it is not referenced by any application
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  description: Project for nodejs

  sourceRepos:
    - 'https://github.com/stae1102/argocd-gitops.git'

  destinations:
    - namespace: '*'
      server: https://kubernetes.default.svc

  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;/p&gt;
&lt;p data-ke-size=&quot;size18&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;metadata.finalizers: 애플리케이션을 삭제할 때 사용할 finalizer. 굳이 지정하지 않아도 되지만, App of Apps 패턴을 사용할 때 계단식으로 삭제할 수 있어 유용합니다.&lt;/li&gt;
&lt;li&gt;spec.description: 프로젝트 설명&lt;/li&gt;
&lt;li&gt;spec.sourceRepos: 프로젝트에서 사용할 레포지터리. 리스트 형태로 원하는 레포지터리를 지정합니다. 애스터리스크를 사용해 모든 레포지터리를 지정할 수도 있고, !(느낌표)를 붙여서 레포지터리를 제외할 수도 있습니다.&lt;/li&gt;
&lt;li&gt;spec.destinations: argocd가 동기화할 클러스터.&lt;/li&gt;
&lt;li&gt;spec.clusterResourceWhitelist: argocd가 동기화할 클러스터 리소스. 종류에 상관 없이 모두 지정하고자 애스터리스크를 사용했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쿠버네티스에 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700047955412&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f 앱프로젝트.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Application 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 다음 실질적으로 사용할 애플리케이션을 생성합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nodejs-server
  namespace: argocd
spec:
  destination:
    namespace: default
    server: 'https://kubernetes.default.svc'
  project: nodejs
  source:
    repoURL: https://github.com/stae1102/argocd-gitops.git
    targetRevision: HEAD
    path: nodejs-server
    helm:
      valueFiles:
        - values.yaml
  syncPolicy:
    automated:
      prune: true
    syncOptions:
      - Validate=false
      - CreateNamespace=true
      - PrunePropagationPolicy=background&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spec.destination: 위와 같습니다.&lt;/li&gt;
&lt;li&gt;spec.projects: 등록될 프로젝트. 앞서 생성한 프로젝트의 metadata.name과 같이 지정하여 그룹화합니다.&lt;/li&gt;
&lt;li&gt;spec.source: 애플리케이션이 사용할 소스. 레포지터리 URL, 리비전, 애플리케이션이 담긴 디렉터리 주소, 헬름이 사용할 value 파일 주소까지 작성합니다.&lt;/li&gt;
&lt;li&gt;spec.syncPolicy: 동기화 정책. 자동으로 동기화를 하려면 automated.prune을 true로 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쿠버네티스에 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700048264008&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f 애플리케이션.yaml&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;size18&quot;&gt;이렇게 모두 완료가 되었으면, 애플리케이션의 helm chart를 만들어줍니다. 저는 배포 레포지터리의 nodejs-server 디렉터리에 차트를 생성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700048351320&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.
├── Chart.lock
├── Chart.yaml
├── templates
│&amp;nbsp;&amp;nbsp; ├── NOTES.txt
│&amp;nbsp;&amp;nbsp; ├── _helpers.tpl
│&amp;nbsp;&amp;nbsp; ├── deployment.yaml
│&amp;nbsp;&amp;nbsp; ├── hpa.yaml
│&amp;nbsp;&amp;nbsp; ├── ingress.yaml
│&amp;nbsp;&amp;nbsp; ├── sealed-secret.yaml
│&amp;nbsp;&amp;nbsp; ├── secrets.yaml # git ignore
│&amp;nbsp;&amp;nbsp; ├── service.yaml
│&amp;nbsp;&amp;nbsp; ├── serviceaccount.yaml
│&amp;nbsp;&amp;nbsp; └── tests
│&amp;nbsp;&amp;nbsp;     └── test-connection.yaml
└── values.yaml&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;size18&quot;&gt;먼저 values.yaml을 확인하겠습니다. 간단하게 배포할 목적이므로 최대한 간결하게 작성했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# Default values for nodejs-server.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1
imagePullSecrets:
  - name: ecr-regcred
nameOverride: &quot;&quot;
fullnameOverride: &quot;&quot;

image:
  repository: 213360237800.dkr.ecr.ap-northeast-2.amazonaws.com/nodejs-server
  tag: &quot;1.0.0&quot;
  
serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Automatically mount a ServiceAccount's API credentials?
  automount: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template
  name: &quot;&quot;
  
podAnnotations: {}
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000

securityContext: {}
# capabilities:
#   drop:
#   - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000

service:
  type: LoadBalancer
  port: 3000
ingress:
  enabled: false
  className: &quot;&quot;
  annotations: {}
  # kubernetes.io/ingress.class: nginx
  # kubernetes.io/tls-acme: &quot;true&quot;
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
#   cpu: 100m
#   memory: 128Mi
# requests:
#   cpu: 100m
#   memory: 128Mi

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100
  targetCPUUtilizationPercentage: 80
  # targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
#   secret:
#     secretName: mysecret
#     optional: false

# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
#   mountPath: &quot;/etc/foo&quot;
#   readOnly: true

nodeSelector: {}
tolerations: []
affinity: {}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;replicaCount: 1
imagePullSecrets:
  - name: ecr-regcred

image:
  repository: 213360237800.dkr.ecr.ap-northeast-2.amazonaws.com/nodejs-server
  tag: &quot;1.0.0&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;repository는 ecr 레포지터리를 지정해줍니다. tag는 manifest를 수정하면서 같이 수정되므로 크게 신경쓸 필요 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기서 레지스트리가 private이라면, 그에 상응하는 권한을 가지고 있어야 합니다. 그렇기 때문에 imagePullSecrets에 secret을 지정해주도록 합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl create secret docker-registry ecr-regcred \
  --dry-run=client \
  --docker-server=&quot;$ECR_REGISTRY_URI&quot; \
  --docker-username=AWS \
  --docker-password=&quot;$(aws ecr get-login-password --region ap-northeast-2)&quot; \
  --namespace=default \
  -o yaml | kubectl apply -f -&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;시크릿을 생성한 다음 쿠버네티스에 적용하는 코드입니다. 이는 로컬 터미널에서 수행될 예정이며, aws cli에서 ecr에 로그인하고 pull할 권한이 있는 유저로 설정이 되어 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ECR 레지스트리 URI를 외부에서 받아오기 위해 스크립트를 작성했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;#!/bin/bash

ECR_REPOSITORY_NAME=&quot;&quot;

while getopts &quot;r:&quot; opt; do
  case $opt in
    r)
      ECR_REPOSITORY_NAME=&quot;$OPTARG&quot;
      ;;
    \?)
      echo &quot;잘못된 옵션: -$OPTARG&quot; &amp;gt;&amp;amp;2
      exit 1
      ;;
  esac
done

if [ -z &quot;$ECR_REPOSITORY_NAME&quot; ]; then
  echo &quot;ECR_REGISTRY_NAME이 지정되지 않았습니다.&quot; &amp;gt;&amp;amp;2
  exit 1
fi

ECR_REGISTRY_URI=$(aws ecr describe-repositories --repository-names &quot;$ECR_REPOSITORY_NAME&quot; --query 'repositories[0].repositoryUri' --output text)

if [ -z &quot;$ECR_REGISTRY_URI&quot; ]; then
  echo &quot;ECR 레지스트리 URI를 가져올 수 없습니다.&quot; &amp;gt;&amp;amp;2
  exit 1
fi

kubectl create secret docker-registry ecr-regcred \
  --dry-run=client \
  --docker-server=&quot;$ECR_REGISTRY_URI&quot; \
  --docker-username=AWS \
  --docker-password=&quot;$(aws ecr get-login-password --region ap-northeast-2)&quot; \
  --namespace=default \
  -o yaml | kubectl apply -f -&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;레포지터리 이름을 받아서 registry uri를 가져오는 스크립트입니다. 이제 이미지를 받아올 때, 해당 시크릿을 통해 프라이빗 레지스트리에 접근할 수 있게 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1700049150696&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./add_registry_secret.sh -r 레포지터리_이름&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sealed-Secret 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;애플리케이션을 배포할 때, 환경변수를 사용하여 비밀번호나 토큰의 시크릿값, SSO 설정 등의 민감한 정보를 가져옵니다. 이때, 주로 사용되는 것이 ConfigMap과 Secret입니다. ConfigMap보다는 Secret이 조금 더 안전한 오브젝트인데, 여전히 Secret은 base64로 인코딩이 되므로 레포지터리에 그대로 올릴 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 이 문제를 해결하고자 SealedSecret이 등장했습니다.&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;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qAA5w/btsAmtglVQk/J18IxLTQpJyJT76bgL9rD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qAA5w/btsAmtglVQk/J18IxLTQpJyJT76bgL9rD1/img.png&quot; data-alt=&quot;출처: https://docs.bitnami.com/tutorials/sealed-secrets&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qAA5w/btsAmtglVQk/J18IxLTQpJyJT76bgL9rD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqAA5w%2FbtsAmtglVQk%2FJ18IxLTQpJyJT76bgL9rD1%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;669&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;669&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://docs.bitnami.com/tutorials/sealed-secrets&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1696&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OluSa/btsArevdb5e/7xjPfHxTVcFhoQk6OLt3U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OluSa/btsArevdb5e/7xjPfHxTVcFhoQk6OLt3U0/img.png&quot; data-alt=&quot;출처:&amp;amp;nbsp;https://github.com/bitnami-labs/sealed-secrets&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OluSa/btsArevdb5e/7xjPfHxTVcFhoQk6OLt3U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOluSa%2FbtsArevdb5e%2F7xjPfHxTVcFhoQk6OLt3U0%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;1696&quot; height=&quot;272&quot; data-origin-width=&quot;1696&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;nbsp;https://github.com/bitnami-labs/sealed-secrets&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;sealed-secret 차트에서 확인했을 때, 이렇게 설명이 되어 있네요. 즉, Secret을 제외한 모든 쿠버네티스 설정을 git으로 관리하고자 할 때 사용할 수 있는 솔루션입니다. 시크릿을 SealedSecret으로 암호화하여 레포지터리에 안전하게 올릴 수 있으며, 이 시크릿은 클러스터에서 동작하는 컨트롤러만 복호화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;SealedSecret은 helm chart를 통해 쉽게 설치할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700049698334&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add sealed-secret https://bitnami-labs.github.io/sealed-secrets/
&quot;sealed-secret&quot; has been added to your repositories&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1700049950568&quot; class=&quot;lsl&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ helm search repo sealed
NAME                        	CHART VERSION	APP VERSION	DESCRIPTION
bitnami/sealed-secrets      	1.5.12       	0.24.3     	Sealed Secrets are &quot;one-way&quot; encrypted K8s Secr...
sealed-secret/sealed-secrets	2.13.2       	v0.24.3    	Helm chart for the sealed-secrets controller.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1700049895134&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ helm install sealed-secret sealed-secret/sealed-secrets --namespace=kube-system
NAME: sealed-secret
LAST DEPLOYED: Mon Nov 13 22:12:26 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **

You should now be able to create sealed secrets.

1. Install the client-side tool (kubeseal) as explained in the docs below:

    https://github.com/bitnami-labs/sealed-secrets#installation-from-source

2. Create a sealed secret file running the command below:

    kubectl create secret generic secret-name --dry-run=client --from-literal=foo=bar -o [json|yaml] | \
    kubeseal \
      --controller-name=sealed-secret-sealed-secrets \
      --controller-namespace=kube-system \
      --format yaml &amp;gt; mysealedsecret.[json|yaml]

The file mysealedsecret.[json|yaml] is a commitable file.

If you would rather not need access to the cluster to generate the sealed secret you can run:

    kubeseal \
      --controller-name=sealed-secret-sealed-secrets \
      --controller-namespace=kube-system \
      --fetch-cert &amp;gt; mycert.pem

to retrieve the public cert used for encryption and store it locally. You can then run 'kubeseal --cert mycert.pem' instead to use the local cert e.g.

    kubectl create secret generic secret-name --dry-run=client --from-literal=foo=bar -o [json|yaml] | \
    kubeseal \
      --controller-name=sealed-secret-sealed-secrets \
      --controller-namespace=kube-system \
      --format [json|yaml] --cert mycert.pem &amp;gt; mysealedsecret.[json|yaml]

3. Apply the sealed secret

    kubectl create -f mysealedsecret.[json|yaml]

Running 'kubectl get secret secret-name -o [json|yaml]' will show the decrypted secret that was generated from the sealed secret.

Both the SealedSecret and generated Secret must have the same name and namespace.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 SealedSecret을 설치해준 다음 kubeseal로 SealedSecret 상세 파일을 생성할 수 있습니다. 해당 차트에서도 추천하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아래 링크에서 kubeseal 설치방법을 확인하여 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://github.com/bitnami-labs/sealed-secrets#kubeseal&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/bitnami-labs/sealed-secrets#kubeseal&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700049798567&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 - bitnami-labs/sealed-secrets: A Kubernetes controller and tool for one-way encrypted Secrets&quot; data-og-description=&quot;A Kubernetes controller and tool for one-way encrypted Secrets - GitHub - bitnami-labs/sealed-secrets: A Kubernetes controller and tool for one-way encrypted Secrets&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/bitnami-labs/sealed-secrets#kubeseal&quot; data-og-url=&quot;https://github.com/bitnami-labs/sealed-secrets&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cleTkl/hyUuVjopsn/ZZYlTqM72GSqVnH6X6Zkz0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/bitnami-labs/sealed-secrets#kubeseal&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/bitnami-labs/sealed-secrets#kubeseal&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cleTkl/hyUuVjopsn/ZZYlTqM72GSqVnH6X6Zkz0/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 - bitnami-labs/sealed-secrets: A Kubernetes controller and tool for one-way encrypted Secrets&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Kubernetes controller and tool for one-way encrypted Secrets - GitHub - bitnami-labs/sealed-secrets: A Kubernetes controller and tool for one-way encrypted Secrets&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;size18&quot;&gt;이제 아래 명령어를 입력해 줍니다. 여기서 controller-name은 sealed-secret을 설치할 때 사용한 릴리즈 이름입니다. secrets.yaml은 암호화할 시크릿입니다. sealed-secert.yaml은 암호화된 시크릿입니다. 둘 다 원하는 대로 설정해주시면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700049834319&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubeseal \
      --controller-name=sealed-secret-sealed-secrets \
      --controller-namespace=kube-system \
	  --format yaml &amp;lt; secrets.yaml &amp;gt; sealed-secret.yaml&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;size18&quot;&gt;그러면 아래와 같이 SealedSecret이 생성됩니다. base64로 인코딩했을 때보다 확실히 더 복잡해졌네요.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: mysql-secret
  namespace: default
spec:
  encryptedData:
    DB_HOST: AgCaTFN9AEH/igbDpH7oeVW+G+Ia8JAMDCdQ4lqNDaK9drVvBMJzEMRe5TttKgU0cV8nKqW7+dgE4Cpc24yV8jiiTHAQmdWToBPPqL4exd3yHyjRxFjd9mhcbAbkaoD5mgMdiSg/gX7aX+uOPmWJSEXU4mlTOeNgVlc3XjZ/my/MjcdrVmw8Xh8vKUgqZfGtFSiLuLeYJxN8WZSzx37lzqHz7pH+av26BqXiwkee/5zxcnqEGxM8O08Khw0/M5gLIx7Dz2chVfkxnmEgUYZZ5uqsckaRwlhVFeH4TH9Aw2yQNCy1CM6xCxd5z6CP9m8VyXxWa2fAh3kFsy05rIk4C8YT70YlvNmut5dhVCQmMYQZ0PT37xdGXiqWcKTwEBfUNuUl6WniXvXEYh6nGJs+1wi+1COft2KzxZDPTTN35S/Jlh6JDaMjMBhmiyfXue5JWf7O2xLM4ChLd58NniI6hl7GV1yZqNWK4e3cvme5866YuoTrxnDb+DHG/wlOt839yadjV8EFyst+mXi8Yv11EIQK9ndTTULzqzWpK2s51cubB5xrnLK2lP+R7wEBCIfsurtNalPQojlh27yns+URvK1OhMzFwdylf6YXOCoD853AGJn0LOGWNKKkD4aHTfq1GZtUcm74ZtkLkuz8iAT86fVJPvx8+GyAPDWybmf8xwbuwAHbg5EUzn5AV3EN3FaUchJKPuUWlrT2zQ==
    DB_NAME: AgAH3Udxzg3IIqwcJsBMBzNi/8Mud2XgLBxB0zyKQIq+atYdBjFtbBFt6yMcDzNrL/iMqsS6L7E5Dh7f1UiHmpnOPPat1Mbs/47yfEe7Ua2OKhZ/r2aerMFA8E3hSY4tonB0ZtuQSdr+v7jZjpOsMVRfJwYZWGOaUhgZpl2G0FAWfDB27ycFrzDQmbMaRRBqnxfc1OLHROT5Etj3xf9jBbFqrZa7VsgJ2aOGgpXDdCbfuYhhWom9gNZRP3I8TVBns7Ka7QiO04RcYJK9ZaoRVb5frO+G4oTk8t7Fzk3lv+7nLzvfybmWzTGYr1KtWNeX4CxYFrJvauzlj2mmrvWZA3LtOO/sC/LRIXZNqE7O1kCJPuMlFs+EqrceVCVdCAycgopKE7iNwsDTGaNobdz/frvrbhLq/xxiElDnMAam1aMJORII4FeKfuzLBc47JRSQy9MeFpotP3BebxH/DjRJJaxPxRtDBwW3V/ULRRNcu5bcK+e0ffuU7QkAquhGHzFrf+CO1cV2O5YD746QIhkrUg+fy3wPblSo5/Ik07tbsSvALFxLXUVJzEkFsxoIFbUofI6x2TdyKf9fNNpx2LBvP/HzBSxwWLgaM4bemhH6M/mLYPaHwPIUZvEb1egMiW0jSGmWOt0K9IXVL1XveVqi9hubZXpnDv6emwy53fGUePvK89mAe0N0RyphJI1PZFP99RJOKg3OKEg=
    DB_PASSWORD: AgBI/eFBJ14W0XkM4MDbuqqvLEH5fd7GazbTsBy8EDN3OpkJV1IITOGfKVudLlTVUUYDA2hV0cejwXPyy3yCXuWGPXjDnSXb0k+qsqe40f/UE2fs3+xWzDdNC9kz10BQfMYhwlOWax9zaU/4o0mxq3C/Cw1HVmWnRSE44GvSYj5PwQI6/aM66pgl2DXLoscO+RWNru34tMWItre2/crAn5Vgx0Jc8CgZDyCENZgM2C4sOzU6IxIT0bISPVsUhD/e7KyGhko51nIxWBSgRFf0h+SHpAzQap1nIDisqVc8Q1DPwdL+lhwF4Tcrd1KapiGQDXKmmnl4SZxXU8beAMYNnOHo9nzt2+4zMekYN3Jorq959pL56//yeXwrHsxN6sC9qYhX7yYjbxFDF4/M50JWB7GrIRGudwMfEsxKLkKjHDc95YB+VDP8TV3Gm4JITcBb7X4j/qhUwUjCyxcpbiTQrdnHgUCbSxIt8QXd68dv07NMZXIsAwUFLx5/oZB1aM9AGNRwySfZkI7BmyPdf6T3F4Q8V4hsHTA/IPj4jVLE/XTWGr+cnOHt8KSNShjQAJO+eE+RpCNY1yquFWznuG1MeSrrIW8x2eE7+M8SEGm5Fp4ug5yBjdBNzW4QAcVT87jk8Yw29EOWIxdipQuCRPSkBMa3VT7ZCW6cMgFvI0KwCWG+1WPkfxeNVJm7fm7IMN4P2a6rnxgYAKxiK0BPqOO4
    DB_PORT: AgCNTtfaW1r0E6oVTCCFPoSaTZkSYSDFwyILft36DlFsFpneVZjIvf3LEtTEVuPsZvq4J0fpjiXLAhJgoFlqUsDa/3wwmM+CwZ1H9BinpPFj2IOF14F6IAdj0UbStqTYCRc0BVqeMfTRJnJ39SC0GbjY5uGOn8KN9qXw8BvQwfrllZSBuiJd+IO6Sm8dMQKBWUkcbK62cGd1N9X/XLz6qnjizrh6FJxFX7D1LLqi1nCFc6ub1P76zOcP+9RemABI1nj6lu55IVtvpMh0QlSjBtr952nrDtzgub6UGWSduHUkp/z/KtM4WhmIjgHNd2KfSU2xtC4TFBaf1IXfoRsMJTlLpPQ+w5jZgKnTLzcaVolt1TzY8YgdAD+hdJiozRDRDOV8z1DXbYc2ZR7ci8zf+qsfwHQ4myTp+NllYkMBUJJV9oQx6ztrkfE1NAll6Oaz/EQ4sUUD3LQ+4cRjpzY4HVig0gejrS28dN5IRqWwm/G2tNdTrLLcOaySaI3bvvdmLBwY9pXRLa/OekYlOGCqzQpKixpm5N2IqjI2IFAmfUs0YWQL21IMsJL85vJ8Oar4uzuMz1Q4Unf4PWKSGjE4PQ4oK4mdL4pqKtlpBqaj0rSa52GbuY68oEADtysOIx4d0RUXuNJYE9+IYIpFhJMPK6k77PO8PTujAkZ/WMLV8jcmnErGJX1npaQh6/I6zMCYz9QMRbzu
    DB_USERNAME: AgDLyTHrQL0WUWFkVHYtolWr0TEv4+nl/82gb5XKhLSypqbFlfNCkFMg6tUlSH5dscQFIWroX5cKZh3Gp8tjSLpJTZoCK+6Tzb6ZSXmglmp1dkaaWBeqqL3ITJ3IvAEHaYv2nTFEyeNUcHS0utxm9XTw/a9nDAfz3m5Qi90xAVIhXKX8EyNsxX+y2ln5RnkubAo5RM6KzklXLngGhVcKX9zJWrOIWVHuu7haWsxqqtqn7SYNDP+NKlEEP5qOaJumCwc6W7yMLrYvHio0pmy/QUfD2YVoqCoGaUCKGZdQbc/4ZE08evYpvGI7l1HCLfuE+cj9XTn2GFZxbeh48uqFi+Sz+oYZahCCHT2XBqfTFy0s7ncZpCX9/GbV+0MhwHxzQiJsvrb+VR6k63k/Eb+xVQlSO1J8B2ncZSUi/C/eawO0m4YFmcTC0HmDylygOdWbGqLE6fvJI+SNTbE+rXSDrCtG/0/ARj/bFCr2WdDVErPjJ7ZjOqILGdyoCnh/tj+M1LQByCp/JZdA8loMC7B51fu0QK7jkLwsg1MzjPqkoEeWiHH1LkJYHzpgoSptRrP3HoJ96Laq6zgl/24I4cLZhS1A4u5N2RqpIjNufkM3GYVRBqYNoYofBe+3dMeUicgFeU/ub8r1L9X/ktx56gAdnZG6iyi1m62JwhihmnaDOYwOgXvJiVcKqqTMTVfI+nw+KtOTtYvzHWDxWb0=
  template:
    metadata:
      creationTimestamp: null
      name: mysql-secret
      namespace: default
    type: Opaque
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애플리케이션에서 SealedSecret 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;디플로이먼트에서 SealedSecret을 사용할 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;...
containers:
  - name: {{ .Chart.Name }}
    securityContext:
      {{- toYaml .Values.securityContext | nindent 12 }}
    image: &quot;{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}&quot;
    imagePullPolicy: {{ .Values.image.pullPolicy }}
    ports:
      - name: http
        containerPort: {{ .Values.service.port }}
        protocol: TCP
    livenessProbe:
      httpGet:
        path: /
        port: http
    readinessProbe:
      httpGet:
        path: /
        port: http
    resources:
      {{- toYaml .Values.resources | nindent 12 }}
    {{- with .Values.volumeMounts }}
    volumeMounts:
      {{- toYaml . | nindent 12 }}
    {{- end }}
    envFrom:
      - secretRef:
          name: mysql-secret
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;envFrom 필드에 secretRef name 값으로 SealedSecret name을 지정해주면 끝입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ github actions workflow 작성 - CD&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CI 이후 사용할 job입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cd:
  needs: [ ci ]
  runs-on: ubuntu-latest
  steps:
    - name: Checkout Private Repository
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
        repository: stae1102/argocd-gitops
        ref: main
        token: ${{ secrets.PAT }}

    - name: Replace image tag in helm values (LOCAL)
      uses: mikefarah/yq@master
      env:
        IMAGE_TAG: ${{ needs.ci.outputs.IMAGE_TAG }}
      with:
        cmd: yq eval -i '.image.tag = env(IMAGE_TAG)' 'nodejs-server/values.yaml'

    - name: Commit helm chart changes
      env:
        IMAGE_TAG: ${{ needs.ci.outputs.IMAGE_TAG }}
      run: |
        cd nodejs-server
        git config --global user.email &quot;stae1102@gmail.com&quot;
        git config --global user.name &quot;stae1102&quot;

        git add values.yaml
        git commit --message &quot;ci: update nodejs-server image tag to $IMAGE_TAG&quot;

    - name: Push commit
      uses: ad-m/github-push-action@master
      with:
        github_token: ${{ secrets.PAT }}
        repository: stae1102/argocd-gitops&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;각 step은 다음과 같습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;- name: Checkout Private Repository
  uses: actions/checkout@v4
  with:
    fetch-depth: 0
    repository: stae1102/argocd-gitops
    ref: main
    token: ${{ secrets.PAT }}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다른 레포지터리에 checkout 하기 위해 토큰을 사용합니다. 토큰에는 github public access token이 사용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l9Epy/btsAmrwaY4e/QJf7fXep1a1Kgknkn6GISK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l9Epy/btsAmrwaY4e/QJf7fXep1a1Kgknkn6GISK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l9Epy/btsAmrwaY4e/QJf7fXep1a1Kgknkn6GISK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl9Epy%2FbtsAmrwaY4e%2FQJf7fXep1a1Kgknkn6GISK%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;1558&quot; height=&quot;374&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;374&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;size18&quot;&gt;생성한 token은 github repository의 secret으로 등록해줍니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;- name: Replace image tag in helm values (LOCAL)
  uses: mikefarah/yq@master
  env:
    IMAGE_TAG: ${{ needs.ci.outputs.IMAGE_TAG }}
  with:
    cmd: yq eval -i '.image.tag = env(IMAGE_TAG)' 'nodejs-server/values.yaml'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이미지 태그를 수정합니다. 경로는 내가 바꿀 helm chart의 values.yaml 경로를 지정해주어야 합니다. 저의 경우, nodejs-server 차트의 values.yaml로 지정했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;- name: Commit helm chart changes
  env:
    IMAGE_TAG: ${{ needs.ci.outputs.IMAGE_TAG }}
  run: |
    cd nodejs-server
    git config --global user.email &quot;stae1102@gmail.com&quot;
    git config --global user.name &quot;stae1102&quot;

    git add values.yaml
    git commit --message &quot;ci: update nodejs-server image tag to $IMAGE_TAG&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;- name: Push commit
  uses: ad-m/github-push-action@master
  with:
    github_token: ${{ secrets.PAT }}
    repository: stae1102/argocd-gitops&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;커밋된 내용을 푸시합니다. 여기도 마찬가지로 token을 지정해줍니다. 아니면, Commit helm chart changes 스텝에서 git push까지 해줘도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;3288&quot; data-origin-height=&quot;1532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bndjEq/btsAp3gGBC8/f9qhF4kIQWGQVVBhChbRk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bndjEq/btsAp3gGBC8/f9qhF4kIQWGQVVBhChbRk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bndjEq/btsAp3gGBC8/f9qhF4kIQWGQVVBhChbRk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbndjEq%2FbtsAp3gGBC8%2Ff9qhF4kIQWGQVVBhChbRk1%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;3288&quot; height=&quot;1532&quot; data-origin-width=&quot;3288&quot; data-origin-height=&quot;1532&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;2434&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca1S25/btsArfgBjbn/cd8wG7PrFvjkiLB6JleD5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca1S25/btsArfgBjbn/cd8wG7PrFvjkiLB6JleD5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca1S25/btsArfgBjbn/cd8wG7PrFvjkiLB6JleD5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca1S25%2FbtsArfgBjbn%2Fcd8wG7PrFvjkiLB6JleD5k%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;2434&quot; height=&quot;168&quot; data-origin-width=&quot;2434&quot; data-origin-height=&quot;168&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;2876&quot; data-origin-height=&quot;1772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZTfPP/btsAqBjWqn8/FsRSKYwDNYokNXwmHde2Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZTfPP/btsAqBjWqn8/FsRSKYwDNYokNXwmHde2Zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZTfPP/btsAqBjWqn8/FsRSKYwDNYokNXwmHde2Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZTfPP%2FbtsAqBjWqn8%2FFsRSKYwDNYokNXwmHde2Zk%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;2876&quot; height=&quot;1772&quot; data-origin-width=&quot;2876&quot; data-origin-height=&quot;1772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;깃허브 액션의 초록불은 언제봐도 절 설레게 합니다. 완벽한 파이프라인 구현은 아니겠지만, 대강 이런 식으로 애플리케이션이 배포가 되는 것을 확인했으며, 행동으로 실천해보니 아주 뿌듯합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음에는 ci 툴을 바꿔보고 파이프라인을 구성해보고자 합니다.&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;a href=&quot;https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions#oidc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions#oidc&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700054994495&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;&amp;quot;Configure AWS Credentials&amp;quot; Action for GitHub Actions - GitHub Marketplace&quot; data-og-description=&quot;Configures AWS credentials for use in subsequent steps in a GitHub Action workflow&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions#oidc&quot; data-og-url=&quot;https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CW1SN/hyUu2izcIA/EsKmDv6ifjfjAoKAvQNwi1/img.png?width=210&amp;amp;height=210&amp;amp;face=0_0_210_210&quot;&gt;&lt;a href=&quot;https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions#oidc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions#oidc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CW1SN/hyUu2izcIA/EsKmDv6ifjfjAoKAvQNwi1/img.png?width=210&amp;amp;height=210&amp;amp;face=0_0_210_210');&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;&quot;Configure AWS Credentials&quot; Action for GitHub Actions - GitHub Marketplace&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Configures AWS credentials for use in subsequent steps in a GitHub Action workflow&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;a href=&quot;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700055011243&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Configuring OpenID Connect in Amazon Web Services - GitHub Docs&quot; data-og-description=&quot;Use OpenID Connect within your workflows to authenticate with Amazon Web Services.&quot; data-og-host=&quot;docs.github.com&quot; data-og-source-url=&quot;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws&quot; data-og-url=&quot;https://ghdocs-prod.azurewebsites.net/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVkHdi/hyUuRagdU8/n022IcpIPj7qChj28FhRh0/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200&quot;&gt;&lt;a href=&quot;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVkHdi/hyUuRagdU8/n022IcpIPj7qChj28FhRh0/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200');&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;Configuring OpenID Connect in Amazon Web Services - GitHub Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Use OpenID Connect within your workflows to authenticate with Amazon Web Services.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700055021174&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;Amazon ECR &amp;quot;Login&amp;quot; Action for GitHub Actions - GitHub Marketplace&quot; data-og-description=&quot;Logs in the local Docker client to one or more Amazon ECR Private registries or an Amazon ECR Public registry&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&quot; data-og-url=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/br9H4s/hyUyl8yxZA/WFCpvh1oaR8mLj5rcvE7aK/img.png?width=210&amp;amp;height=210&amp;amp;face=0_0_210_210&quot;&gt;&lt;a href=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/br9H4s/hyUyl8yxZA/WFCpvh1oaR8mLj5rcvE7aK/img.png?width=210&amp;amp;height=210&amp;amp;face=0_0_210_210');&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 ECR &quot;Login&quot; Action for GitHub Actions - GitHub Marketplace&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Logs in the local Docker client to one or more Amazon ECR Private registries or an Amazon ECR Public registry&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;a href=&quot;https://thingsflow.com/blog/143&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://thingsflow.com/blog/143&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700055028280&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;thingsflow&quot; data-og-description=&quot;Creator economy with AI tech&quot; data-og-host=&quot;thingsflow.com&quot; data-og-source-url=&quot;https://thingsflow.com/blog/143&quot; data-og-url=&quot;https://thingsflow.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hfFCa/hyUuWvOX8n/hP7MzzV4p2z5qoSsBTKRAK/img.png?width=400&amp;amp;height=210&amp;amp;face=0_0_400_210,https://scrap.kakaocdn.net/dn/bQ4ouE/hyUuX2yuM6/BZTf7kzr5jd2dasLj3ida1/img.jpg?width=4976&amp;amp;height=3318&amp;amp;face=0_0_4976_3318,https://scrap.kakaocdn.net/dn/CP4RS/hyUuY8fDCm/lFW2Kxe0nBtHrtyV517o5K/img.png?width=720&amp;amp;height=900&amp;amp;face=0_0_720_900&quot;&gt;&lt;a href=&quot;https://thingsflow.com/blog/143&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://thingsflow.com/blog/143&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hfFCa/hyUuWvOX8n/hP7MzzV4p2z5qoSsBTKRAK/img.png?width=400&amp;amp;height=210&amp;amp;face=0_0_400_210,https://scrap.kakaocdn.net/dn/bQ4ouE/hyUuX2yuM6/BZTf7kzr5jd2dasLj3ida1/img.jpg?width=4976&amp;amp;height=3318&amp;amp;face=0_0_4976_3318,https://scrap.kakaocdn.net/dn/CP4RS/hyUuY8fDCm/lFW2Kxe0nBtHrtyV517o5K/img.png?width=720&amp;amp;height=900&amp;amp;face=0_0_720_900');&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;thingsflow&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Creator economy with AI tech&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;thingsflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@jerome.decoster/argocd-image-updater-56cd94651393&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@jerome.decoster/argocd-image-updater-56cd94651393&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700055096926&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;ArgoCD + Image Updater&quot; data-og-description=&quot;Playing with ArgoCD Image Updater and public and private repositories.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@jerome.decoster/argocd-image-updater-56cd94651393&quot; data-og-url=&quot;https://medium.com/@jerome.decoster/argocd-image-updater-56cd94651393&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bYHthE/hyUu5M8xwI/CU29iHrJBerKtOWN9WOwok/img.png?width=755&amp;amp;height=475&amp;amp;face=0_0_755_475&quot;&gt;&lt;a href=&quot;https://medium.com/@jerome.decoster/argocd-image-updater-56cd94651393&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@jerome.decoster/argocd-image-updater-56cd94651393&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bYHthE/hyUu5M8xwI/CU29iHrJBerKtOWN9WOwok/img.png?width=755&amp;amp;height=475&amp;amp;face=0_0_755_475');&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;ArgoCD + Image Updater&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Playing with ArgoCD Image Updater and public and private repositories.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#managing-projects&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#managing-projects&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700055132457&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;Projects - Argo CD - Declarative GitOps CD for Kubernetes&quot; data-og-description=&quot;Projects Projects Projects provide a logical grouping of applications, which is useful when Argo CD is used by multiple teams. Projects provide the following features: restrict what may be deployed (trusted Git source repositories) restrict where apps may &quot; data-og-host=&quot;argo-cd.readthedocs.io&quot; data-og-source-url=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#managing-projects&quot; data-og-url=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#managing-projects&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#managing-projects&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#managing-projects&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;Projects - Argo CD - Declarative GitOps CD for Kubernetes&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Projects Projects Projects provide a logical grouping of applications, which is useful when Argo CD is used by multiple teams. Projects provide the following features: restrict what may be deployed (trusted Git source repositories) restrict where apps may&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;argo-cd.readthedocs.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700055144225&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;App Deletion - Argo CD - Declarative GitOps CD for Kubernetes&quot; data-og-description=&quot;App Deletion Apps can be deleted with or without a cascade option. A cascade delete, deletes both the app and its resources, rather than only the app. Deletion Using argocd To perform a non-cascade delete: argocd app delete APPNAME --cascade=false To perfo&quot; data-og-host=&quot;argo-cd.readthedocs.io&quot; data-og-source-url=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/&quot; data-og-url=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/&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;App Deletion - Argo CD - Declarative GitOps CD for Kubernetes&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;App Deletion Apps can be deleted with or without a cascade option. A cascade delete, deletes both the app and its resources, rather than only the app. Deletion Using argocd To perform a non-cascade delete: argocd app delete APPNAME --cascade=false To perfo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;argo-cd.readthedocs.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://levelup.gitconnected.com/from-local-development-to-kubernetes-cluster-helm-https-ci-cd-gitops-kustomize-argocd-5d9de6f5364c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://levelup.gitconnected.com/from-local-development-to-kubernetes-cluster-helm-https-ci-cd-gitops-kustomize-argocd-5d9de6f5364c&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700055202496&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;From local development to Kubernetes &amp;mdash; Cluster, Helm, HTTPS, CI/CD, GitOps, Kustomize, ArgoCD &amp;mdash;&amp;hellip;&quot; data-og-description=&quot;This is the second part of the series: From local development to Kubernetes &amp;mdash; Cluster, Helm, HTTPS, CI/CD, GitOps, Kustomize, ArgoCD. You&amp;hellip;&quot; data-og-host=&quot;levelup.gitconnected.com&quot; data-og-source-url=&quot;https://levelup.gitconnected.com/from-local-development-to-kubernetes-cluster-helm-https-ci-cd-gitops-kustomize-argocd-5d9de6f5364c&quot; data-og-url=&quot;https://levelup.gitconnected.com/from-local-development-to-kubernetes-cluster-helm-https-ci-cd-gitops-kustomize-argocd-5d9de6f5364c&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RuNh3/hyUu2QmmPe/tbGWvi8t2wGEzikDp2YyNK/img.png?width=1200&amp;amp;height=653&amp;amp;face=0_0_1200_653&quot;&gt;&lt;a href=&quot;https://levelup.gitconnected.com/from-local-development-to-kubernetes-cluster-helm-https-ci-cd-gitops-kustomize-argocd-5d9de6f5364c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://levelup.gitconnected.com/from-local-development-to-kubernetes-cluster-helm-https-ci-cd-gitops-kustomize-argocd-5d9de6f5364c&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RuNh3/hyUu2QmmPe/tbGWvi8t2wGEzikDp2YyNK/img.png?width=1200&amp;amp;height=653&amp;amp;face=0_0_1200_653');&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;From local development to Kubernetes &amp;mdash; Cluster, Helm, HTTPS, CI/CD, GitOps, Kustomize, ArgoCD &amp;mdash;&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This is the second part of the series: From local development to Kubernetes &amp;mdash; Cluster, Helm, HTTPS, CI/CD, GitOps, Kustomize, ArgoCD. You&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;levelup.gitconnected.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@seifeddinerajhi/gitops-ci-cd-automation-workflow-using-github-actions-argocd-and-helm-charts-deployed-on-k8s-3811b253030b&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@seifeddinerajhi/gitops-ci-cd-automation-workflow-using-github-actions-argocd-and-helm-charts-deployed-on-k8s-3811b253030b&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700055228408&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;GitOps: CI/CD automation workflow using GitHub Actions, ArgoCD, and Helm charts deployed on K8s&amp;hellip;&quot; data-og-description=&quot;Introduction&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@seifeddinerajhi/gitops-ci-cd-automation-workflow-using-github-actions-argocd-and-helm-charts-deployed-on-k8s-3811b253030b&quot; data-og-url=&quot;https://medium.com/@seifeddinerajhi/gitops-ci-cd-automation-workflow-using-github-actions-argocd-and-helm-charts-deployed-on-k8s-3811b253030b&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DYZNN/hyUuWo6hSf/12f0gJpu4u11OrQBvrGMS1/img.png?width=1195&amp;amp;height=491&amp;amp;face=0_0_1195_491&quot;&gt;&lt;a href=&quot;https://medium.com/@seifeddinerajhi/gitops-ci-cd-automation-workflow-using-github-actions-argocd-and-helm-charts-deployed-on-k8s-3811b253030b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@seifeddinerajhi/gitops-ci-cd-automation-workflow-using-github-actions-argocd-and-helm-charts-deployed-on-k8s-3811b253030b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DYZNN/hyUuWo6hSf/12f0gJpu4u11OrQBvrGMS1/img.png?width=1195&amp;amp;height=491&amp;amp;face=0_0_1195_491');&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;GitOps: CI/CD automation workflow using GitHub Actions, ArgoCD, and Helm charts deployed on K8s&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Introduction&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>데브옵스 &amp;amp; 인프라/Kubernetes</category>
      <category>argocd</category>
      <category>ECR</category>
      <category>Github Actions</category>
      <category>helm</category>
      <category>k8s</category>
      <category>Kubernetes</category>
      <category>sealed-secret</category>
      <category>쿠버네티스</category>
      <author>턴태</author>
      <guid isPermaLink="true">https://dev-scratch.tistory.com/181</guid>
      <comments>https://dev-scratch.tistory.com/181#entry181comment</comments>
      <pubDate>Wed, 15 Nov 2023 22:34:53 +0900</pubDate>
    </item>
  </channel>
</rss>