<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>클라우드 개발 일지</title>
    <link>https://nali.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 29 Jul 2026 09:34:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>나리 집사</managingEditor>
    <image>
      <title>클라우드 개발 일지</title>
      <url>https://tistory1.daumcdn.net/tistory/4464372/attach/ccceab3d340e4f28b63d697b72c6eed3</url>
      <link>https://nali.tistory.com</link>
    </image>
    <item>
      <title>2024-2025 회고</title>
      <link>https://nali.tistory.com/413</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Recap 타임!&lt;br&gt;대학생 때 연말&amp;amp;연초에 한 해동안 했던 일과 앞으로 하고 싶은 일들을 정리하곤 했다.&lt;br&gt;그러나 취업 이후에는 그러한 시간이 적어진 것 같다.&lt;br&gt;과거를 돌아보며 앞으로의 미래를 더 잘 그려보고자 하는 취지로 회고를 작성한다.&lt;br&gt;고고!&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;목차&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;1.본업&lt;br&gt;- 2024년 1월 취업~ ing&lt;br&gt;- 운영 및 MSP&lt;br&gt;- 해외출장 1회&lt;br&gt;- 마이그레이션 프로젝트&lt;br&gt;- AI 개선 과제 리딩&lt;br&gt;&amp;nbsp;&lt;br&gt;2. 자격증&lt;br&gt;- Google Cloud Certified Associate Cloud Engineer (24년 10월)&lt;br&gt;- Azure AI Fundamentals (24년 10월)&lt;br&gt;- AWS Certified Solutions Architect - Associate (24년 11월)&lt;br&gt;- Azure Administrator Associate (25년 6월)&lt;br&gt;- AWS Certified AI Practitioner (25년 9월)&lt;br&gt;&amp;nbsp;&lt;br&gt;3.공부, 커뮤니티&lt;br&gt;24년 상반기: 테라폼, 클라우드자동화&amp;nbsp;&lt;br&gt;24년 하반기: 퍼블릭클라우드 플랫폼 공부, 자격증 취득&lt;br&gt;25년 상반기: AI, 영어&lt;br&gt;25년 하반기: 웹개발&lt;br&gt;+ 모교 선배멘토링 행사 참석&lt;br&gt;+ 클라우드, AI 등 여러 세미나 참석&lt;br&gt;+ 클라우드 클럽 TF 참여!&lt;br&gt;&amp;nbsp;&lt;br&gt;4. 취미&lt;br&gt;- 회사 밴드 가입 (25년 상반기) 및 공연&lt;br&gt;- 동아리 밴드 가입 (25년 하반기)&lt;br&gt;- 기타 학원 n개월 및 공연&lt;br&gt;- 콘텐츠 시청 1n개..&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;5. 운동&lt;br&gt;- PT 3개월&lt;br&gt;- 찍먹: 필라테스, 요가, 테니스&lt;br&gt;&amp;nbsp;&lt;br&gt;6. 여행&lt;br&gt;- 2023 12/24~12/25 월미도 (T)&lt;br&gt;&lt;i&gt;- 2024 1/1~1/5 오사카 (가족)&lt;/i&gt;&lt;br&gt;- 2024 8/10~8/12 여수 (AT)&lt;br&gt;&lt;i&gt;- 2024 8/14~8/18 세부 (가족)&lt;/i&gt;&lt;br&gt;- 2025 2/7~2/8 전주 (M)&lt;br&gt;- 2025 3/17~3/18 경주 (C)&lt;br&gt;&lt;i&gt;- 2025 5/18~5/24 미국 (출장)&lt;/i&gt;&lt;br&gt;- 2025 7/5~7/6 부여 (가족)&lt;br&gt;&lt;i&gt;- 2025 8/5~8/9 몽골 (가족)&lt;/i&gt;&lt;br&gt;- 2025 12/12~12/13 제천 (가족)&lt;br&gt;- 2025 12/16~12/27 김해, 부산 (C)&lt;br&gt;&amp;nbsp;&lt;br&gt;적어보니까 은근히 한 일이 많은 것 같다.&lt;br&gt;오히려 회사에 취업하고 나서 불안정성이 줄어들고 안정 수입도 생겨 해보고 싶었던 것들을 다 해볼 수 있었던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;아래 디테일한 내용과 느낀점을 풀어보겠다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;본업 (2024년도 1월~ing)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;운좋게 졸업하며 바로 회사에 취업해서, 이제는 3년차이다!&lt;br&gt;취업 준비를 하면서 바로 붙을 것이라는 생각은 못했었는데, 대기업 위주로 지원해서 5개 기업에 2차면접에 가고 결국 지금 회사에 최종합격하게 되었다.&lt;br&gt;내 경험이 그렇게 특별하다고 생각하지는 않지만.. 나중에 풀어보며 후배들에게 도움이 되고 싶다.&lt;br&gt;&amp;nbsp;&lt;br&gt;회사 생활에서는 함께하는 사람들이 정말 중요한 것 같은데, 나는 감사하게도 좋은 동료분들을 만났고 좋은 방향으로 이끌어주셔서 정말 감사하게 생각하고 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;- 운영 및 MSP&lt;br&gt;&amp;nbsp;&lt;br&gt;업무 관련해서는, 내가 인프라쪽 경험이 많지는 않았는데 처음부터 알려주셔서 정말 많은 것을 배울 수 있었다.&lt;br&gt;특히나 이 회사에서는 의사소통 능력이나 프로젝트 매니징, 사업관리와 같이 요즘 같은 AI 시대에 더욱 중요한 능력을 많이 키울 수 있는 것 같다. 물론 기술적으로도 배울 것이 많다!&lt;br&gt;&amp;nbsp;&lt;br&gt;- 해외 출장 1회&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ih8Cd/dJMcaivjMR9/0fGkq5kxHrEzz6D9ClTrZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ih8Cd/dJMcaivjMR9/0fGkq5kxHrEzz6D9ClTrZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ih8Cd/dJMcaivjMR9/0fGkq5kxHrEzz6D9ClTrZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIh8Cd%2FdJMcaivjMR9%2F0fGkq5kxHrEzz6D9ClTrZK%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;400&quot; height=&quot;533&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;상반기에는 미국출장을 다녀왔다.&lt;br&gt;영어 실력을 더 키워야겠다고 느껴져, 출장 한 달 전부터 다른 공부보다도 영어 공부만 하던 것이 생각난다.&lt;br&gt;일주일동안 출장을 가서 프로젝트를 하면서 영어로 발표, 소통도 많이하고 성공적으로 끝마치고 의미있는 피드백도 얻어올 수 있어서 즐거웠었다. ㅎㅎ (아직 주니어이기도 하고 같이 출장가신 시니어분이 있어서 엄청 큰 부담은 없었던 것이 행운이다.)&lt;br&gt;특히 온라인으로만 보던 해외 사람들을 보니 더 서로를 사람처럼 대하고 소통이 원활해진 것이 큰 수확이다.&lt;br&gt;글로벌 회사인만큼 영어 역량을 더 키워야겠다고 느껴진다.&lt;br&gt;&amp;nbsp;&lt;br&gt;- 마이그레이션 프로젝트&lt;br&gt;&amp;nbsp;&lt;br&gt;내부적으로 중요한 프로젝트였고, 팀원들과 으쌰으쌰해서 잘 끝마쳐진 것 같다.&lt;br&gt;덕분에 기술에 대한 이해가 깊어졌고, 마이그레이션이 어떤 식으로 진행되는지 몸소 알 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;- AI 과제 리딩&lt;br&gt;&amp;nbsp;&lt;br&gt;회사에서 AI에 대한 중요성이 부각되면서, 나는 팀 내 주니어 대표로... AI 과제를 진행하는 역할을 맡게 되었고, 솔직히 하고 싶었는데 내가 하게 되어 너무 기뻤다! (이렇게 새로운 기회를 만들어낼 수 있는 것이 지속적으로 공부하면 좋은 점 같다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;RAG를 이용한 챗봇이라는 나름 스테디한 과제를 주제로 진행했는데, AWS를 연동하고 챗봇을 직접 구현하고, 회사의 여러 보안 검토를 거치면서 나름 의미있는 결과물을 만들어냈었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이를 주제로 회사 사람들 상대로 세션을 하기도 했는데, 320명 가량의 사우분들이 들어와주셔서 긴장되었지만 회사 생활에서 쉽게 할 수 없는 경험을 한 것 같아 뿌듯하다. 또 RAG 챗봇을 구성하며 SDK를 활용한 자동화 코드를 작성해서 사내 개발자 컨플에 기고하기도 했는데, 이 게시글이 한 달 내 좋아요 수 1위를 기록하기도 하여 도파민 돌았던 기억이 있다. ㅎㅎ&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;개인적으로 운영만 하는 것보다는 이렇게 개선하고 새로운 프로젝트를 하는 것이 나에게 활력소가 되어주는 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;- 마무리&lt;br&gt;&lt;br&gt;먼저 오픈으로 공개된 내용만 썼다. 자세한 내용은 쓸 수가 없당..&lt;br&gt;&amp;nbsp;&lt;br&gt;업무를 열심히 해야 하는가?에 직장인들 사이에서 많이들 대해서는 의견이 갈리는 부분인데... 나는 두 가지 이유로 열심히 하는 것이 좋은 것 같다고 개인적으로 생각한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;+ 자기 만족과 재미 : 회사에서 주40시간 가량의 시간 동안 일을 해야 한다. 이때 흥미가 없다면 오히려 회사 생활을 지속할 동기부여가 없어진다고 생각한다. 자신의 성향에 맞는 업무를 하며 나름의 재미를 찾는 것이 좋은 것 같다. 물론 회사밖에서도 재미와 의미를 찾아야 하지만, 회사에서의 즐거움을 굳이 모두 내려놓을 필요는 없다고 생각한다.&lt;br&gt;+ 멘탈 관리 : 상황은 항상 변한다. 회사에서 구조조정을 하거나 정치적인 상황이 있다면, 나에게 회사에 어필할 것이 없거나 혹은 완전히 종속되어 있다면 완전히 무너질 수 있다. 오히려 일이나 자기개발을 어느정도는 열심히 하는 것이 더 숨통을 틔우고 즐겁게 일하는 방법이라고 생각한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;나는 오히려 소득적인 부분보다 위와 같은 부분이 더 중요하다고 생각한다. 소득은 따라오는 것이라고 생각하기 때문이다.&lt;br&gt;물론 상황에 따라 생각이 바뀔 수도 있다.. 나이가 많아지면 책임지고 내려놓을 것들이 더욱 많아지기 때문이다.&lt;br&gt;또한 근로소득의 한계는 분명히 있기 때문에 재태크도 열심히 해야할 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;미래를 생각해보면, 나중에 연차가 쌓이고 이끄는 자리에 가게 된다면 넓게 시야를 가지고 올바르게 판단하고 제대로 말할 수 있는 사람이 되고 싶다. 물론 이끄는 자리에 가지 않고 기술 실력자가 되어도 오히려 좋다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;자격증&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2년 동안 자격증 5개를 취득했다. 회사에서 자격증비를 지원해줘서 가능했던 일이다. ♥️&lt;br&gt;이쪽은 자기만족과 발전, 회사 KPI를 위해 한 부분이라 빠르게 줄이겠다.&lt;br&gt;2년차까지 기본적인 자격증들을 step-by-step으로 취득해보았으니 올해는 PRO 자격증들을 취득해봐야겠다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tna0n/dJMcacaRKH9/MLgOSh5fn7qmjHu7UlenU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tna0n/dJMcacaRKH9/MLgOSh5fn7qmjHu7UlenU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tna0n/dJMcacaRKH9/MLgOSh5fn7qmjHu7UlenU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftna0n%2FdJMcacaRKH9%2FMLgOSh5fn7qmjHu7UlenU0%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;400&quot; height=&quot;438&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;공부/커뮤니티&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 회사를 다니면서 상황에 맞추어 필요한 공부를 꾸준히 해왔다고 생각한다.&lt;br&gt;개인적으로 IT 관련 공부하는 일이 은근히 적성에 맞는 것 같다...&lt;br&gt;주니어일 때 많이 배우고 싶다!!&lt;br&gt;다만 피드백을 하자면 공부한 것을 티내는 능력이 조금 부족한 것 같다. 올해에는 잘 정리하고 잘 티내보자.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 커뮤니티나 세미나에 참여해서, 더 넓게 공부하고 새로운 사람을 만나기도 했는데 재미가 있었다.&lt;br&gt;올해에도 이것저것 해보아야겠다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;취미&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;나는 중학생 때 음악쪽 진로를 가지고 싶을 정도로, 음악을 듣고 연주하는 것을 좋아했었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;현실적인 사유로 그만두었었고, 대학생 때도 열심히 하지는 못했었다.&lt;br&gt;이제 취업을 하고 생활패턴이 생기며 여유 시간이 생기고, 취미로 계속 하게 되는 것이 음악인 것을 보면 정말 좋아하는 것은 이게 아닐까 싶기도 하다. ㅎㅎ&lt;br&gt;그래서 무엇을 하느냐, 하면 밴드를 하고 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;- 회사 밴드&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz7ysj/dJMcabbVK8n/29OgiBasHnnB8vTXxkUbkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz7ysj/dJMcabbVK8n/29OgiBasHnnB8vTXxkUbkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz7ysj/dJMcabbVK8n/29OgiBasHnnB8vTXxkUbkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz7ysj%2FdJMcabbVK8n%2F29OgiBasHnnB8vTXxkUbkk%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;400&quot; height=&quot;522&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;25년 상반기부터 지금까지 참여하고 있다.&amp;nbsp;&lt;br&gt;처음에는 건반으로 시작해서 지금은 일렉기타를 치고 있다!&lt;br&gt;25년 상반기에는 건반 연주자로 3주만에 공연을 했었는데 떨리긴 했지만 재밌었당.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;- 기타 학원&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2509&quot; data-origin-height=&quot;2623&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWPzyV/dJMcag5ofp9/9PREY0pR7urBnEg1F0V7JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWPzyV/dJMcag5ofp9/9PREY0pR7urBnEg1F0V7JK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWPzyV/dJMcag5ofp9/9PREY0pR7urBnEg1F0V7JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWPzyV%2FdJMcag5ofp9%2F9PREY0pR7urBnEg1F0V7JK%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;400&quot; height=&quot;418&quot; data-origin-width=&quot;2509&quot; data-origin-height=&quot;2623&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;기타 학원을 다니며, 25년 하반기에는 공연을 해보기도 했다. 헤헤&lt;br&gt;사실 부끄러워서 친구도 많이 못불렀지만... 기타로 공연을 하다닝 재밌었던 경험이었다. (지금은 다니지 않고 있다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;- 클클 밴드&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4031&quot; data-origin-height=&quot;2772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IcTgs/dJMcaf6sUGo/n82J776k1YYKwQgy3YkVj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IcTgs/dJMcaf6sUGo/n82J776k1YYKwQgy3YkVj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IcTgs/dJMcaf6sUGo/n82J776k1YYKwQgy3YkVj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIcTgs%2FdJMcaf6sUGo%2Fn82J776k1YYKwQgy3YkVj1%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;400&quot; height=&quot;275&quot; data-origin-width=&quot;4031&quot; data-origin-height=&quot;2772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이전에 활동하던 연합동아리에서 YB/OB 상관없이 밴드를 모집해서, 유일한 건반으로 합류하게 되었다.&lt;br&gt;다들 연주를 잘해서 IT 직종과 연주실력의 상관관계에 대해 고민하게 되었다. ㅋㅋㅋ&lt;br&gt;&amp;nbsp;&lt;br&gt;뜬금없지만 내 장점을 말하자면 1.초기 습득력이 빠름 2. 초견이 빠름(건반 한정) 이다.&lt;br&gt;건반이면 1시간이면 악보보고 웬만한 곡은 칠 수 있는 듯하다.&lt;br&gt;그래서 연습 시간 별로 안들고도 모범회원이 될 수 있는 장점이 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;올해 건반을 더 열심히 연습해주고 다른 악기도 또 해보고 싶다!!&lt;br&gt;편곡이나 톤메이킹도 도전해보고 싶당.&lt;br&gt;&amp;nbsp;&lt;br&gt;밴드를 하면 좋은 점은 악기 연주를 지속적으로 할 수 있고, 무대에 올라간 내 모습 영상을 보면서 여러모로 자기객관화를 할 수 있다. 그리고 재밌다!&lt;br&gt;&lt;br&gt;- ott&lt;br&gt;&lt;br&gt;진귀주톱 극장판이 다 나온 해이다. ㄷㄷ&lt;br&gt;n회차 하느라 힘들었다.^^..&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;운동&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 초에 PT를 열심히 했는데, 나름의 성취감이 있었다. 근육도 많이 늘고 살이 많이 빠졌던 기억이다.&lt;br&gt;이외 요가, 필라테스, 테니스 찍먹을 해보았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;할 말이 별로 없는 것이,,, 솔직히 운동은 열심히 안했다.&lt;br&gt;원래 살이 안찌는 체질이었고 건강했는데.. 회사 다니며 살이 좀 쪄서 올해는 재밌는 운동을 찾아서 꾸준히 해보는 것이 목표이다.&amp;nbsp;&lt;br&gt;1월에 시작한 운동이 있는데 꾸준히 가길! ^^&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;여행&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;소중한 가족 그리고 친구들과 여행을 많이 다녀왔다!&lt;br&gt;국내 7번, 해외 4번(출장포함) 여행을 다녀왔다. (너무 많이 갔나..? 대학생 때 많이 안갔으니 보상이라고 치자.)&lt;br&gt;새로운 곳을 탐방하고 맛있는 것도 먹고 이야기도 많이 하고 즐거운 추억이 많아졌다.&lt;br&gt;&amp;nbsp;&lt;br&gt;여행을 많이 다니면서, 나는 여행지 자체보다 소중한 사람과 보내는 시간 자체가 더 좋다는 것을 느꼈다.&lt;br&gt;그리고 여행을 가면 모든 것을 내려놓는 합법적 온전한 휴가 기간인 것이 좋다. 이래서 직장인들이 여행에 미쳐있나보다.&lt;br&gt;&amp;nbsp;&lt;br&gt;올해는 재태크에 신경을 쓰면서 여행을 많이 가기는 어려울 수 있는데, 기회가 올때는 꼭꼭 가려 한다.&lt;br&gt;왜냐하면 소중한 사람들과 함께할 수 있는 시간도 한순간이기 때문이다..&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리 &amp;amp; 26년 목표&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 회사에 적응도 하고, 공부도 하고, 취미도 찾고, 친구들과도 깊어지며 나름 알차게 시간을 보낸 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;올해는 주변에서 나를 흔들려고 하는 외부요인이 있어도 더욱 나를 믿어주어야겠다.&lt;br&gt;또! 나를 드러내고 있는 그대로의 나를 인정받는 연습을 해야겠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그럼 이만...&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Review/회고</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/413</guid>
      <comments>https://nali.tistory.com/413#entry413comment</comments>
      <pubDate>Sun, 25 Jan 2026 17:14:45 +0900</pubDate>
    </item>
    <item>
      <title>Azure ID 및 권한, 거버넌스 관리</title>
      <link>https://nali.tistory.com/398</link>
      <description>&lt;h2 id=&quot;1.%20Azure%20Entra%20ID-1&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;1. Azure Entra ID&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/entra/fundamentals/whatis?WT.mc_id=Portal-Microsoft_AAD_IAM&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/entra/fundamentals/whatis?WT.mc_id=Portal-Microsoft_AAD_IAM&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;Microsoft Entra ID는 직원이 외부 리소스에 액세스하는 데 사용할 수 있는 클라우드 기반 ID 및 액세스 관리 서비스입니다. 예 리소스에는 Microsoft 365, Azure Portal 및 수천 개의 기타 SaaS 애플리케이션이 포함됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자와&amp;nbsp;그룹은&amp;nbsp;ID&amp;nbsp;솔루션의&amp;nbsp;기본&amp;nbsp;구성&amp;nbsp;요소입니다.&lt;span style=&quot;background-color: #ffffff; color: #161616; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔지니어가&amp;nbsp;Microsoft&amp;nbsp;Entra&amp;nbsp;ID를&amp;nbsp;사용하여&amp;nbsp;인증할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;사용자와&amp;nbsp;그룹을&amp;nbsp;프로비저닝하는&amp;nbsp;작업을&amp;nbsp;맡았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fefefe; color: #656565; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Microsoft Entra ID를 Active Directory DS와 비교하고, Microsoft Entra ID P1 및 P2에 대해 알아보고, 클라우드에서 도메인에 가입된 장치와 앱을 관리하기 위한 Microsoft Entra Domain Services를 살펴보세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테넌트는&amp;nbsp;조직을&amp;nbsp;대표하며&amp;nbsp;내부&amp;nbsp;및&amp;nbsp;외부&amp;nbsp;사용자를&amp;nbsp;위해&amp;nbsp;Microsoft&amp;nbsp;클라우드&amp;nbsp;서비스의&amp;nbsp;특정&amp;nbsp;인스턴스를&amp;nbsp;관리하는&amp;nbsp;데&amp;nbsp;도움을&amp;nbsp;줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) Entra ID 테넌트 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #272727; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Azure의 클라우드 기반 ID 및 액세스 관리 솔루션인 &lt;/span&gt;Microsoft Entra ID 서비스로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #272727; text-align: left;&quot;&gt;테넌트 관리 탭에서 새로운 Azure AD 테넌트를 생성하고, 생성 완료된 테넌트를 확인합니다. &lt;/span&gt;&lt;/p&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/bKN4BO/btsLNOznuAE/r3qTO1XapHFKaAU1N0lw0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKN4BO/btsLNOznuAE/r3qTO1XapHFKaAU1N0lw0K/img.png&quot; width=&quot;400&quot; height=&quot;415&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;1309&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.5292%; margin-right: 10px;&quot; data-widthpercent=&quot;50.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKN4BO/btsLNOznuAE/r3qTO1XapHFKaAU1N0lw0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKN4BO%2FbtsLNOznuAE%2Fr3qTO1XapHFKaAU1N0lw0K%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;1261&quot; height=&quot;1309&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ch5PPp/btsLOEP7phZ/KOCfnOJKbDBXiL9bgiej11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ch5PPp/btsLOEP7phZ/KOCfnOJKbDBXiL9bgiej11/img.png&quot; width=&quot;400&quot; height=&quot;417&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;1318&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.308%;&quot; data-widthpercent=&quot;49.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ch5PPp/btsLOEP7phZ/KOCfnOJKbDBXiL9bgiej11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fch5PPp%2FbtsLOEP7phZ%2FKOCfnOJKbDBXiL9bgiej11%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;1264&quot; height=&quot;1318&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테넌트 유형: &lt;span style=&quot;background-color: #ffffff; color: #292827; text-align: start;&quot;&gt;Microsoft Entra ID 및 Azure AD B2C를 통해 사용자는 조직에서 게시한 애플리케이션에 액세스하고 동일한 관리 환경을 공유할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #292827; letter-spacing: 0px;&quot;&gt;위치: 위에서 선택한 위치에 따라 Microsoft Entra ID가 Core Store 데이터만 저장할 지리적 위치가 결정됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 사용자 계정 생성 및 구성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신규 테넌트 하위에서 새 사용자를 생성합니다. 외부 이메일 사용자도 초대가 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;1335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxFymO/btsLN33TgI7/7PMXgCApO3oL4wWi84OGp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxFymO/btsLN33TgI7/7PMXgCApO3oL4wWi84OGp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxFymO/btsLN33TgI7/7PMXgCApO3oL4wWi84OGp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxFymO%2FbtsLN33TgI7%2F7PMXgCApO3oL4wWi84OGp1%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;400&quot; height=&quot;420&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;1335&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;h4 data-ke-size=&quot;size20&quot;&gt;3) 그룹 생성 및 멤버 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;소유자와 구성원을 포함하여 신규 그룹을 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fcfcfc; color: #272727; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-line-start=&quot;196&quot; data-line-end=&quot;197&quot;&gt;만료를 사용하여 그룹 수명 구성이 가능하고, 명명 정책으로 차단 단어 혹은 접두사, 접미사 구성이 가능합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #fcfcfc; color: #272727; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-line-start=&quot;196&quot; data-line-end=&quot;197&quot;&gt;그룹 멤버십은 정적 혹은 동적으로 할당될 수 있습니다.&lt;/p&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/rjfSn/btsLL8eK39m/ZBTxk6kLrsfhysEmERfuKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rjfSn/btsLL8eK39m/ZBTxk6kLrsfhysEmERfuKK/img.png&quot; width=&quot;400&quot; height=&quot;431&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;1332&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.5804%; margin-right: 10px;&quot; data-widthpercent=&quot;49.15&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rjfSn/btsLL8eK39m/ZBTxk6kLrsfhysEmERfuKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrjfSn%2FbtsLL8eK39m%2FZBTxk6kLrsfhysEmERfuKK%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;1237&quot; height=&quot;1332&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH2aYC/btsLL5PTOL0/AmsWozFOT9xYAgYTiIFuz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH2aYC/btsLL5PTOL0/AmsWozFOT9xYAgYTiIFuz0/img.png&quot; width=&quot;400&quot; height=&quot;416&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;1324&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.85&quot; style=&quot;width: 50.2568%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH2aYC/btsLL5PTOL0/AmsWozFOT9xYAgYTiIFuz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH2aYC%2FbtsLL5PTOL0%2FAmsWozFOT9xYAgYTiIFuz0%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;1324&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #fcfcfc; color: #272727; text-align: left;&quot; data-line-end=&quot;197&quot; data-line-start=&quot;196&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;2. Azure RBAC 및 권한 관리&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;역할을 확인하고 활동 로그를 확인합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;리소스 그룹을 선택하고, 액세스 제어(IAM)의 역할 탭으로 전환합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;리소스 그룹에 대해서 '역할'과 '멤버'를 선택하여 권한을 부여합니다.&lt;/p&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/lClr5/btsLNYBM7T3/4oDanIQb8XUba7jIUdF2s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lClr5/btsLNYBM7T3/4oDanIQb8XUba7jIUdF2s1/img.png&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;1338&quot; data-is-animation=&quot;false&quot; width=&quot;400&quot; height=&quot;422&quot; style=&quot;width: 49.1952%; margin-right: 10px;&quot; data-widthpercent=&quot;49.77&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lClr5/btsLNYBM7T3/4oDanIQb8XUba7jIUdF2s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlClr5%2FbtsLNYBM7T3%2F4oDanIQb8XUba7jIUdF2s1%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;1269&quot; height=&quot;1338&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Wz0Q/btsLN0fd0Qw/7gJDgD12hprEIl6WlXaZLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Wz0Q/btsLN0fd0Qw/7gJDgD12hprEIl6WlXaZLK/img.png&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;1327&quot; data-is-animation=&quot;false&quot; width=&quot;400&quot; height=&quot;418&quot; style=&quot;width: 49.642%;&quot; data-widthpercent=&quot;50.23&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Wz0Q/btsLN0fd0Qw/7gJDgD12hprEIl6WlXaZLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Wz0Q%2FbtsLN0fd0Qw%2F7gJDgD12hprEIl6WlXaZLK%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;1270&quot; height=&quot;1327&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;활동 로그 또 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1257&quot; data-origin-height=&quot;1323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciniRS/btsLNyQVNOY/JHJkSNc6A39ampBAiQnZMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciniRS/btsLNyQVNOY/JHJkSNc6A39ampBAiQnZMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciniRS/btsLNyQVNOY/JHJkSNc6A39ampBAiQnZMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciniRS%2FbtsLNyQVNOY%2FJHJkSNc6A39ampBAiQnZMk%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;400&quot; height=&quot;421&quot; data-origin-width=&quot;1257&quot; data-origin-height=&quot;1323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;3.%20Azure%20Policy-1&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;3. Azure Policy&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- Azure Policy를 통해 거버넌스를 관리합니다.&lt;/p&gt;</description>
      <category>  Cloud Architect/Azure</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/398</guid>
      <comments>https://nali.tistory.com/398#entry398comment</comments>
      <pubDate>Tue, 14 Jan 2025 22:20:07 +0900</pubDate>
    </item>
    <item>
      <title>CDN 동작과 특장점</title>
      <link>https://nali.tistory.com/391</link>
      <description>&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;p data-ke-size=&quot;size16&quot;&gt;라인의 CDN 플랫폼 세미나를 참고하였습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CDN이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Content Delivery Network.&lt;br /&gt;지역적으로 분산되어 있는 프록시 서버들의 클러스터, 데이터 센터이다. 유저들에게 가장 가까운 위치에 서비스를 배치해서 높은 가용성과 성능을 제공해준다.&lt;br /&gt;CDN은 주요 ISP 내부에 CDN 내부에 배치를 해서 CDN 서비스 제공이 가능하다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CDN 다운로드 흐름&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1761&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kADoD/btsJWSC2XLZ/TstEhybM5HKzbJKQiQYLok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kADoD/btsJWSC2XLZ/TstEhybM5HKzbJKQiQYLok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kADoD/btsJWSC2XLZ/TstEhybM5HKzbJKQiQYLok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkADoD%2FbtsJWSC2XLZ%2FTstEhybM5HKzbJKQiQYLok%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;1761&quot; height=&quot;808&quot; data-origin-width=&quot;1761&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;[DNS]&lt;br /&gt;CDN 접속에서부터 시작을 한다.&lt;br /&gt;1) DNS Lookup이라는 단계를 통해, 근처의 CDN 서버의 IP를 받게 된다. 유저가 접근하는 DNS Record(서비스 도메인)을 CNAME이라는 레코드 타입을 써서 CDN 서비스의 DNS 서버로 위임을 시켜줄 필요가 있다.&amp;nbsp;&lt;br /&gt;2) 이 상태에서 유저가 IP Address를 받기 위해 DNS Resolving을 시작하면, 유저가 가지고 있는 Recursive DNS 서버를 통해서 최종적으로는 CDN 서비스가 가지고 있는 DNS 서버로 'IP를 주세요'라는 요청이 가게 된다.&lt;br /&gt;3) 요청이 갈 때, 유저가 가지고 있는 DNS 서버의 Recursive DNS 서버의 주소가 같이 전달이 된다. CDN의 DNS 서버에서는 IP를 보고, IP가 한국인지/일본인지 판단하여, 가까운 Edge&amp;nbsp; 서버의 IP를 돌려준다.&lt;br /&gt;4) 유저는 실제 다운로드를 받기 위해, IP에 CDN 서버에 HTTP 리퀘스트를 전달한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;[CDN 서버 동작]&lt;br /&gt;1) 요청을 받은 CDN 서버는 일단 그 요청에 해당하는 콘텐츠가 있는지 확인한다. 캐싱이 되어 있는지 확인한다.&lt;br /&gt;2) 콘텐츠가 없다면, 원본 오리진 서버에 요청을 던져서 오리진 서버가 요청을 주면 그걸 캐싱을 하여 전달한다.&lt;br /&gt;3) 이후 다른 유저가 같은 콘텐츠를 요청을 하면 캐싱된 콘텐츠를 그대로 다운로드할 수 있게 도와준다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CDN의 Value는 어떤 것인가요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CDN을 썼을 때 얻을 수 있는 효과는, 크게 3가지이다.&lt;br /&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;1765&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pzUhC/btsJV5JWZxy/R0BgCcISNfkJlRReuBtVDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pzUhC/btsJV5JWZxy/R0BgCcISNfkJlRReuBtVDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pzUhC/btsJV5JWZxy/R0BgCcISNfkJlRReuBtVDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpzUhC%2FbtsJV5JWZxy%2FR0BgCcISNfkJlRReuBtVDk%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;1765&quot; height=&quot;814&quot; data-origin-width=&quot;1765&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 성능이다.&lt;br /&gt;User 관점에서 빠르게 콘텐츠를 받아볼 수 있다. 멀리 있는 원본 서버에서 가져오는 것보다 캐싱된 콘텐츠를 가져올 때 더 빠르다.&lt;br /&gt;원본 서버가 유저에서 거리가 멀수록 CDN에서 받는 속도가 빨라진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;811&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9hwjS/btsJVZpCUjb/xswXkKIvh3pI3jkKhGEIr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9hwjS/btsJVZpCUjb/xswXkKIvh3pI3jkKhGEIr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9hwjS/btsJVZpCUjb/xswXkKIvh3pI3jkKhGEIr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9hwjS%2FbtsJVZpCUjb%2FxswXkKIvh3pI3jkKhGEIr0%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;1764&quot; height=&quot;811&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;811&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 원본 서버의 부하 경감(오프로드)이다.&lt;br /&gt;원본 서버 입장에서는 유저들의 요청을 감당해야 하는데, 앞의 CDN 서버가 처리해주기 때문에 원본 서버의 부하가 경감된다.&lt;br /&gt;실제 유저의 다운로드 대역폭을 보면, 실제 요청 150Gbps에서 원본은 4.5Gbps만 처리하고 있다. 원본 서버를 작게 유지할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1759&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cllFvL/btsJV8T8V3z/CL5Jw7Lo8smNWdvAP7Bq10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cllFvL/btsJV8T8V3z/CL5Jw7Lo8smNWdvAP7Bq10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cllFvL/btsJV8T8V3z/CL5Jw7Lo8smNWdvAP7Bq10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcllFvL%2FbtsJV8T8V3z%2FCL5Jw7Lo8smNWdvAP7Bq10%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;1759&quot; height=&quot;814&quot; data-origin-width=&quot;1759&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째는 보안이다.&lt;br /&gt;원본 서비스가 직접 유저의 요청을 받는게 아니라 CDN에서 받기 때문에 보안적인 효과가 있다.&lt;br /&gt;앞의 CDN 서버에서는 저수준 L4 Level 수준의 DDoS 공격이 있어도 처리가 가능하며, CDN 위에 L7 레이어의 웹 방화벽을 설치해서 고수준의 공격도 차단하는 기능도 제공을 하고 있다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CDN 용어&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Time to Live Control&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 캐시에 적용이 되는 부분이다. 캐시가 얼마나 오랫동안 콘텐츠를 보관하고 있어야 하느냐의 뜻.&lt;br /&gt;지정된 TTL만큼 콘텐츠를 유지하게 되고, 기간 만료가 되면 콘텐츠가 갱신이 있는지 원본에 확인을 해보며, 갱신된 게 없으면 원래의 콘텐츠를 TTL만큼 사용할 수 있도록 처리해준다. 바뀐 게 있다면 원본에서 새로운 콘텐츠를 내려준다.&lt;br /&gt;원본 서버에서 Last Modified Time 혹은 Etag 정보를 포함해서, 원본 서버에서 이를 바탕으로 갱신이 있는지 없는지를 판단한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cache Key&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 요청할 때 요청을 바탕으로 색인을 만들어서 이를 바탕으로 콘텐츠를 찾는다.&lt;br /&gt;보통 Domain, URL, Query String을 기본적으로 사용하고 있으며, 추가하여 유저의 IP 정보 User Agent 정보 등을 Cache Key로 지정할 수도 있다.&lt;br /&gt;중요한 점이, 동일한 Cache key를 많이 호출할 수록 캐시의 효율성이 올라가나, 잘못 쓰면 잘못된 콘텐츠가 내려갈 수 있어서 주의 깊게 사용해야 한다.&lt;br /&gt;예시) 프로덕트 아이디별로 다른 사이트를 호출하도록. product ID라는 Query String을 반드시 Cache Key로 포함해야 한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Purge and eviction&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 서버는 TTL을 준수해서 콘텐츠를 유지한다. TTL을 지키지 못하는 경우는 다음과 같다.&lt;br /&gt;Purge, 서비스 개발자가 필요하여 콘텐츠를 제거하는 경우가 있다. 새로운 객체로 변경해야 하기 때문에 현재의 콘텐츠를 모두 삭제한다.&lt;br /&gt;모든 서버에 가서 이 콘텐츠는 서빙하면 안되니, 새로운 서비스를 가져오도록 지시하는 것이다.&lt;br /&gt;&amp;nbsp;Eviction. 엣지 서버도 서버이기에, 필요한 경우 인기 없는 콘텐츠부터 제거해서 공간을 확보하게 된다. Edge가 필요해서 콘텐츠를 지키는 것이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Client Cache&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시가 두 가지가 있다.&lt;br /&gt;CDN 서버 자체가 캐싱하는 것이 있고, 클라이언트 쪽에서 캐싱하는 것이 있다.&lt;br /&gt;CDN 관련 문제가 생기면, 트러블슈팅 시 CDN 서버나 클라이언트 캐시가 있어서 혼란을 겪는 경우가 있으며, 클라이언트 캐시를 분리하여 결정을 해야 한다.&lt;br /&gt;CDN 서버 캐시 TTL에는 smax-age를 통, 클라이언트 캐시 TTL은 max-age를 통해 설정을 할 수 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;[참고]&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Zz-IehTBgjY&amp;amp;t=330s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=Zz-IehTBgjY&amp;amp;t=330s&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=XQKpHc2e59Y&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=XQKpHc2e59Y&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>  Cloud Architect/CDN</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/391</guid>
      <comments>https://nali.tistory.com/391#entry391comment</comments>
      <pubDate>Sun, 6 Oct 2024 22:16:18 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] OpenTofu</title>
      <link>https://nali.tistory.com/390</link>
      <description>&lt;h2 id=&quot;OpenTofu%EB%8A%94%3F-1&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://ssungz.tistory.com/35#OpenTofu%EB%8A%94%3F-1&quot;&gt;OpenTofu는?&lt;/a&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://opentofu.org/docs/&quot;&gt;https://opentofu.org/docs/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;OpenTofu란 Terraform이 라이센스를 비즈니스 레벨로 변경함에 따라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #8a3db6; text-align: start;&quot;&gt;&lt;b&gt;Terraform의 포크(fork)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;버전이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이전의 이름은 OpenTF였지만, OpenTofu로 이름을 변경하였고, 오픈 소스, 커뮤니티 중심, Linux Foundation에서 관리한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Hashicorp에서 Terraform의 라이센스를 MPL(Mozilla Public License)에서 BUSL(Business Source License) 비오픈 라이센스로 변경하였고, 이에 대한 대응으로 탕생하게 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Terraform을 포크한 오픈 소스이기 때문에 Terraform의 구성과 워크플로에 호환되며,&amp;nbsp;현재는 버전 차이가 크지 않아 업데이트에 잘 따라가고 있으나 회사와 같은 기술 로드맵이 있지 않으며 업데이트가 늘어날 수록 기능에 차이가 생길 것으로 예상된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Oracle&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;에서&amp;nbsp;기업용 제품에서 사용중인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Terraform&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;OpenTofu&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;교체&lt;/b&gt;했다. 즉, Oracle E-Business Suite (EBS) Cloud Manager의 최신 버전에서 Terraform 대신 오픈소스 포크인 OpenTofu를 사용한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;핵심-workflow&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 workflow&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTofu의 핵심 워크플로는 세 단계로 구성됩니다.&lt;/p&gt;
&lt;h4 id=&quot;write쓰기&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Write(쓰기)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리소스를 정의&lt;/p&gt;
&lt;h4 id=&quot;plan-계획&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Plan (계획)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTofu는 기존 인프라와 구성에 따라 생성, 업데이트 또는 파괴할 인프라를 설명하는 실행 계획 생성&lt;/p&gt;
&lt;h4 id=&quot;apply-적용&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Apply (적용)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;승인 시 OpenTofu는 모든 리소스 종속성을 고려하여 정의한 순서대로 적용&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;%08Terraform%20to%20Opentofu-1&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://ssungz.tistory.com/35#%08Terraform%20to%20Opentofu-1&quot;&gt;Terraform to Opentofu&lt;/a&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;테라폼과 opentofu는 기능이 상당히 유사하며, 기존 테라폼 파일을 실행해도 큰 문제가 없다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;opentofu 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1722699915753&quot; class=&quot;bash&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ brew install tenv
$ tenv -v
$ tenv tofu -h
$ tenv tofu list-remote
$ tenv tofu install 1.7.3
$ tenv tofu use 1.7.3&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722699915756&quot; class=&quot;dsconfig&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ tenv tf list-remote
$ tenv tf install 1.8.5
$ tenv tf use 1.8.5
$ tenv tf list&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;586&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBhgjO/btsITvXcaNc/3iEoqwH0BwnfNYka3L3Ttk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBhgjO/btsITvXcaNc/3iEoqwH0BwnfNYka3L3Ttk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBhgjO/btsITvXcaNc/3iEoqwH0BwnfNYka3L3Ttk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBhgjO%2FbtsITvXcaNc%2F3iEoqwH0BwnfNYka3L3Ttk%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;586&quot; height=&quot;48&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;%EB%A7%88%EB%AC%B4%EB%A6%AC-1&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;Provider-defined functions&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenTofu에서는 Terraform에는 없는&amp;nbsp;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://opentofu.org/docs/language/functions/#provider-defined-functions&quot;&gt;&lt;b&gt;Provider-defined Functions&lt;/b&gt;&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;을 사용할 수 있습니다. provider 블록안에 함수를 지정해서 사용할 수 있습니다. 이 기능은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7; text-align: start;&quot;&gt;&lt;b&gt;데이터 소스를 사용해도 tf.statefile의 크기가 증가하지 않고 코드 작성이 덜 필요하기 때문에 데이터 소스를 사용하는 것에 비해 크게 개선&lt;/b&gt;&lt;/span&gt;된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;loopable-import-blocks&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;Loopable import blocks&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;aws의 리소스를 Terraform 혹은 OpenTofu로 관리하기 위해 terraform state(tfstate)파일을 생성해야 합니다. 하지만 현재 내 local에는 tfstate 파일이 없을 때 remote 공간에서 관리되는 tfstate 파일을 로컬로 가져와 관리하기 위해 import 기능을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;OpenTufo에서는 이러한 기본 기능에서 문제가 되었던&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;import할 resource가 여러 개일때 복잡하기에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://opentofu.org/docs/language/import/#importing-multiple-resources&quot;&gt;Importing multiple resources&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기능을 제공&lt;/b&gt;합니다. 이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;for_each문을 사용해서 여러 리소스를 가져올 수 있고, set, tuple 또는 map 형식으로 가져오며 개별 요소를 가져오기 위해 each.key, each.value 변수를 사용&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Cloud Architect/IaC</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/390</guid>
      <comments>https://nali.tistory.com/390#entry390comment</comments>
      <pubDate>Sun, 4 Aug 2024 00:42:23 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼으로 EKS 배포하기</title>
      <link>https://nali.tistory.com/388</link>
      <description>&lt;h2 style=&quot;color: #666666;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. Karpenter on EKS Fargate&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;code&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722095893176&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git clone https://github.com/aws-ia/terraform-aws-eks-blueprints
cd terraform-aws-eks-blueprints/patterns/karpenter
tree&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;VPC 배포&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722095933639&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# VPC 정보 확인
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml

# vpc 배포 : 3분 소요
terraform apply -target=&quot;module.vpc&quot; -auto-approve

# 배포 확인
terraform state list&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;1267&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvPXmq/btsIOEt9jO0/IW27JOemMAHEb1T0c4WjYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvPXmq/btsIOEt9jO0/IW27JOemMAHEb1T0c4WjYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvPXmq/btsIOEt9jO0/IW27JOemMAHEb1T0c4WjYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvPXmq%2FbtsIOEt9jO0%2FIW27JOemMAHEb1T0c4WjYk%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;1267&quot; height=&quot;310&quot; data-origin-width=&quot;1267&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EKS 배포&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;1258&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqiPIk/btsIQoi1sHJ/gsnd2KOLIyqdpBtIDiNZZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqiPIk/btsIQoi1sHJ/gsnd2KOLIyqdpBtIDiNZZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqiPIk/btsIQoi1sHJ/gsnd2KOLIyqdpBtIDiNZZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqiPIk%2FbtsIQoi1sHJ%2Fgsnd2KOLIyqdpBtIDiNZZ0%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;1258&quot; height=&quot;150&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2. EKS With Terraform&lt;/span&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;b&gt;사전 준비&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: awscli(IAM &amp;lsquo;관리자 수전&amp;rsquo; 자격증명), terraform, kubectl -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #d4bca7;&quot; href=&quot;https://aws-ia.github.io/terraform-aws-eks-blueprints/getting-started/&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;b&gt;코드 준비&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #d4bca7;&quot; href=&quot;https://github.com/aws-samples/eks-workshop-v2&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722092327317&quot; class=&quot;awk&quot; style=&quot;color: #000000;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#
git clone https://github.com/aws-samples/eks-workshop-v2
cd eks-workshop-v2/cluster/terraform

# 각각 .tf 파일 확인해보기&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722092327318&quot; class=&quot;nix&quot; style=&quot;color: #000000;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;locals {
  tags = {
    created-by = &quot;eks-workshop-v2&quot;
    study      = &quot;t101&quot;
    env        = var.cluster_name
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722092327319&quot; class=&quot;properties&quot; style=&quot;color: #000000;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;terraform init
tree .terraform
cat .terraform/modules/modules.json | jq
tree .terraform/providers/registry.terraform.io/hashicorp -L 2&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722092327319&quot; class=&quot;pf&quot; style=&quot;color: #000000;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# VPC 정보 확인
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml

# vpc 배포 : 3분 소요
terraform apply -target=&quot;module.vpc&quot; -auto-approve

# 배포 확인
terraform state list
terraform show
...

# VPC 정보 확인
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml

# 상세 정보 확인 : VPC/Subnet tag 정보 확인
echo &quot;data.aws_availability_zones.available&quot; | terraform console
terraform state show 'module.vpc.aws_vpc.this[0]'
VPCID=&amp;lt;각자 자신의 VPC ID&amp;gt;
aws ec2 describe-subnets --filters &quot;Name=vpc-id,Values=$VPCID&quot; | jq
aws ec2 describe-subnets --filters &quot;Name=vpc-id,Values=$VPCID&quot; --output text

# public 서브넷과 private 서브넷 CIDR 확인
## private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 3, k + 3)]
## public_subnets  = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 3, k)]
## private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
## public_subnets  = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)]
terraform state show 'module.vpc.aws_subnet.public[0]'
terraform state show 'module.vpc.aws_subnet.private[0]'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x5PxJ/btsIPzeAq6l/dFUsuVR1se0arBZGYFF1C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x5PxJ/btsIPzeAq6l/dFUsuVR1se0arBZGYFF1C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x5PxJ/btsIPzeAq6l/dFUsuVR1se0arBZGYFF1C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx5PxJ%2FbtsIPzeAq6l%2FdFUsuVR1se0arBZGYFF1C1%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;296&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;배포&lt;/p&gt;
&lt;pre id=&quot;code_1722092327323&quot; class=&quot;pf&quot; style=&quot;color: #000000;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# EKS 배포 : 11분 소요
terraform apply -auto-approve

# 배포 확인
terraform state list

terraform show

#
terraform output
configure_kubectl = &quot;aws eks --region ap-northeast-2 update-kubeconfig --name t101-karpenter&quot;

# EKS 자격증명
## aws eks --region &amp;lt;REGION&amp;gt; update-kubeconfig --name &amp;lt;CLUSTER_NAME&amp;gt; --alias &amp;lt;CLUSTER_NAME&amp;gt;
aws eks --region ap-northeast-2 update-kubeconfig --name eks-workshop
cat ~/.kube/config

# k8s 클러스터 정보 확인
kubectl cluster-info

# 각자 자신의 IAM User 의 access entry 생성
ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
MYIAMUSER=&amp;lt;각자 자신의 IAM User&amp;gt;
MYIAMUSER=admin

echo $ACCOUNT_ID $MYIAMUSER
aws eks create-access-entry --cluster-name eks-workshop --principal-arn arn:aws:iam::${ACCOUNT_ID}:user/${MYIAMUSER}
aws eks list-access-entries --cluster-name eks-workshop

# 각자 자신의 IAM User에 AmazonEKSClusterAdminPolicy 연동
aws eks associate-access-policy --cluster-name eks-workshop --principal-arn arn:aws:iam::${ACCOUNT_ID}:user/${MYIAMUSER} \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy --access-scope type=cluster

aws eks list-associated-access-policies --cluster-name eks-workshop --principal-arn arn:aws:iam::${ACCOUNT_ID}:user/${MYIAMUSER} | jq
aws eks describe-access-entry --cluster-name eks-workshop --principal-arn arn:aws:iam::${ACCOUNT_ID}:user/${MYIAMUSER} | jq
 
# (참고) context name 변경
kubectl config rename-context &quot;arn:aws:eks:ap-northeast-2:$(aws sts get-caller-identity --query 'Account' --output text):cluster/eks-workshop&quot; &quot;T101-Lab&quot;


# k8s 클러스터, 노드, 파드 정보 확인
kubectl cluster-info
kubectl get node
kubectl get nodes -L node.kubernetes.io/instance-type -L topology.kubernetes.io/zone
NAME                                               STATUS   ROLES    AGE   VERSION               INSTANCE-TYPE   ZONE
ip-10-42-124-1.ap-northeast-2.compute.internal     Ready    &amp;lt;none&amp;gt;   22m   v1.30.0-eks-036c24b   m5.large        ap-northeast-2a
ip-10-42-134-162.ap-northeast-2.compute.internal   Ready    &amp;lt;none&amp;gt;   22m   v1.30.0-eks-036c24b   m5.large        ap-northeast-2b
ip-10-42-167-206.ap-northeast-2.compute.internal   Ready    &amp;lt;none&amp;gt;   22m   v1.30.0-eks-036c24b   m5.large        ap-northeast-2c


# 상세 정보 확인
terraform state list
terraform state show 'module.eks.aws_ec2_tag.cluster_primary_security_group[&quot;study&quot;]'
terraform state show 'module.eks.aws_eks_addon.before_compute[&quot;vpc-cni&quot;]'
terraform state show 'module.eks.aws_eks_cluster.this[0]'
terraform state show 'module.eks.aws_iam_openid_connect_provider.oidc_provider[0]'
terraform state show 'module.eks.aws_iam_policy.cluster_encryption[0]'
terraform state show 'module.eks.time_sleep.this[0]'
terraform state show 'module.eks.module.eks_managed_node_group[&quot;default&quot;].aws_eks_node_group.this[0]'
terraform state show 'module.eks.module.eks_managed_node_group[&quot;default&quot;].aws_iam_role.this[0]'
terraform state show 'module.eks.module.eks_managed_node_group[&quot;default&quot;].aws_launch_template.this[0]'
terraform state show 'module.eks.module.eks_managed_node_group[&quot;default&quot;].module.user_data.null_resource.validate_cluster_service_cidr'
terraform state show 'module.eks.module.kms.aws_kms_key.this[0]'
terraform state show 'module.eks.module.kms.aws_kms_alias.this[&quot;cluster&quot;]'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHl5YV/btsIPMSpFlu/PY6L7zcZc6gobQJ1MTfkKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHl5YV/btsIPMSpFlu/PY6L7zcZc6gobQJ1MTfkKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHl5YV/btsIPMSpFlu/PY6L7zcZc6gobQJ1MTfkKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHl5YV%2FbtsIPMSpFlu%2FPY6L7zcZc6gobQJ1MTfkKK%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;330&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;330&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;1280&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baFRKk/btsIQnEqqhR/nx7TOmfiMQDpX4C7jSlkCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baFRKk/btsIQnEqqhR/nx7TOmfiMQDpX4C7jSlkCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baFRKk/btsIQnEqqhR/nx7TOmfiMQDpX4C7jSlkCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaFRKk%2FbtsIQnEqqhR%2Fnx7TOmfiMQDpX4C7jSlkCk%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;172&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;kube-ops-view 설치&lt;/p&gt;
&lt;pre id=&quot;code_1722092327330&quot; class=&quot;vim&quot; style=&quot;color: #000000;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# helm 배포
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ=&quot;Asia/Seoul&quot; --namespace kube-system

# 포트 포워딩
kubectl port-forward deployment/kube-ops-view -n kube-system 8080:8080

# 접속 주소 확인 : 각각 1배, 1.5배, 3배 크기
echo -e &quot;KUBE-OPS-VIEW URL = http://localhost:8080&quot;
echo -e &quot;KUBE-OPS-VIEW URL = http://localhost:8080/#scale=1.5&quot;
echo -e &quot;KUBE-OPS-VIEW URL = http://localhost:8080/#scale=3&quot;&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;1122&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yiBhh/btsIQJtBLa0/42MNesVLZHn7bQuhxQkVEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yiBhh/btsIQJtBLa0/42MNesVLZHn7bQuhxQkVEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yiBhh/btsIQJtBLa0/42MNesVLZHn7bQuhxQkVEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyiBhh%2FbtsIQJtBLa0%2F42MNesVLZHn7bQuhxQkVEK%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;1122&quot; height=&quot;402&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;삭제&lt;/p&gt;
&lt;pre id=&quot;code_1722092327331&quot; class=&quot;sql&quot; style=&quot;color: #000000;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# kube-ops-view 삭제
helm uninstall kube-ops-view -n kube-system

# 삭제 : vpc 삭제가 잘 안될 경우 aws 콘솔에서 vpc 수동 삭제 -&amp;gt; vnic 등 남아 있을 경우 해당 vnic 강제 삭제 : 9분 소요
terraform destroy -auto-approve

# VPC 삭제 확인 : 
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml

# kubeconfig 삭제
rm -rf ~/.kube/config&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;</description>
      <category>  Cloud Architect/IaC</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/388</guid>
      <comments>https://nali.tistory.com/388#entry388comment</comments>
      <pubDate>Sat, 27 Jul 2024 23:56:52 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] Module &amp;amp; Runner</title>
      <link>https://nali.tistory.com/386</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 모듈&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼으로 인프라와 서비스를 관리하면 시간이 지날수록 구성이 복잡해지고 관리하는 리소스가 늘어나게 된다. 테라폼의 구성 파일과 디렉터리 구성에는 제약이 없기 때문에 단일 파일 구조상에서 지속적으로 업데이트할 수 있지만, 다음과 같은 문제가 발생한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼 구성에서 원하는 항목을 찾고 수정하는 것이 점점 어려워짐&lt;/li&gt;
&lt;li&gt;리소스들 간의 연관 관계가 복잡해질수록 변경 작업의 영향도를 분석하기 위한 노력이 늘어남&lt;/li&gt;
&lt;li&gt;개발/스테이징/프로덕션 환경으로 구분된 경우 비슷한 형태의 구성이 반복되어 업무 효율이 줄어듦&lt;/li&gt;
&lt;li&gt;새로운 프로젝트를 구성하는 경우 기존 구성에서 취해야 할 리소스 구성과 종속성 파악이 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈은 루트 모듈과 자식 모듈로 구분된다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;루트 모듈&lt;/b&gt; Root Module : 테라폼을 실행하고 프로비저닝하는 최상위 모듈&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자식 모듈&lt;/b&gt; Chile Module : 루트 모듈의 구성에서 호출되는 외부 구성 집합&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈은 테라폼 &lt;b&gt;구성의 집합&lt;/b&gt;이다. 테라폼으로 관리하는 대상의 규모가 커지고 복잡해져 생긴 문제를 보완하고 관리 작업을 수월하게 하기 위한 방안으로 활용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;관리성&lt;/b&gt; : 모듈은 서로 연관 있은 구성의 묶음이다. 원하는 구성요소를 단위별로 쉽게 찾고 업데이트할 수 있다. 모듈은 다른 구성에서 쉽게 하나의 덩어리로 추가하거나 삭제할 수 있다. 또한 모듈이 업데이트되면 이 모듈을 사용하는 모든 구성에서 일관된 변경 작업을 진행할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캡슐화&lt;/b&gt; : 테라폼 구성 내에서 각 모듈은 논리적으로 묶여져 독립적으로 프로비저닝 및 관리되며, 그 결과는 은닉성을 갖춰 필요한 항목만을 외부에 노출시킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재사용성&lt;/b&gt; : 구성을 처음부터 작성하는 것에는 시간과 노력이 필요하고 작성 중간에 디버깅과 오류를 수정하는 반복 작업이 발생한다. 테라폼 구성을 모듈화하면 이후에 비슷한 프로비저닝에 이미 검증된 구성을 바로 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관성&lt;/b&gt;과 &lt;b&gt;표준화&lt;/b&gt; : 테라폼 구성 시 모듈을 활용하는 워크플로는 구성의 일관성을 제공하고 서로 다른 환경과 프로젝트에도 이미 검증한 모듈을 적용해 복잡한 구성과 보안 사고를 방지할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-1. 모듈 작성 기본 원칙&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기본 원칙&lt;/b&gt; : 모듈은 대부분의 프로그래밍 언어에서 쓰이는 라이브러리나 패키지와 역할이 비슷하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈 디렉터리 형식을 terraform-&amp;lt;프로바이더 이름&amp;gt;-&amp;lt;모듈 이름&amp;gt; 형식을 제안한다. 이 형식은 Terraform Cloud, Terraform Enterprise에서도 사용되는 방식으로 1) 디렉터리 또는 레지스트리 이름이 테라폼을 위한 것이고, 2) 어떤 프로바이더의 리소스를 포함하고 있으며, 3) 부여된 이름이 무엇인지 판별할 수 있도록 한다.&lt;/li&gt;
&lt;li&gt;테라폼 구성은 궁극적으로 &lt;b&gt;모듈화가 가능한 구조로 작성&lt;/b&gt;할 것을 제안한다. 처음부터 모듈화를 가정하고 구성파일을 작성하면 단일 루트 모듈이라도 후에 다른 모듈이 호출할 것을 예상하고 구조화할 수 있다. 또한 작성자는 의도한 리소스 묶음을 구상한 대로 논리적인 구조로 그룹화할 수 있다.&lt;/li&gt;
&lt;li&gt;각각의 &lt;b&gt;모듈을 독립적으로 관리하기&lt;/b&gt;를 제안한다. 리모트 모듈을 사용하지 않더라도 처음부터 모듈화가 진행된 구성들은 떄로 루트 모듈의 하위 파일 시스템에 존재하는 경우가 있다. 하위 모듈 또한 독립적인 모듈이므로 루트 모듈 하위에 두기보다는 동일한 파일 시스템 레벨에 위치하거나 별도 모듈만을 위한 공간에서 불러오는 것을 권장한다. 이렇게 하면 VCS를 통해 관리하기가 더 수월하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공개된 테라폼 레지스트리의 모듈&lt;/b&gt;을 참고하기를 제안한다. 대다수의 테라폼 모듈은 공개된 모듈이 존재하고 거의 모든 인수에 대한 변수 처리, 반복문 적용 리소스, 조건에 따른 리소스 활성/비활성 등을 모범 사례로 공개해두었다. 물론 그대로 가져다 사용하는 것보다는 프로비저닝하려는 상황에 맞게 참고하는 것을 권장한다.&lt;/li&gt;
&lt;li&gt;작성된 모듈은 공개 또는 비공개로 게시해 팀 또는 커뮤니티와 &lt;b&gt;공유하기&lt;/b&gt;를 제안한다. 모듈의 사용성을 높이고 피드백을 통해 더 발전된 모듈을 구성할 수 있는 자극이 된다.&lt;/li&gt;
&lt;li&gt;모듈을 독립적으로 관리하기 위해 디렉터리 구조를 생성할 때 모듈을 위한 별도 공간을 생성하는 방식으로 진행한다. 특정 &lt;b&gt;루트 모듈 하위에 자식 모듈을 구성&lt;/b&gt;하는 경우 단순히 복잡한 코드를 분리하는 요도로 명시되며 종속성이 발생하므로 루트 모듈 사이에 &lt;b&gt;모듈 디렉터리&lt;/b&gt;를 지정한다. 아래 구성의 예.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아래와 같은 기본 작성 원칙을 제안함&lt;/b&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;885&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clj9PB/btsIzTpw9Nn/Ndknw3LjgteMMkvNEbBhS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clj9PB/btsIzTpw9Nn/Ndknw3LjgteMMkvNEbBhS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clj9PB/btsIzTpw9Nn/Ndknw3LjgteMMkvNEbBhS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclj9PB%2FbtsIzTpw9Nn%2FNdknw3LjgteMMkvNEbBhS1%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;885&quot; height=&quot;331&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.2 모듈화 해보기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈의 기본적 구조는 테라폼 구성으로 &lt;b&gt;입력 변수&lt;/b&gt;를 구성하고 &lt;b&gt;결과를 출력&lt;/b&gt;하기 위한 구조로 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lsquo;&lt;b&gt;모듈화&lt;/b&gt;&amp;rsquo;라는 용어는 이런 &lt;b&gt;구조&lt;/b&gt;를 &lt;b&gt;재활용&lt;/b&gt;하기 위한 템플릿 작업을 말한다.&lt;/li&gt;
&lt;li&gt;애플리케이션 개발시에도 자주 사용되는 용어로 테라폼은 &lt;b&gt;작성된 모듈&lt;/b&gt;을 다른 &lt;b&gt;루트 모듈에서 가져다 사용&lt;/b&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;916&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWHm9w/btsIyvKar5Q/0laHE2cN0NzYLVSRLKy1kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWHm9w/btsIyvKar5Q/0laHE2cN0NzYLVSRLKy1kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWHm9w/btsIyvKar5Q/0laHE2cN0NzYLVSRLKy1kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWHm9w%2FbtsIyvKar5Q%2F0laHE2cN0NzYLVSRLKy1kK%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;916&quot; height=&quot;627&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;627&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습은 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1720894846974&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 자식 모듈 작성
module &quot;mypw1&quot; {
  source = &quot;../modules/terraform-random-pwgen&quot;
}

module &quot;mypw2&quot; {
  source = &quot;../modules/terraform-random-pwgen&quot;
  isDB   = true
}

output &quot;mypw1&quot; {
  value  = module.mypw1
}

output &quot;mypw2&quot; {
  value  = module.mypw2
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1720894865177&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 자식 모듈 호출
terraform init &amp;amp;&amp;amp; terraform plan &amp;amp;&amp;amp; terraform apply -auto-approve
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
mypw1 = {
  &quot;id&quot; = &quot;equipped-mustang&quot;
  &quot;pw&quot; = &quot;OXST1EYqQc&quot;
}
mypw2 = {
  &quot;id&quot; = &quot;diverse-impala&quot;
  &quot;pw&quot; = &quot;y8mEbOJhS6dCTiK#&quot;
}

# 확인
terraform state list

# tfstate에 모듈 정보 확인 : VSCODE에서 terraform.tfstate 파일 확인
cat terraform.tfstate | grep module

# terraform init 시 생성되는 modules.json 파일 확인
tree .terraform
.terraform
├── modules
│   └── modules.json
...

## 모듈로 묶여진 리소스는 module이라는 정의를 통해 단순하게 재활용하고 반복 사용할 수 있다.
## 모듈의 결과 참조 형식은 module.&amp;lt;모듈 이름&amp;gt;.&amp;lt;output 이름&amp;gt;으로 정의된다.
cat .terraform/modules/modules.json | jq
{
  &quot;Modules&quot;: [
    {
      &quot;Key&quot;: &quot;&quot;,
      &quot;Source&quot;: &quot;&quot;,
      &quot;Dir&quot;: &quot;.&quot;
    },
    {
      &quot;Key&quot;: &quot;mypw1&quot;,
      &quot;Source&quot;: &quot;../modules/terraform-random-pwgen&quot;,
      &quot;Dir&quot;: &quot;../modules/terraform-random-pwgen&quot;
    },
    {
      &quot;Key&quot;: &quot;mypw2&quot;,
      &quot;Source&quot;: &quot;../modules/terraform-random-pwgen&quot;,
      &quot;Dir&quot;: &quot;../modules/terraform-random-pwgen&quot;
    }
  ]
}

# graph 확인
terraform graph &amp;gt; graph.dot&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;6.3 모듈 사용 방식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모듈과 프로바이더&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈에서 사용되는 모든 리소스는 관련 프로바이더의 정의가 필요하다. 여기서 사용자는 프로바이더 정의를 모듈 안 or 밖 고민&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nMHOt/btsIyYZqiMB/KZ1kRc7m3bZEoWVL6L8uR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nMHOt/btsIyYZqiMB/KZ1kRc7m3bZEoWVL6L8uR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nMHOt/btsIyYZqiMB/KZ1kRc7m3bZEoWVL6L8uR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnMHOt%2FbtsIyYZqiMB%2FKZ1kRc7m3bZEoWVL6L8uR1%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;910&quot; height=&quot;265&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유형 1. 자식 모듈에서 프로바이더 정의&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈에서 사용하는 프로바디어 버전과 구성 상세를 자식 모듈에서 고정하는 방법이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로바이더 버전과 구성에 민감하거나, 루트 모듈에서 프로바이더 정의 없이 자식 모듈이 독립적인 구조일 때 고려할 방법이다&lt;/li&gt;
&lt;li&gt;하지만 동일한 프로바이더가 루트와 자식 양쪽에 또는 서로 다른 자식 모듈에 버전 조건 합의가 안 되면, 오류가 발생하고 모듈에 반복문을 사용할 수 없다는 단점이 있으므로 잘 &lt;b&gt;사용하지 않는다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유형 2. 루트 모듈에서 프로바이더 정의(실습)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자식 모듈&lt;/b&gt;은 &lt;b&gt;루트 모듈&lt;/b&gt;의 &lt;b&gt;프로바이더 구성에 종속&lt;/b&gt;되는 방식이다.&lt;/li&gt;
&lt;li&gt;디렉터리 구조로는 분리되어 있지만 테라폼 실행 단계에서 동일 계층으로 해석되므로 프로바이더 버전과 구성은 루트 모듈의 설정이 적용된다. 프로바이더를 모듈 내 리소스와 데이터 소스에 일괄 적용하고, 자식 모듈에 대한 반복문 사용에 자유로운 것이 장점이다. 자식 모듈에 특정 프로바이더 구성의 종속성은 반영할 수 없으므로 자식 모듈을 프로바이더 조건에 대해 기록하고, 자식 모듈을 사용하는 루트 모듈에서 정의하는 프로바이더에 맞게 업데이트 해야 한다.&lt;/li&gt;
&lt;li&gt;다음은 동일한 모듈에 사용되는 프로바이더 조건이 다른 경우 각 모듈별로 프로바이더를 맵핑하는 방안이다.&lt;/li&gt;
&lt;li&gt;리소스와 데이터 소스에 provider 메타인수로 지정하는 방식과 비슷하나 모듈에는 다수의 프로바이더가 사용될 가능성이 있으므로 map 타입으로 구성하는 provider로 정의한다. 실습을 위한 디렉터리 구성의 예는 다음과 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디렉터리 생성 및 06-module-traning/modules/terraform-aws-ec2/&lt;span data-token-index=&quot;1&quot;&gt;main.tf&lt;/span&gt; &lt;span data-token-index=&quot;3&quot;&gt;variable.tf output.tf&lt;/span&gt; 파일 생성&lt;/p&gt;
&lt;pre id=&quot;code_1720895269515&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir -p 06-module-traning/modules/terraform-aws-ec2/
cd 06-module-traning/modules/terraform-aws-ec2/
touch main.tf variable.tf output.tf

terraform {
  required_providers {
    aws = {
      source = &quot;hashicorp/aws&quot;
    }
  }
}

resource &quot;aws_default_vpc&quot; &quot;default&quot; {}

data &quot;aws_ami&quot; &quot;default&quot; {
  most_recent = true
  owners      = [&quot;amazon&quot;]

  filter {
    name   = &quot;owner-alias&quot;
    values = [&quot;amazon&quot;]
  }

  filter {
    name   = &quot;name&quot;
    values = [&quot;amzn2-ami-hvm*&quot;]
  }
}

resource &quot;aws_instance&quot; &quot;default&quot; {
  depends_on    = [aws_default_vpc.default]
  ami           = data.aws_ami.default.id
  instance_type = var.instance_type

  tags = {
    Name = var.instance_name
  }
}

# variable.tf
variable &quot;instance_type&quot; {
  description = &quot;vm 인스턴스 타입 정의&quot;
  default     = &quot;t2.micro&quot;
}

variable &quot;instance_name&quot; {
  description = &quot;vm 인스턴스 이름 정의&quot;
  default     = &quot;my_ec2&quot;
}

# output.tf
output &quot;private_ip&quot; {
  value = aws_instance.default.private_ip
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;작성된 모듈을 사용할 루트 모듈 디렉터리 생성 및 파일 생성&lt;/p&gt;
&lt;pre id=&quot;code_1720895279412&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;
mkdir -p 06-module-traning/multi_provider_for_module/
cd 06-module-traning/multi_provider_for_module/
touch main.tf output.tf
    
# main.tf
provider &quot;aws&quot; {
  region = &quot;ap-southeast-1&quot;  
}

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

module &quot;ec2_singapore&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;t3.small&quot;
}
    
# output.tf
output &quot;module_output_singapore&quot; {
  value = module.ec2_singapore.private_ip
}

output &quot;module_output_seoul&quot; {
  value = module.ec2_seoul.private_ip
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행&lt;/p&gt;
&lt;pre id=&quot;code_1720895343200&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#
cd 06-module-traning/multi_provider_for_module/
terraform init
cat .terraform/modules/modules.json | jq

#
terraform apply -auto-approve
terraform output
terraform state list
terraform state show module.ec2_seoul.data.aws_ami.default
terraform state show module.ec2_singapore.data.aws_ami.default

# tfstate에 모듈 정보 확인 : VSCODE에서 terraform.tfstate 파일 확인
cat terraform.tfstate | grep module

# graph 확인
terraform graph &amp;gt; graph.dot

# aws cli로 ec2 확인
aws ec2 describe-instances --region ap-northeast-2 --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output text
aws ec2 describe-instances --region ap-southeast-1 --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output text

# 실습 완료 후 리소스 삭제
terraform destroy -auto-approve&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모듈 반복문&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈 또한 리소스에서 반복문을 사용하듯 구성할 수 있다.&lt;/li&gt;
&lt;li&gt;모듈이라는 리소스 정의 묶음을 원하는 수량으로 프로비저닝할 수 있으므로 모듈 없이 구성하는 것과 대비해 리소스 종속성 관리와 유지 보수에 장점이 있다. count를 사용한 반복문 사용은 리소스에서의 사용 방식처럼 module 블록 내에 선언한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1720895436701&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;provider &quot;aws&quot; {
  region = &quot;ap-northeast-2&quot;  
}

module &quot;ec2_seoul&quot; {
  count  = 2
  source = &quot;../modules/terraform-aws-ec2&quot;
  instance_type = &quot;t3.small&quot;
}

output &quot;module_output&quot; {
  value  = module.ec2_seoul[*].private_ip   
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720895450688&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#
cd 06-module-traning/module_loop_count/
terraform init
cat .terraform/modules/modules.json | jq

#
terraform apply -auto-approve
terraform output
terraform state list

# tfstate에 모듈 정보 확인 : VSCODE에서 terraform.tfstate 파일 확인
cat terraform.tfstate | grep module

# graph 확인
terraform graph &amp;gt; graph.dot

# aws cli로 ec2 확인
aws ec2 describe-instances --region ap-northeast-2 --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output text

# 실습 완료 후 리소스 삭제
terraform destroy -auto-approve&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈 묶음에 &lt;b&gt;일관된 구성과 구조&lt;/b&gt;로 프로비저닝이 되는 경우라면 &lt;b&gt;count&lt;/b&gt;가 간편한 방안이지만, 동일한 모듈 구성에 &lt;b&gt;필요한 인수 값이 다르다&lt;/b&gt;면 &lt;b&gt;for_each&lt;/b&gt;를 활용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.4 모듈 소스 관리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Module 블록에 정의된 소스 구성으로 모듈의 코드 위치를 정의한다. terraform &lt;b&gt;init&lt;/b&gt;을 수행할 때 지정된 모듈을 다운로드해 사용한다 - &lt;a href=&quot;https://developer.hashicorp.com/terraform/language/modules/sources&quot;&gt;링크&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 디렉터리 경로&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테라폼 레지스트리&lt;/b&gt; Terraform Registry&lt;/li&gt;
&lt;li&gt;&lt;b&gt;깃허브&lt;/b&gt; Github&lt;/li&gt;
&lt;li&gt;비트버킷 Bitbucket&lt;/li&gt;
&lt;li&gt;깃 Generic Git Repository&lt;/li&gt;
&lt;li&gt;HTTP URLs&lt;/li&gt;
&lt;li&gt;S3 Bucket&lt;/li&gt;
&lt;li&gt;GCS Bucket google cloud storage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테라폼 공식 문서 안내와 같이 루트 경로에 모듈을 배치하는 것 외에 패키지 하위 디렉터리 경로를 참조하는 것도 하나의 방안이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로컬 디렉터리 경로&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 경로를 지정할 때는 테라폼 레지스트리와 구분하기 위해 하위 디렉터리는 ./로, 상위 디렉터리는 ../로 시작한다.&lt;/li&gt;
&lt;li&gt;대상 모듈은 이미 같은 로컬 파일 시스템에 존재하므로 다운로드 없이 바로 사용한다. 앞서 언급한 대로 재사용성이 고려된다면 상위 디렉터리에 별도 관리하는 것을 권장하고, 항상 루트 모듈과 함께 동작해야 하는 경우 하위 디렉터리에 모듈을 정의한다.&lt;/li&gt;
&lt;li&gt;코드 예시) 작업 환경을 기준으로 하는 로컬 경로 상위에 위치한 모듈 경로 선언&lt;/li&gt;
&lt;li&gt;module &quot;local_module&quot; { source = &quot;../modules/my_local_module&quot; }&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테라폼 레지스트리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼 모듈 레지스트리는 테라폼의 프로토콜을 사용해 모듈을 사용하는 방식이다.&lt;/li&gt;
&lt;li&gt;공개된 테라폼 모듈을 사용하거나 Terraform Cloud, Terraform Enterprise에서 제공되는 비공개 테라폼 모듈을 사용할 때 설정하는 소스 지정 방식이다.&lt;/li&gt;
&lt;li&gt;공개된 모듈 - &lt;a href=&quot;https://registry.terraform.io/browse/modules&quot;&gt;링크&lt;/a&gt; &lt;a href=&quot;https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest&quot;&gt;aws_vpc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;공개된 테라폼 모듈을 source에 선언할 때는 &amp;lt;네임스페이스&amp;gt;/&amp;lt;이름&amp;gt;/&amp;lt;프로바이더&amp;gt; 형태로 설정한다.&lt;/li&gt;
&lt;li&gt;module &quot;vpc&quot; { source = &quot;terraform-aws-modules/vpc/aws&quot; version = &quot;5.1.0&quot; }&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.5 Terraform Runner&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;소개&lt;/span&gt; : Terraform Pull Request Automation - &lt;span data-token-index=&quot;2&quot;&gt;Running&lt;/span&gt; Terraform &lt;span data-token-index=&quot;4&quot;&gt;Workflows&lt;/span&gt; with Ease - &lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.runatlantis.io/&quot; data-token-index=&quot;6&quot;&gt;&lt;span&gt;Link&lt;/span&gt;&lt;/a&gt; &lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/runatlantis/atlantis&quot; data-token-index=&quot;8&quot;&gt;&lt;span&gt;Github&lt;/span&gt;&lt;/a&gt; &lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.runatlantis.io/blog.html&quot; data-token-index=&quot;10&quot;&gt;&lt;span&gt;Blog&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720895570423&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;Welcome to Our Blog | Atlantis&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.runatlantis.io&quot; data-og-source-url=&quot;https://www.runatlantis.io/blog.html&quot; data-og-url=&quot;https://www.runatlantis.io/blog.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.runatlantis.io/blog.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.runatlantis.io/blog.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;Welcome to Our Blog | Atlantis&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.runatlantis.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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git에 업로드 시 인프라에 자동 배포&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Cloud Architect/IaC</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/386</guid>
      <comments>https://nali.tistory.com/386#entry386comment</comments>
      <pubDate>Sun, 14 Jul 2024 02:40:32 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 State</title>
      <link>https://nali.tistory.com/385</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.1 State의 목적과 의미&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상태 파일&lt;/b&gt;은 배포할 때마다 변경되는 &lt;b&gt;프라이빗 API&lt;/b&gt; private API로, 오직 테라폼 내부에서 사용하기 위한 것입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테라폼 상태 파일&lt;/b&gt;을 &lt;b&gt;직접 편집하거나 직접 읽는 코드로 작성해서는 안됩&lt;/b&gt;니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;팀 단위에서 테라폼 운영 시 문제점&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상태 파일을 저장하는 &lt;b&gt;공유 스토리지&lt;/b&gt; Shared storage for state files
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 팀원이 &lt;b&gt;동일한 테라폼 상태 파일 사용&lt;/b&gt;을 위해서, 공유 위치에 저장이 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상태 &lt;b&gt;파일 잠금&lt;/b&gt; Locking state files
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잠금 기능 없이 두 팀원이 동시에 테라폼 실행 시 여러 테라폼 프로세스가 상태 파일을 동시에 업데이트하여 &lt;b&gt;충돌 가능&lt;/b&gt;(경쟁 상태 race condition)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상태 &lt;b&gt;파일 격리&lt;/b&gt; Isolating state files
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들면 테스트 dev 와 검증 stage 과 상용 prodction &lt;b&gt;각 환경에 대한 격리&lt;/b&gt;가 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;상태 파일 공유로 버전 관리 시스템 비추천&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;수동 오류&lt;/b&gt; Manual error
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼을 실행하기 전에 최신 변경 사항을 가져오거나 실행하고 나서 push 하는 것을 잊기 쉽습니다(?).&lt;/li&gt;
&lt;li&gt;팀의 누군가가 이전 버전의 상태 파일로 테라폼을 실행하고, 그 결과 실수로 이전 버전으로 롤백하거나 이전에 배포된 인프라를 복제하는 문제가 발생 할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;잠금&lt;/b&gt; Locking
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 버전 관리 시스템은 여러 명의 팀 구성원이 동시에 하나의 상태 파일에 terraform apply 명령을 실행하지 못하게 하는 잠금 기능이 제공되지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시크릿&lt;/b&gt; Secrets
&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;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원되는 원격 백엔드 : AWS S3, Azure Blob Storage, Google Cloud Storage, Consul, Postgres database, k8s secret 등 - &lt;a href=&quot;https://developer.hashicorp.com/terraform/language/settings/backends/local&quot;&gt;링크&lt;/a&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수동 오류 해결 : plan/apply 실행 시 마다 해당 백엔드에서 파일을 자동을 로드, apply 후 상태 파일을 백엔드에 자동 저장&lt;/li&gt;
&lt;li&gt;잠금 : apply 실행 시 테라폼은 자동으로 잠금을 활성화, -lock-timout=&amp;lt;TIME&amp;gt; 로 대기 시간 설정 지정 가능&lt;/li&gt;
&lt;li&gt;시크릿 : 대부분 원격 백엔드는 기본적으로 데이터를 보내거나 상태 파일을 저장할 때 암호화하는 기능을 지원&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;상태 확인 실습&lt;/h4&gt;
&lt;pre id=&quot;code_1720309257954&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 파일 내용을 바꾸며 재배포
terraform plan &amp;amp;&amp;amp; terraform apply -auto-approve

# 상태 파일 비교(바로 직전 상태 백업)
ls terraform.tfstate*
terraform.tfstate        terraform.tfstate.backup

diff terraform.tfstate terraform.tfstate.backup
&amp;lt;   &quot;serial&quot;: 6,
---
&amp;gt;   &quot;serial&quot;: 4,
...&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;5.2 State 동기화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼 구성 파일은 기존 State와 구성을 비교해 실행 계획에서 생성, 수정, 삭제 여부를 결정한다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTf52O/btsIp887dXo/7MrdPibJMOoXwKtlDxfDNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTf52O/btsIp887dXo/7MrdPibJMOoXwKtlDxfDNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTf52O/btsIp887dXo/7MrdPibJMOoXwKtlDxfDNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTf52O%2FbtsIp887dXo%2F7MrdPibJMOoXwKtlDxfDNK%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;894&quot; height=&quot;409&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;409&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;테라폼 구성과 State 흐름 : Plan 과 Apply 중 각 리소스에 발생할 수 있는 네 가지 사항, 아래 실행 계획 출력 기호와 의미기호 의미
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;Create&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Destroy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-/+&lt;/td&gt;
&lt;td&gt;Replace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~&lt;/td&gt;
&lt;td&gt;Updated in-place&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Replace 동작은 기본값을 삭제 후 생성하지만 lifecycle의 create_before_destroy 옵션을 통해 생성 후 삭제 설정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유형별 실습&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유형 별 실습 + 문제상황 &amp;rarr; 복구 import&lt;/b&gt;* : 테라폼 구성에 추가된 리소스와 State에 따라 어떤 동작이 발생하는지 다음 표로 살펴본다유형 구성 리소스 정의(*.tf) State 구성 데이터 실제 리소스 기본 예상 동작&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;리소스 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;리소스 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;동작 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;리소스 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;td&gt;동작 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;유형1 : 신규 리소스 정의 &amp;rarr; Apply &amp;rArr; 리소스 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;locals {
  name = &quot;mytest&quot;
}

resource &quot;aws_iam_user&quot; &quot;myiamuser1&quot; {
  name = &quot;${local.name}1&quot;
}

resource &quot;aws_iam_user&quot; &quot;myiamuser2&quot; {
  name = &quot;${local.name}2&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1720309542446&quot; class=&quot;dsconfig&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;# 실행
terraform init &amp;amp;&amp;amp; terraform apply -auto-approve
terraform state list
terraform state show aws_iam_user.myiamuser1

#
ls *.tfstate
cat terraform.tfstate | jq

# 아래 실행 시 어떻게 되나? 테라폼은 멱등성 한가?
terraform apply -auto-approve
ls *.tfstate

# iam 사용자 리스트 확인
aws iam list-users | jq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유형2 : 실제 리소스 수동 제거 &amp;rarr; Apply &amp;rArr; 리소스 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720309609526&quot; class=&quot;dsconfig&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;# 실제 리소스 수동 제거
aws iam delete-user --user-name mytest1
aws iam delete-user --user-name mytest2
aws iam list-users | jq
terraform state list

# 아래 명령어 실행 결과 차이는?
terraform plan
terraform plan -refresh=false
cat terraform.tfstate | jq .serial

#
terraform apply -auto-approve
terraform state list
cat terraform.tfstate | jq .serial

# iam 사용자 리스트 확인
aws iam list-users | jq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유형3 : Apply &amp;rarr; Apply &amp;larr;&lt;/b&gt; 코드, State, 형상 모두 일치한 경우&lt;/p&gt;
&lt;pre id=&quot;code_1720309627132&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#
terraform apply -auto-approve
cat terraform.tfstate | jq .serial

terraform apply -auto-approve
cat terraform.tfstate | jq .serial

terraform apply -auto-approve
cat terraform.tfstate | jq .serial&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유형4 : 코드에서 일부 리소스 삭제 &amp;rarr; Apply&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720309646071&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# main.tf 파일 수정
locals {
  name = &quot;mytest&quot;
}

resource &quot;aws_iam_user&quot; &quot;myiamuser1&quot; {
  name = &quot;${local.name}1&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1720309663605&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실행
terraform apply -auto-approve
terraform state list
terraform state show aws_iam_user.myiamuser1

#
ls *.tfstate
cat terraform.tfstate | jq

# iam 사용자 리스트 확인
aws iam list-users | jq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유형6&lt;/b&gt; : &lt;b&gt;실수로 tfstate 파일 삭제 &amp;rarr; plan/apply&lt;/b&gt; &lt;b&gt;&amp;larr; 책에는 없는 내용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720309711682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실수로 tfstate 파일 삭제
terraform state list
aws_iam_user.myiamuser1

rm -rf terraform.tfstate*

#
terraform plan
aws iam list-users | jq
terraform plan -refresh=false

#
terraform apply -auto-approve
terraform state list
cat terraform.tfstate | jq

# iam 사용자 리스트 확인
aws iam list-users | jq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rArr; 위 상황에서 복구? 하는 방법은? import 등 방법이 있습니다!&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유형7&lt;/b&gt; : 실수로 tfstate 파일 삭제 시 &amp;rarr; import 로 tfstate 파일 복구 - &lt;a href=&quot;https://developer.hashicorp.com/terraform/cli/import&quot;&gt;Docs&lt;/a&gt; &lt;a href=&quot;https://developer.hashicorp.com/terraform/tutorials/state/state-import&quot;&gt;Tutorials&lt;/a&gt;&lt;a href=&quot;https://malwareanalysis.tistory.com/633&quot;&gt;https://malwareanalysis.tistory.com/633&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720309781065&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실행
# iam 사용자 리스트 확인
$ aws iam list-users | jq

# import 도움말 : 빨간색 설명 출력...
$ terraform import

# ADDR은 리소스주소 , ID는 
# terraform [global options] import [options] ADDR ID
$ terraform import aws_iam_user.myiamuser1 mytest1
aws_iam_user.myiamuser1: Importing from ID &quot;mytest1&quot;...
aws_iam_user.myiamuser1: Import prepared!
  Prepared aws_iam_user for import
aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

#
$ terraform state list
$ cat terraform.tfstate | jq
$ terraform apply -auto-approve&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 실습을 위해 삭제&lt;/b&gt;: &lt;b&gt;terraform destroy -auto-approve&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.3 Terraform Backend&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1720309996018&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;backend &quot;s3&quot; {
    #bucket         = &quot;&amp;lt;닉네임&amp;gt;-hello-t1014-remote-backend&quot;
    bucket         = &quot;gasida-hello-t1014-remote-backend&quot;
    key            = &quot;terraform/state-test/terraform.tfstate&quot;
    region         = &quot;ap-northeast-2&quot;
    #dynamodb_table = &quot;terraform-lock&quot; 주석처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼의 backend 블록에는 변수나 참조를 사용 할 수 없음 &amp;rarr; 아래 코드 사용할 수 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 결과 S3 버킷 이름, 리전, DynamoDB 테이블 이름을 모두 테라폼 모듈에 수동으로 복사붙여녛어야 함, 심지어 &lt;b&gt;key&lt;/b&gt; 값은 중복되면 안되며 고유하게 넣어야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;partial configuration&lt;/b&gt; 을 통해 일부 매개 변수를 전달해서 사용 할 수 있음 - &lt;a href=&quot;https://developer.hashicorp.com/terraform/language/settings/backends/configuration#partial-configuration&quot;&gt;링크&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만, 이 경우에도 모듈마다 서로 다른 key 값을 설정해야 하기 때문에 key 매개 변수는 테라폼 코드에 있어야함&lt;/li&gt;
&lt;li&gt;부분적으로 구성한 것들을 모두 결합하려면 -backend-config 인수와 함께 terraform init 명령을 실행&lt;/li&gt;
&lt;li&gt;terraform init -backend-config=backend.hcl&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 위 단점을 보완해주는 오픈 소스 &lt;b&gt;테라그런트&lt;/b&gt;(Terragrunt)가 있음&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;5.4 워크스페이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상태 파일 격리 소개 State File Isolation&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼 상태를 격리하지 않았을 때 발생하는 일 - &lt;a href=&quot;https://charity.wtf/2016/03/30/terraform-vpc-and-why-you-want-a-tfstate-file-per-env/&quot;&gt;링크&lt;/a&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;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;작업 공간을 통한 격리&lt;/b&gt; Isolation via &lt;b&gt;workspaces&lt;/b&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;/li&gt;
&lt;li&gt;&lt;b&gt;파일 레이아웃을 이용한 격리&lt;/b&gt; Isolation via &lt;b&gt;file layout&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보다 강력하게 분리해야 하는 운영 환경에 적합&lt;/li&gt;
&lt;li&gt;테라폼 프로젝트의 &lt;b&gt;파일 레이아웃 설명&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 테라폼 &lt;b&gt;구성 파일&lt;/b&gt;을 분리된 폴더에 넣기. (예. stage , prod)&lt;/li&gt;
&lt;li&gt;각 환경에 서로 &lt;b&gt;다른 백엔드&lt;/b&gt; 구성. (예. S3 버킷 백엔드의 AWS 계정을 분리)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[예시]&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (2).png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;4159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XU5VI/btsIpXNweZJ/HlB5TW4AMrb7gJPTLStsuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XU5VI/btsIpXNweZJ/HlB5TW4AMrb7gJPTLStsuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XU5VI/btsIpXNweZJ/HlB5TW4AMrb7gJPTLStsuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXU5VI%2FbtsIpXNweZJ%2FHlB5TW4AMrb7gJPTLStsuK%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;4159&quot; data-filename=&quot;Untitled (2).png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;4159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 폴더&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;stage&lt;/b&gt; : 테스트 환경과 같은 사전 프로덕션 워크로드 workload 환경&lt;/li&gt;
&lt;li&gt;&lt;b&gt;prod&lt;/b&gt; : 사용자용 맵 같은 프로덕션 워크로드 환경&lt;/li&gt;
&lt;li&gt;&lt;b&gt;mgmt&lt;/b&gt; : 베스천 호스트 Bastion Host, 젠킨스 Jenkins 와 같은 데브옵스 도구 환경&lt;/li&gt;
&lt;li&gt;&lt;b&gt;global&lt;/b&gt; : S3, IAM과 같이 모든 환경에서 사용되는 리소스를 배치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 환경별 구성 요소&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;vpc&lt;/b&gt; : 해당 환경을 위한 네트워크 토폴로지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;services&lt;/b&gt; : 해당 환경에서 서비스되는 애플리케이션, 각 앱은 자체 폴더에 위치하여 다른 앱과 분리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;data-storage&lt;/b&gt; : 해당 환경 별 데이터 저장소. 각 데이터 저장소 역시 자체 폴더에 위치하여 다른 데이터 저장소와 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명명 규칙 naming conventions (예시)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;http://variables.tf&quot;&gt;variables.tf&lt;/a&gt;&lt;/b&gt; : 입력 변수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;http://outputs.tf&quot;&gt;outputs.tf&lt;/a&gt;&lt;/b&gt; : 출력 변수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;http://main-xxx.tf&quot;&gt;main-xxx.tf&lt;/a&gt;&lt;/b&gt; : 리소스 &amp;rarr; 개별 테라폼 파일 규모가 커지면 특정 기능을 기준으로 &lt;b&gt;별도 파일&lt;/b&gt;로 분리 (ex. &lt;a href=&quot;http://main-iam.tf&quot;&gt;main-iam.tf&lt;/a&gt;, &lt;a href=&quot;http://main-s3.tf&quot;&gt;main-s3.tf&lt;/a&gt; 등) 혹은 &lt;b&gt;모듈&lt;/b&gt; 단위로 나눔&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;http://dependencies.tf&quot;&gt;dependencies.tf&lt;/a&gt;&lt;/b&gt; : 데이터 소스&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;http://providers.tf&quot;&gt;providers.tf&lt;/a&gt;&lt;/b&gt; : 공급자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;소개&lt;/span&gt; : &lt;span data-token-index=&quot;2&quot;&gt;State&lt;/span&gt;를 &lt;span data-token-index=&quot;4&quot;&gt;관리&lt;/span&gt;하는 &lt;span data-token-index=&quot;6&quot;&gt;논리적인 가상 공간&lt;/span&gt;을 &lt;span data-token-index=&quot;8&quot;&gt;워크스페이스&lt;/span&gt;라고 한다 - &lt;a style=&quot;color: #000000;&quot; href=&quot;https://developer.hashicorp.com/terraform/cloud-docs/workspaces&quot; data-token-index=&quot;10&quot;&gt;&lt;span&gt;링크&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼 구성 파일은 동일하지만 작업자는 &lt;b&gt;서로 다른 State&lt;/b&gt;를 갖는 &lt;b&gt;실제 대상을 프로비저닝&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;워크스페이스는 기본 default로 정의된다. 로컬 작업 환경의 워크스페이스 관리를 위한 CLI 명령어로 workspace가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1720310259093&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 새 작업 공간 workspace 생성 : mywork1
terraform workspace new mywork1
terraform workspace show

# 서브 디렉터리 확인
tree terraform.tfstate.d
terraform.tfstate.d
└── mywork1

# plan 시 어떤 결과 내용이 출력되나요?
terraform plan

# apply 해보자!
terraform apply -auto-approve


# 워크스페이스 확인
terraform workspace list

#
cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
cat terraform.tfstate.d/mywork1/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'

# graph 확인
terraform graph &amp;gt; graph.dot


# 새 작업 공간 workspace 생성 : mywork2
terraform workspace new mywork2

# 서브 디렉터리 확인
tree terraform.tfstate.d
...

# plan &amp;amp; apply
terraform plan &amp;amp;&amp;amp; terraform apply -auto-approve
cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
cat terraform.tfstate.d/mywork1/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
cat terraform.tfstate.d/mywork2/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'

# workspace 정보 확인
terraform workspace show
terraform workspace list

# 실습 리소스 삭제
terraform workspace select default
terraform destroy -auto-approve

terraform workspace select mywork1
terraform destroy -auto-approve

terraform workspace select mywork2
terraform destroy -auto-approve&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 &lt;b&gt;루트 모듈&lt;/b&gt;에서 다른 환경을 위한 리소스를 동일한 &lt;b&gt;테라폼 구성&lt;/b&gt;으로 프로비저닝하고 관리&lt;/li&gt;
&lt;li&gt;기존 프로비저닝된 환경에 영향을 주지 않고 변경 사항 실험 가능&lt;/li&gt;
&lt;li&gt;깃의 브랜치 전략처럼 동일한 구성에서 서로 다른 리소스 결과 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State가 동일한 저장소(로컬 또는 백엔드)에 저장되어 State 접근 권한 관리가 불가능&lt;/li&gt;
&lt;li&gt;모든 환경이 동일한 리소스를 요구하지 않을 수 있으므로 테라폼 구성에 분기 처리가 다수 발생 가능&lt;/li&gt;
&lt;li&gt;프로비저닝 대상에 대한 인증 요소를 완벽히 분리하기 어려움&amp;rArr; 해결하기 위해 루트 모듈을 별도로 구성하는 디렉터리 기반의 레이아웃을 사용할 수 있다 or Terraform Cloud 환경의 워크스페이스를 활용&lt;/li&gt;
&lt;li&gt;&amp;rarr; 가장 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;큰 단점은 완벽한 격리가 불가능&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Cloud Architect/IaC</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/385</guid>
      <comments>https://nali.tistory.com/385#entry385comment</comments>
      <pubDate>Sat, 6 Jul 2024 23:52:13 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 Provider</title>
      <link>https://nali.tistory.com/384</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-origin-width=&quot;1266&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHl3y/btsIpLmeMrx/7v3ZGzNgIA0mWkAKwSdvYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHl3y/btsIpLmeMrx/7v3ZGzNgIA0mWkAKwSdvYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHl3y/btsIpLmeMrx/7v3ZGzNgIA0mWkAKwSdvYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHl3y%2FbtsIpLmeMrx%2F7v3ZGzNgIA0mWkAKwSdvYk%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;1266&quot; height=&quot;484&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;484&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;b&gt;terraform&lt;/b&gt; 바이너리 파일을 시작으로 로컬 환경에나 배포 서버와 같은 원격 환경에서 원하는 대상을 호출하는 방식으로 실행된다. 이때 &amp;lsquo;원하는 대상&amp;rsquo;은 호출하는 방식이 서로 다르지만 대상의 &lt;b&gt;공급자&lt;/b&gt;, 즉 &lt;b&gt;프로바이더&lt;/b&gt;가 제공하는 &lt;b&gt;API&lt;/b&gt;를 호출해 상호작용을 한다. 여기서 &lt;b&gt;테라폼&lt;/b&gt;이 대상과의 상호작용을 할 수 있도록 하는 것이 &amp;lsquo;&lt;b&gt;프로바이더&lt;/b&gt;&amp;rsquo;다.&lt;/li&gt;
&lt;li&gt;각 프로바이더의 API구현은 서로 다르지만 테라폼의 고유 문법으로 동일한 동작을 수행하도록 구현되어 있다. 프로바이더는 플러그인 형태로 테라폼에 결합되어 대상이 되는 클라우드, SaaS, 기타 서비스 API를 사용해 동작을 수행한다. 각 프로바이더는 테라폼이 관리하는 리소스 유형과 데이터 소스를 사용할 수 있도록 연결한다.&lt;/li&gt;
&lt;li&gt;즉, 테라폼은 프로바이더 없이는 어떤 종류의 인프라와 서비스도 관리할 수 없다는 의미다. 대부분의 프로바이더는 대상 인프라 환경이나 서비스 환경에 대한 리소스를 관리하므로, 프로바이더를 구성할 때는 &lt;b&gt;대상과의 연결과 인증&lt;/b&gt;에*(예. AWS 자격증명 등)* 필요한 정보가 제공되어야 한다.&lt;/li&gt;
&lt;li&gt;각 프로바이더는 테라폼 실행 파일과는 별도로 자체 관리되고 게시된다. 테라폼 레지스트리 사이트에서 주요 프로바이더와 관련 문서를 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.1 프로바이더 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼 레지스트리의 프로바이더 목록에는 유지 보수 및 게시에 대한 권한에 따라 &lt;span data-token-index=&quot;1&quot;&gt;Tier 정보&lt;/span&gt;가 제공 - &lt;a style=&quot;color: #000000;&quot; href=&quot;https://registry.terraform.io/browse/providers&quot; data-token-index=&quot;3&quot;&gt;&lt;span&gt;링크&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOgrd4/btsIpvcWYzW/OEpqmFtPCPgKVNZYzjkh81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOgrd4/btsIpvcWYzW/OEpqmFtPCPgKVNZYzjkh81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOgrd4/btsIpvcWYzW/OEpqmFtPCPgKVNZYzjkh81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOgrd4%2FbtsIpvcWYzW%2FOEpqmFtPCPgKVNZYzjkh81%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;934&quot; height=&quot;517&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정하는 프로바이더의 요구사항을 정의 : 레지스트리의 Overview 항목 오른쪽에 있는 [USE PROVIDER] 클릭해 현재 버전의 정의 방법을 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지정하는 프로바이더의 요구사항을 정의 : 레지스트리의 Overview 항목 오른쪽에 있는 [USE PROVIDER] 클릭해 현재 버전의 정의 방법을 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로컬 이름과 프로바이더 지정&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;terraform 블록의 required_providers 블록 내에 &lt;b&gt;&amp;lt;로컬 이름&amp;gt; = { }&lt;/b&gt; 으로 여러 개의 프로바이더를 정의할 수 있다. 여기서 사용되는 &lt;b&gt;로컬 이름&lt;/b&gt;은 테라폼 모듈 내에서 고유해야 한다. 로컬 이름과 리소스 접두사는 독립적으로 선언되며, 각 프로바이더의 소스 경로가 지정되면 프로바이더의 고유 접두사가 제공된다. 만약 동일한 접두사를 사용하는 프로바이더가 선언되는 경우 &lt;b&gt;로컬 이름을 달리해&lt;/b&gt; 관련 리소스에서 어떤 프로바이더를 사용하는지 명시적으로 지정할 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어 다음의 main.tf에서처럼 동일한 http 이름을 사용하는 다수의 프로바이더가 있는 경우 각 프로바이더에 고유한 이름을 부여하고 리소스와 데이터 소스에 어떤 프로바이더를 사용할지 provider 인수에 명시한다. 단, 동일한 source에 대해 다수의 정의는 불가능한다.&lt;/li&gt;
&lt;li&gt;동일한 &lt;b&gt;http 접두사&lt;/b&gt;를 사용하는 &lt;b&gt;다수의 프로바이더&lt;/b&gt; 사용 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1720297770296&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform {
  required_providers {
    architech-http = {
      source = &quot;architect-team/http&quot;
      version = &quot;~&amp;gt; 3.0&quot;
    }
    http = {
      source = &quot;hashicorp/http&quot;
    }
    aws-http = {
      source = &quot;terraform-aws-modules/http&quot;
    }
  }
}

data &quot;http&quot; &quot;example&quot; {
  provider = aws-http
  url = &quot;https://checkpoint-api.hashicorp.com/v1/check/terraform&quot;

  request_headers = {
    Accept = &quot;application/json&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단일 프로바이더의 다중 정의&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동일한 프로바이더&lt;/b&gt;를 사용하지만 다른 조건을 갖는 경우, 사용되는 리소스마다 별도로 선언된 프로바이더를 지정해야 하는 경우가 있다. 예를 들면, AWS 프로바이더를 사용하는데 서로 다른 권한의 IAM을 갖는 Access ID 또는 대상 리전을 지정해야 하는 경우다. 이때는 프로바이더 선언에서 alias를 명시하고 사용하는 리소스와 데이터 소스에서는 provider 메타인수를 사용해 특정 프로바이더를 지정할 수 있다. provider 메타인수에 지정되지 않은 경우 &lt;b&gt;alias&lt;/b&gt;가 없는 프로바이더가 기본 프로바이더로 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래 예제는 리전을 다르게 구성한 AWS 프로바이더를 aws_instance에 지정하는 방법이다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1720297840481&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;provider &quot;aws&quot; {
  region = &quot;ap-southeast-1&quot;
}

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

resource &quot;aws_instance&quot; &quot;app_server1&quot; {
  ami           = &quot;ami-06b79cf2aee0d5c92&quot;
  instance_type = &quot;t2.micro&quot;
}

resource &quot;aws_instance&quot; &quot;app_server2&quot; {
  provider      = aws.seoul
  ami           = &quot;ami-0ea4d4b8dc1e46212&quot;
  instance_type = &quot;t2.micro&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1720297873578&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 배포 실행
terraform init &amp;amp;&amp;amp; terraform plan
terraform apply -auto-approve

# 확인
terraform state list
echo &quot;aws_instance.app_server1.public_ip&quot; | terraform console
echo &quot;aws_instance.app_server2.public_ip&quot; | terraform console
aws ec2 describe-instances --filters Name=instance-state-name,Values=running --output table
aws ec2 describe-instances --filters Name=instance-state-name,Values=running --output table --region ap-southeast-1

# 삭제
terraform destroy -auto-approve&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로바이더 요구사항 정의&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼 실행 시 요구되는 프로바이더 요구사항은 terraform 블록의 &lt;b&gt;required_providers&lt;/b&gt; 블록에 여러 개를 정의할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;source&lt;/b&gt;에는 프로바이더 다운로드 경로를 지정하고 &lt;b&gt;version&lt;/b&gt;은 버전 제약을 명시한다.&lt;/li&gt;
&lt;li&gt;프로바이더 요구사항 정의 블록
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;호스트 주소&lt;/b&gt; : 프로바이더를 배포하는 주소로서 기본값은 &lt;b&gt;&lt;a href=&quot;http://registry.terraform.io&quot;&gt;registry.terraform.io&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네임스페이스&lt;/b&gt; : 지정된 레지스트리 내에서 구분하는 네임스페이스로, 공개된 레지스트리 및 Terraform Cloud의 비공개 레지스트리의 프로바이더를 게시하는 조직을 의미&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유형&lt;/b&gt; : 프로바이더에서 관리되는 플랫폼이나 서비스 이름으로 일반적으로 접두사와 일치하나 일부 예외가 있을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1720297903368&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform {
  required_providers {
    &amp;lt;프로바이더 로컬 이름&amp;gt; = {
      source = [&amp;lt;호스트 주소&amp;gt;/]&amp;lt;네임스페이스&amp;gt;/&amp;lt;유형&amp;gt;
      version = &amp;lt;버전 제약&amp;gt;
    }
    ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로바이더는 기능이나 조건이 시간이 지남에 따라 변경될 수 있다. 이 같은 변경에 특정 버전을 명시하거나 버전 호환성을 정의할 때, version에 명시할 수 있다. 이 값이 생략되는 경우 terraform init을 하는 당시의 가장 최신 버전으로 선택된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로바이더 설치&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼을 실행하기 전 &lt;b&gt;terraform&lt;/b&gt; &lt;b&gt;init&lt;/b&gt; 명령을 통해 정의된 &lt;b&gt;프로바이더&lt;/b&gt;를 &lt;b&gt;다운로드, 복사, 캐시&lt;/b&gt;에서 읽어오게 된다. 항상 지정된 구성에 대해 동일한 프로바이더를 설치하도록 하려면 테라폼 구성에 사용되는 프로바이더에 대해 명시적으로 terraform 블록에 정의하거나 &lt;b&gt;.terraform.lock.hcl&lt;/b&gt; 잠금 파일을 코드 저장소에 공유하는 방안이 요구된다.&lt;/li&gt;
&lt;li&gt;2장에서 살펴본 것처럼 &lt;b&gt;required_providers&lt;/b&gt;에 지정된 프로바이더가 있는 경우 코드상 구성에서 사용 여부에 관계없이 프로바이더를 다운로드하게 되고, required_providers에 지정하지 않더라고 테라폼 구성 코드사에서 사용된 프로바이더를 테라폼에서 추론해 최신 버전의 프로바이더를 다운로드 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로바이더 간 전환 여부&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라우드를 대상으로 테라폼을 사용하는 경우 다른 클라우드 프로바이더로 전환이 가능할까? &amp;rArr; 불가능하다!&lt;/li&gt;
&lt;li&gt;테라폼은 인프라에 대한 단일 프로비저닝 도구로 사용되지만 대상이 되는 환경은 서로 다른 API로 구현된 프로바이더가 제공된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.2 프로바이더 에코시스템&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 테라폼의 에코시스템은 사용자가 사용하는 방식과 구조에 테라폼을 적용할 수 있도록 설계된다. &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에코시스템을 위한 테라폼 통합은 워크플로 파트너와 인프라 파트너로 나눈다.&lt;/li&gt;
&lt;li&gt;워크플로 파트너는 테라폼 실행 및 Terraform Cloud/Enterprise와 연계하여 동작하는 기능을 제공하는 항목으로 이루어져 있다.&lt;/li&gt;
&lt;li&gt;대표적으로 테라폼 구성을 관리하기 위한 VCS를 제공하는 깃허브, 깃랩, 비트버킷, 애저 데브옵스가 이 항목에 해당된다.&lt;/li&gt;
&lt;li&gt;프로바이더의 경우 인프라 파트너로 해당된다.&lt;/li&gt;
&lt;li&gt;인프라 파트너는 사용자가 테라폼으로 대상 플랫폼의 API로 상호작용 가능한 리소스를 관리할 수 있도록 한다.&lt;/li&gt;
&lt;li&gt;인프라 파트너의 분류와 프로바이더 대상은 다음과 같다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IaaS, SaaS 및 PaaS를 포함한 다양한 서비스를 제공하는 대규모 글로벌 클라우드 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;컨테이너 오케스트레이션&lt;/b&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;b&gt;IaaS(Infrastructure-as-a-Service)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스토리지, 네트워킹 및 가상화와 같은 솔루션을 제공하는 인프라 및 IaaS 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;보안 및 인증&lt;/b&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;b&gt;자산 관리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어 라이선스, 하드웨어 자산 및 클라우드 리소스를 비롯한 주요 조직 및 IT 리소스의 자산 관리를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;CI/CD&lt;/b&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;b&gt;로깅 및 모니터링&lt;/b&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;b&gt;유틸리티&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임의 값 생성, 파일 생성, http 상호 작용 및 시간 기반 리소스와 같은 도우미 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;클라우드 자동화&lt;/b&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;b&gt;데이터 관리&lt;/b&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;b&gt;네트워킹&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우팅, 스위칭, 방화벽 및 SD-WAN 솔루션과 같은 네트워크별 하드웨어 및 가상화된 제품과 통합&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;VCS(버전 제어 시스템)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Terraform 내에서 VCS(버전 제어 시스템) 프로젝트, 팀 및 리포지토리에 중점&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;통신 및 메시징&lt;/b&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;b&gt;데이터베이스&lt;/b&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;b&gt;PaaS(Platform-as-a-Service)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 하드웨어, 소프트웨어 및 애플리케이션 개발 도구를 제공하는 플랫폼 및 PaaS&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;웹 서비스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 호스팅, 웹 성능, CDN 및 DNS 서비스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;퍼블릭 클라우드&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;퍼블릭 클라우드(Iaas, Paas) 프로바이더 + 프라이빗 클라우드/VM&lt;/li&gt;
&lt;li&gt;컨테이너 오케스트레이터&lt;/li&gt;
&lt;li&gt;SaaS 서비스&lt;/li&gt;
&lt;li&gt;기타 솔루션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.3 프로바이더 실습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2개의 리전에 Ubuntu EC2 배포하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;- provider_data.tf &lt;/span&gt;파일 생성 : 리전 별 AMI ID값이 다르므로, 필터를 활용하자&lt;/p&gt;
&lt;pre id=&quot;code_1720298178780&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;provider &quot;aws&quot; {
  region = &quot;ap-northeast-2&quot;
  alias  = &quot;region_1&quot;
}

provider &quot;aws&quot; {
  region = &quot;ap-southeast-1&quot;
  alias  = &quot;region_2&quot;
}

data &quot;aws_region&quot; &quot;region_1&quot; {
  provider = aws.region_1
}

data &quot;aws_region&quot; &quot;region_2&quot; {
  provider = aws.region_2
}

data &quot;aws_ami&quot; &quot;ubuntu_region_1&quot; {
  provider = aws.region_1

  most_recent = true
  owners      = [&quot;099720109477&quot;] # Canonical

  filter {
    name   = &quot;name&quot;
    values = [&quot;ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*&quot;]
  }
}

data &quot;aws_ami&quot; &quot;ubuntu_region_2&quot; {
  provider = aws.region_2

  most_recent = true
  owners      = [&quot;099720109477&quot;] # Canonical

  filter {
    name   = &quot;name&quot;
    values = [&quot;ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*&quot;]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;ec2.tf &lt;/span&gt;파일 생성 : 리전, ami 참조&lt;/p&gt;
&lt;pre id=&quot;code_1720308697945&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;region_1&quot; {
  provider = aws.region_1

  ami           = data.aws_ami.ubuntu_region_1.id
  instance_type = &quot;t2.micro
}

resource &quot;aws_instance&quot; &quot;region_2&quot; {
  provider = aws.region_2

  ami           = data.aws_ami.ubuntu_region_2.id
  instance_type = &quot;t2.micro&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행&lt;/p&gt;
&lt;pre id=&quot;code_1720308718004&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# [터미널1] ap-northeast-2
while true; do aws ec2 describe-instances --region ap-northeast-2 --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output text ; echo &quot;------------------------------&quot; ; sleep 1; done

# [터미널2] ap-southeast-1
while true; do aws ec2 describe-instances --region ap-southeast-1 --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output text ; echo &quot;------------------------------&quot; ; sleep 1; done

# init &amp;amp; plan &amp;amp; apply
terraform init &amp;amp;&amp;amp; terraform plan &amp;amp;&amp;amp; terraform apply -auto-approve
terraform state list

# 확인
aws ec2 describe-instances --region ap-northeast-2 --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output text
aws ec2 describe-instances --region ap-southeast-1 --query &quot;Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}&quot; --filters Name=instance-state-name,Values=running --output text&lt;/code&gt;&lt;/pre&gt;</description>
      <category>  Cloud Architect/IaC</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/384</guid>
      <comments>https://nali.tistory.com/384#entry384comment</comments>
      <pubDate>Sat, 6 Jul 2024 23:51:54 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 프로비저너, terraform_data, moved, CLI 환경변수</title>
      <link>https://nali.tistory.com/381</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;lsquo;테라폼으로 시작하는 IaC&amp;rsquo; 책을 기준하여 정리하였습니다.&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3.12 프로비저너&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;프로비저너&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로비저너는 프로바이더와 비슷하게 &amp;lsquo;제공자&amp;rsquo;로 해석되나, 프로바이더로 실행되지 않는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;커맨드와 파일 복사 같은 역할을 수행&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax&quot;&gt;링크&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.hashicorp.com/terraform/tutorials/provision/cloud-init&quot;&gt;Tutorial&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 AWS EC2 생성 후 특정 패키지를 설치해야 하거나 파일을 생성해야 하는 경우, 이것들은 테라폼의 구성과 별개로 동작해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화되지 않으므로 프로비저닝에 대한 결과가 항상 같다고 보장할 수 없다&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rArr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;선언적 보장 안됨&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;따라서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;프로비저너 사용을 최소화&lt;/b&gt;하는 것이 좋다. 프로비저너의 종류에는 파일 복사와 명령어 실행을 위한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;file, local-exec, remote-exec&lt;/b&gt;가 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로비저너&lt;/b&gt;의 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;리소스 프로비저닝 이후 동작&lt;/b&gt;하도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;구성&lt;/b&gt;할 수 있다. 예를 들어 AWS EC2 생성 후 CLI를 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;별도 작업 수행 상황을 가정&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719681002266&quot; class=&quot;routeros&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;variable &quot;sensitive_content&quot; {
  default   = &quot;secret&quot;
  #sensitive = true
}

resource &quot;local_file&quot; &quot;foo&quot; {
  content  = upper(var.sensitive_content)
  filename = &quot;${path.module}/foo.bar&quot;

  provisioner &quot;local-exec&quot; {
    command = &quot;echo The content is ${self.content}&quot;
  }

  provisioner &quot;local-exec&quot; {
    command    = &quot;abc&quot;
    on_failure = continue
  }

  provisioner &quot;local-exec&quot; {
    when    = destroy
    command = &quot;echo The deleting filename is ${self.filename}&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;local-exec 프로비저너: 테라폼이 실행되는 환경에서 수행할 커맨드를 정의 -&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.hashicorp.com/terraform/language/resources/provisioners/local-exec&quot;&gt;링크&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;
&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;&lt;b&gt;command&lt;/b&gt;(필수) : 실행할 명령줄을 입력하며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;lt;&amp;lt; 연산자&lt;/b&gt;를 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;여러 줄&lt;/b&gt;의 커맨드 입력 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;working_dir&lt;/b&gt;(선택) : command의 명령을 실행할 디렉터리를 지정해야 하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;상대/절대 경로&lt;/b&gt;로 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;interpreter&lt;/b&gt;(선택) : 명령을 실행하는 데 필요한 인터프리터를 지정하며, 첫 번째 인수로 인터프리터 이름이고 두 번째부터는 인터프리터 인수 값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;environment&lt;/b&gt;(선택) : 실행 시 환경 변수 는 실행 환경의 값을 상속받으면, 추가 또는 재할당하려는 경우 해당 인수에 key = value 형태로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;command의 &amp;lt;&amp;lt; 연산자를 통해 다중 라인의 명령을 수행하여 각 환경에 맞는 인터프리터를 지정해 해당 명령을 수행한다.&lt;/li&gt;
&lt;li&gt;Apply 수행 시 이 명령의 실행 위치를 working_dir를 사용해 지정하고 command에서 사용하는 환경 변수에 대해 environment에서 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719681002267&quot; class=&quot;routeros&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;# UNIX/LINUX/macOS
resource &quot;null_resource&quot; &quot;example1&quot; {
  
  provisioner &quot;local-exec&quot; {
    command = &amp;lt;&amp;lt;EOF
      echo Hello!! &amp;gt; file.txt
      echo $ENV &amp;gt;&amp;gt; file.txt
      EOF
    
    interpreter = [ &quot;bash&quot; , &quot;-c&quot; ]

    working_dir = &quot;/tmp&quot;

    environment = {
      ENV = &quot;world!!&quot;
    }

  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원격지 연결&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.hashicorp.com/terraform/language/resources/provisioners/remote-exec&quot;&gt;링크&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;remote-exec와 file 프로비저너를 사용하기 위해 원격지에 연결할 SSH, WinRM 연결 정의가 필요하다&lt;/li&gt;
&lt;li&gt;connection 블록 리소스 선언 시,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;해당 리소스 내&lt;/b&gt;에 구성된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;프로비저너&lt;/b&gt;에 대해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;공통으로 선언&lt;/b&gt;되고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;프로비저너 내에 선언&lt;/b&gt;되는 경우,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;해당 프로비저너에서만 적용&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719681176059&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# connection 블록으로 원격지 연결 정의
resource &quot;null_resource&quot; &quot;example1&quot; {
  
  connection {
    type     = &quot;ssh&quot;
    user     = &quot;root&quot;
    password = var.root_password
    host     = var.host
  }

  provisioner &quot;file&quot; {
    source      = &quot;conf/myapp.conf&quot;
    destination = &quot;/etc/myapp.conf&quot;
  }

  provisioner &quot;file&quot; {
    source      = &quot;conf/myapp.conf&quot;
    destination = &quot;C:/App/myapp.conf&quot;

    connection {
        type     = &quot;winrm&quot;
        user     = &quot;Administrator&quot;
        password = var.admin_password
        host     = var.host
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;file&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;프로비저너 : 테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉터리를 복사하는 데 사용 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.hashicorp.com/terraform/language/resources/provisioners/file&quot;&gt;링크&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉터리를 복사하는 데 사용&lt;/li&gt;
&lt;li&gt;사용되는 인수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;source&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 소스 파일 또는 디렉터리로, 현재 작업 중인 디렉터리에 대한 상태 경로 또는 절대 경로로 지정할 수 있다. content와 함께 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;content&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 연결 대상에 복사할 내용을 정의하며 대상이 디렉터리인 경우 tf-file-content 파일이 생성되고, 파일인 경우 해당 파일에 내용이 기록된다. source와 함께 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;destination&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 필수 항목으로 항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;절대 경로&lt;/b&gt;로 지정되어야 하며, 파일 또는 디렉터리다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719681201515&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;null_resource&quot; &quot;foo&quot; {
  
  # myapp.conf 파일이 /etc/myapp.conf 로 업로드
  provisioner &quot;file&quot; {
    source      = &quot;conf/myapp.conf&quot;
    destination = &quot;/etc/myapp.conf&quot;
  }
  
  # content의 내용이 /tmp/file.log 파일로 생성
  provisioner &quot;file&quot; {
    content     = &quot;ami used: ${self.ami}&quot;
    destination = &quot;/tmp/file.log&quot;
  }
  
  # configs.d 디렉터리가 /etc/configs.d 로 업로드
  provisioner &quot;file&quot; {
    source      = &quot;conf/configs.d&quot;
    destination = &quot;/etc&quot;
  }
  
  # apps/app1 디렉터리 내의 파일들만 D:/IIS/webapp1 디렉터리 내에 업로드
  provisioner &quot;file&quot; {
    source      = &quot;apps/app1/&quot;
    destination = &quot;D:/IIS/webapp1&quot;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;remote-exec&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;프로비저너 : 원격지 환경에서 실행할 커맨드와 스크립트를 정의 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.hashicorp.com/terraform/language/resources/provisioners/remote-exec&quot;&gt;링크&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들면 AWS의 EC2 인스턴스를 생성하고 해당 VM에서 명령을 실행하고 패키지를 설치하는 등의 동작을 의미한다.&lt;/li&gt;
&lt;li&gt;사용하는 인수는 다음과 같고 각 인수는 서로 배타적이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;inline&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 명령에 대한 목록으로 [ ] 블록 내에 &amp;ldquo; &amp;ldquo;로 묶인 다수의 명령을 , 로 구분해 구성한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;script&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 로컬의 스크립트 경로를 넣고 원격에 복사해 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;scripts&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 로컬의 스크립트 경로의 목록으로 [ ] 블록 내에 &amp;ldquo; &amp;ldquo;로 묶인 다수의 스크립트 경로를 , 로 구분해 구성한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;script 또는 scripts의 대상 스크립트 실행에 필요한 인수는 관련 구성에서 선언할 수 없으므로 필요할 때 file 프로바이더로 해당 스크립트를 업로드하고 inline 인수를 활용해 스크립트에 인수를 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719681247055&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;web&quot; {
  # ...

  # Establishes connection to be used by all
  # generic remote provisioners (i.e. file/remote-exec)
  connection {
    type     = &quot;ssh&quot;
    user     = &quot;root&quot;
    password = var.root_password
    host     = self.public_ip
  }

  provisioner &quot;file&quot; {
    source      = &quot;script.sh&quot;
    destination = &quot;/tmp/script.sh&quot;
  }

  provisioner &quot;remote-exec&quot; {
    inline = [
      &quot;chmod +x /tmp/script.sh&quot;,
      &quot;/tmp/script.sh args&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.13 null_resource와 terraform_data&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼 1.4 버전이 릴리즈되면서 기존 &lt;span data-token-index=&quot;1&quot;&gt;null_resource&lt;/span&gt; 리소스를 대체하는 &lt;span style=&quot;color: #337ea9;&quot; data-token-index=&quot;3&quot;&gt;terraform_data&lt;/span&gt;&lt;span style=&quot;color: #337ea9;&quot; data-token-index=&quot;4&quot;&gt; &lt;/span&gt;리소스가 추가되었다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;null_resource&lt;/span&gt; : 아무 작업도 수행하지 않는 리소스를 구현&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 리소스가 필요한 이유는 테라폼 프로비저닝 동작을 설계하면서 &lt;b&gt;사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황&lt;/b&gt;이 발생하여, 프로바이더가 제공하는 리소스 수명주기 관리만으로는 이를 해결하기 어렵기 때문이다.&lt;/li&gt;
&lt;li&gt;주로 사용되는 시나리오
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로비저닝 수행 과정에서 명령어 실행&lt;/li&gt;
&lt;li&gt;프로비저너와 함께 사용&lt;/li&gt;
&lt;li&gt;모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용&lt;/li&gt;
&lt;li&gt;출력을 위한 데이터 가공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예를 들어 다음의 상황을 가정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS EC2 인스턴스를 프로비저닝하면서 웹서비스를 실행시키고 싶다&lt;/li&gt;
&lt;li&gt;웹서비스 설정에는 노출되어야 하는 고정된 외부 IP가 포함된 구성이 필요하다. 따라서 aws_eip 리소스를 생성해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AWS EC2 인스턴스를 프로비저닝하기 위해 aws_instance 리소스 구성 시 앞서 확인한 프로비저너를 활용하여 웹서비스를 실행하고자 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719731800063&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ...
resource &quot;aws_eip&quot; &quot;myeip&quot; {
  #vpc = true
  instance = aws_instance.example.id
  associate_with_private_ip = &quot;172.31.0.100&quot;
}

resource &quot;null_resource&quot; &quot;echomyeip&quot; {
  provisioner &quot;remote-exec&quot; {
    connection {
      host = aws_eip.myeip.public_ip
      type = &quot;ssh&quot;
      user = &quot;ubuntu&quot;
      private_key =  file(&quot;/Users/gasida/.ssh/kp-gasida.pem&quot;) # 각자 자신의 EC2 SSH Keypair 파일 위치 지정
      #password = &quot;qwe123&quot;
    }
    inline = [
      &quot;echo ${aws_eip.myeip.public_ip}&quot;
      ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Trigger&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null_resource는 정의된 속성이 &amp;lsquo;id&amp;rsquo;가 전부이므로, 선언된 내부의 구성이 변경되더라도 새로운 Plan 과정에서 실행 계획에 포함되지 못한다.&lt;/li&gt;
&lt;li&gt;따라서 사용자가 null_resource에 정의된 내용을 강제로 다시 실행하기 위한 인수로 &lt;b&gt;trigger&lt;/b&gt;가 제공된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;trigger&lt;/b&gt;는 임의의 string 형태의 map 데이터를 정의하는데, 정의된 값이 변경되면 null_resource 내부에 정의된 행위를 다시 실행한다.&lt;/li&gt;
&lt;li&gt;trigger 정의와 동작 예제&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719731817556&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;null_resource&quot; &quot;foo&quot; {
  triggers = {
    ec2_id = aws_instance.bar.id # instance의 id가 변경되는 경우 재실행
  }
  ...생략...
}

resource &quot;null_resource&quot; &quot;bar&quot; {
  triggers = {
    ec2_id = time() # 테라폼으로 실행 계획을 생성할 떄마다 재실행
  }
  ...생략...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 리소스 또한 자체적으로 아무것도 수행하지 않지만 null_resource는 &lt;b&gt;별도의 프로바이더 구성이 필요&lt;/b&gt;하다는 점과 비교하여 추가 프로바이더 없이 테라폼 자체에 포함된 &lt;b&gt;기본 수명주기 관리자&lt;/b&gt;가 제공된다는 것이 장점이다.&lt;/li&gt;
&lt;li&gt;사용 시나리오는 기본 null_resourcr와 동일하며 강제 재실행을 위한 &lt;b&gt;trigger_replace&lt;/b&gt;와 상태 저장을 위한 &lt;b&gt;input 인수&lt;/b&gt;와 input에 저장된 값을 출력하는 &lt;b&gt;output 속성이 제공&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;triggers_replace에 정의되는 값이 기존 map 형태에서 tuple로 변경되어 쓰임이 더 간단해졌다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719731944966&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;terraform_data&quot; &quot;foo&quot; {
  triggers_replace = [
    aws_instance.foo.id,
    aws_instance.bar.id
  ]

  input = &quot;world&quot;
}

output &quot;terraform_data_output&quot; {
  value = terraform_data.foo.output  # 출력 결과는 &quot;world&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;3.14 moved 블록&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼의 State에 기록되는 리소스 주소의 이름이 변경되면 기존 리소스는 삭제되고 새로운 리소스가 생성됨을 앞서 설명에서 확인했다.&lt;/li&gt;
&lt;li&gt;하지만 테라폼 리소스를 선언하다 보면 이름을 변경해야 하는 상황이 발생하기도 하는데, 예를 들면 다음과 같다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 이름을 변경&lt;/li&gt;
&lt;li&gt;count로 처리하던 반복문을 for_each로 변경&lt;/li&gt;
&lt;li&gt;리소스가 모듈로 이동하여 참조되는 주소가 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;리소스의 이름은 변경되지만 이미 테라폼으로 프로비저닝된 환경을 그대로 유지하고자 하는 경우 테라폼 1.1 버전부터 moved 블록을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&amp;lsquo;moved&amp;rsquo;라는 단어가 의미하는 것처럼 테라폼 State에서 옮겨진 대상의 이전 주소와 새 주소를 알리는 역할을 수행한다.&lt;/li&gt;
&lt;li&gt;moved 블록 이전에는 State를 직접 편집하는 terraform state mv 명령을 사용하여 State를 건드려야 하는 부담이 있었다면, moved 블록은 State에 접근 권한이 없는 사용자라도 변경되는 주소를 리소스 영향 없이 반영할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719732181005&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;b&quot; {
  content  = &quot;foo!&quot;
  filename = &quot;${path.module}/foo.bar&quot;
}

moved {
  from = local_file.a
  to   = local_file.b
}

output &quot;file_content&quot; {
  value = local_file.b.content
}
# 제거나 생성 없음!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;local_file.a 의 프로비저닝 결과를 유지한 채 이름을 변경하기 위해 moved 블록을 활용한다.&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;3.15 CLI를 위한 시스템 환경 변수&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼은 환경 변수를 통해 실행 방식과 출력 내용에 대한 옵션을 조절할 수 있다 - &lt;a href=&quot;https://developer.hashicorp.com/terraform/cli/config/environment-variables&quot;&gt;링크&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 환경 변수를 설정하면, 영구적으로 로컬 환경에 적용되는 옵션이나 별도 서버 환경에서 실행하기 위한 옵션을 부여할 수 있다.&lt;/li&gt;
&lt;li&gt;이를 통해 로컬 작업 환경과 다른 환경 구성에서만 사용될 특정 옵션을 적용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719732243079&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Mac/리눅스/유닉스: export &amp;lt;환경 변수 이름&amp;gt;=&amp;lt;값&amp;gt;
Windows CMD: set &amp;lt;환경 변수 이름&amp;gt;=&amp;lt;값&amp;gt;
Windows PowerShell: $Env:&amp;lt;환경 변수 이름&amp;gt;='&amp;lt;값&amp;gt;'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TF_LOG&lt;/b&gt; : 테라폼의 stderr 로그에 대한 레벨을 정의&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;trace, debug, info, warn, error, off를 설정할 수 있고 관련 환경 변수가 없는 경우 off와 동일하다&lt;/li&gt;
&lt;li&gt;디버깅을 위한 로그 관련 환경 변수 설명은 다음과 같다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TF_LOG: 로깅 레벨 지정 또는 해제&lt;/li&gt;
&lt;li&gt;TF_LOG_PATH: 로그 출력 파일 위치 지정&lt;/li&gt;
&lt;li&gt;TF_LOG_CORE: TF_LOG와 별도로 테라폼 자체 코어에 대한 로깅 레벨 지정 또는 해제&lt;/li&gt;
&lt;li&gt;TF_LOG_PROVIDER: TF_LOG와 별도로 테라폼에서 사용하는 프로바이더에 대한 로깅 레벨 지정 또는 해제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;환경에 맞게 TF_LOG를 info로 설정하고, terraform plan 동작을 실행하면 테라폼 출력에 관련 로그가 출력된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TF_INPUT&lt;/b&gt; : 값을 false 또는 0으로 설정하면 테라폼 실행 시 인수에 -input=false 를 추가한 것과 동일한 수행 결과를 확인&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;환경에 맞게 TF_INPUT을 0으로 설정하고 terraform plan 동작 실행하면 입력받는 동작을 수행하지 않으므로 입력 변수를 입력해야 하는 경우 에러가 출력된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TF_VAR_name&lt;/b&gt; : TF_VAR_&amp;lt;변수 이름&amp;gt;을 사용하면 입력 시 또는 default로 선언된 변수 값을 대체한다 &amp;larr; 3.6절에서 확인!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TF_CLI_ARGS / TF_CLI_ARGS_subcommand&lt;/b&gt; : 테라폼 실행 시 추가할 인수를 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TF_DATA_DIR&lt;/b&gt; : State 저장 백엔드 설정과 같은 작업 디렉터리별 데이터를 보관하는 위치를 지정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 데이터는 .terraform 디렉터리 위치에 기록되지만 TF_DATA_DIR에 경로가 정의되면 기본 경로를 대체하여 사용된다.&lt;/li&gt;
&lt;li&gt;일관된 테라폼 사용을 위해서 해당 변수는 실행 시마다 일관되게 적용될 수 있도록 설정하는 것이 중요하다.&lt;/li&gt;
&lt;li&gt;설정 값이 이전 실행 시에만 적용되는 경우 init 명령으로 수행된 모듈, 아티팩트 등의 파일을 찾지 못한다.&lt;/li&gt;
&lt;li&gt;이미 terraform init이 수행된 상태에서 TF_DATA_DIR로 경로를 재지정하고 실행하는 경우 플러그인 설치가 필요하다는 메시지 출력을 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719732341090&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TF_LOG=info terraform plan

TF_INPUT=0 terraform plan
Error : No value for required variable

# TF_CLI_ARGS=&quot;-input=false&quot; terraform apply -auto-approve 는 terraform apply -input=false -auto-approve 와 같다
TF_CLI_ARGS=&quot;-input=false&quot; terraform apply -auto-approve
Error: No value for required variable

# TF_CLI_ARGS_apply로 인수를 정의하면 terraform apply 커맨드 수행 시에만 동작한다
export TF_CLI_ARGS_apply=&quot;-input=false&quot;
terraform apply -auto-approve
&amp;lt;에러&amp;gt;

terraform plan
&amp;lt;정상 계획 예측 출력&amp;gt;

TF_DATA_DIR=./.terraform_tmp terraform plan
Error: Required plugins anr not installed&lt;/code&gt;&lt;/pre&gt;</description>
      <category>  Cloud Architect/IaC</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/381</guid>
      <comments>https://nali.tistory.com/381#entry381comment</comments>
      <pubDate>Sun, 30 Jun 2024 02:08:27 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 반복문, 조건문, 함수</title>
      <link>https://nali.tistory.com/380</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;lsquo;테라폼으로 시작하는 IaC&amp;rsquo; 책을 기준하여 정리하였습니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.9 반복문&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;for each: 반복문, 선언된 key 값 개수만큼 리소스를 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for-each는&amp;nbsp;&lt;b&gt;반복(for)을 할 때 타입 값에 대해 하나하나 each object로 접근&lt;/b&gt;한다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;each object는 key, value 2개의 속성을 가지고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#each-key&quot;&gt;each.key&lt;/a&gt;&amp;nbsp;&amp;mdash;&amp;nbsp;The map key (or set member) corresponding to this instance.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#each-value&quot;&gt;each.value&lt;/a&gt;&amp;nbsp;&amp;mdash; The map value corresponding to this instance. (If a set was provided, this is the same as&amp;nbsp;each.key.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 for_each는 모든 타입에 대해 each object로 접근 할 수 없고&amp;nbsp;&lt;b&gt;map, set타입만 허용&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map, set타입이 아닌 expression은 map 또는 set으로 타입변환(toset 등)을 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스 또는 모듈 블록에서 for_each에 입력된 데이터 형태가 &lt;span data-token-index=&quot;1&quot;&gt;map&lt;/span&gt; 또는 &lt;span data-token-index=&quot;3&quot;&gt;set&lt;/span&gt;이면, &lt;b&gt;선언된 &lt;span data-token-index=&quot;5&quot;&gt;key 값 개수&lt;/span&gt;만큼 &lt;span data-token-index=&quot;7&quot;&gt;리소스를 생성&lt;/span&gt;하게 된다. &lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# map
resource &quot;azurerm_resource_group&quot; &quot;rg&quot; {
  for_each = tomap({
    a_group       = &quot;eastus&quot;
    another_group = &quot;westus2&quot;
  })
  name     = each.key
  location = each.value
}

# set of strings
resource &quot;aws_iam_user&quot; &quot;the-accounts&quot; {
  for_each = toset([&quot;Todd&quot;, &quot;James&quot;, &quot;Alice&quot;, &quot;Dottie&quot;])
  name     = each.key
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;for_each 실습&lt;/h4&gt;
&lt;pre id=&quot;code_1719661277961&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#main.tf
resource &quot;local_file&quot; &quot;abc&quot; {
  for_each = {
    a = &quot;content a&quot;
    b = &quot;content b&quot;
  }
  content  = each.value
  filename = &quot;${path.module}/${each.key}.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1719661295253&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실행 확인
terraform init &amp;amp;&amp;amp; terraform plan &amp;amp;&amp;amp; terraform apply -auto-approve
terraform state list
terraform state show 'local_file.abc[&quot;a&quot;]'
terraform state show 'local_file.abc[&quot;b&quot;]'
ls *.txt
cat a.txt ;echo
cat b.txt ;echo

# 
echo &quot;local_file.abc&quot; | terraform console
echo 'local_file.abc[&quot;a&quot;]' | terraform console
echo 'local_file.abc[&quot;a&quot;].content' | terraform console
echo 'local_file.abc[&quot;b&quot;].content' | terraform console

# VSCODDE 에서 terraform.tfstate 파일 확인 혹은 cat terraform.tfstate
...
      &quot;instances&quot;: [
        {
          &quot;index_key&quot;: &quot;a&quot;,
          &quot;schema_version&quot;: 0,
      		...
          &quot;index_key&quot;: &quot;b&quot;,
          &quot;schema_version&quot;: 0,
...&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;for_each가 설정된 블록에서는 each 속성을 사용해 구성을 수정할 수 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;each.key&lt;/b&gt; : 이 인스턴스에 해당하는 map 타입의 key 값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;each.value&lt;/b&gt; : 이 인스턴스에 해당하는 map의 value 값&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생성되는 리소스의 경우 &amp;lt;리소스 타입&amp;gt;.&amp;lt;이름&amp;gt;[&amp;lt;key&amp;gt;], 모듈의 경우 module.&amp;lt;모듈 이름&amp;gt;[&amp;lt;key&amp;gt;]로 해당 리소스의 값을 참조한다.&lt;/li&gt;
&lt;li&gt;이 참조 방식을 통해 리소스 간 종속성을 정의하기도 하고 변수로 다른 리소스에서 사용하거나 출력을 위한 결과 값으로 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;/b&gt;main.tf 파일 수정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;local_file.abc는 변수의 map 형태의 값을 참조,&lt;/li&gt;
&lt;li&gt;local_file.def의 경우 local_file.abc 도한 결과가 map으로 반환되므로 다시 for_each 구문을 사용할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719661823062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
  default = {
    a = &quot;content a&quot;
    b = &quot;content b&quot;
    c = &quot;content c&quot;
  }
}

resource &quot;local_file&quot; &quot;abc&quot; {
  for_each = var.names
  content  = each.value
  filename = &quot;${path.module}/abc-${each.key}.txt&quot;
}

resource &quot;local_file&quot; &quot;def&quot; {
  for_each = local_file.abc
  content  = each.value.content
  filename = &quot;${path.module}/def-${each.key}.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1719661873973&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#  count 경우와 유사하게 중간 값을 삭제 후 확인
# main.tf
variable &quot;names&quot; {
  default = {
    a = &quot;content a&quot;
    c = &quot;content c&quot;
  }
}

resource &quot;local_file&quot; &quot;abc&quot; {
  for_each = var.names
  content  = each.value
  filename = &quot;${path.module}/abc-${each.key}.txt&quot;
}

resource &quot;local_file&quot; &quot;def&quot; {
  for_each = local_file.abc
  content  = each.value.content
  filename = &quot;${path.module}/def-${each.key}.txt&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;size16&quot;&gt;&lt;b&gt;key 값은 count의 index와는 달리 고유하므로 중간에 값을 삭제한 후 다시 적용해도 삭제한 값에 대해서만 리소스를 삭제한다. &lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;1380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nIOLJ/btsIhuLrUjj/4X5mDqJmxDuEcFQwCqtADk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nIOLJ/btsIhuLrUjj/4X5mDqJmxDuEcFQwCqtADk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nIOLJ/btsIhuLrUjj/4X5mDqJmxDuEcFQwCqtADk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnIOLJ%2FbtsIhuLrUjj%2F4X5mDqJmxDuEcFQwCqtADk%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;1842&quot; height=&quot;1380&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;1380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;for each: 데이터 유형&lt;/h3&gt;
&lt;pre id=&quot;code_1719662136695&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;aws_iam_user&quot; &quot;the-accounts&quot; {
  for_each = toset([&quot;Todd&quot;, &quot;James&quot;, &quot;Alice&quot;, &quot;Dottie&quot;])
  name     = each.key
}
# the &quot;for_each&quot; argument must be a map, or│ set of strings
# terraform plan으로 확인&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 유형 실습&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SkGWh/btsIhcdhoQ0/gO9raITHdXRNgk1CGMMKF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SkGWh/btsIhcdhoQ0/gO9raITHdXRNgk1CGMMKF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SkGWh/btsIhcdhoQ0/gO9raITHdXRNgk1CGMMKF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSkGWh%2FbtsIhcdhoQ0%2FgO9raITHdXRNgk1CGMMKF0%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;1213&quot; height=&quot;367&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;367&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;b&gt;기본 유형&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;string : 글자 유형&lt;/li&gt;
&lt;li&gt;number : 숫자 유형&lt;/li&gt;
&lt;li&gt;bool : true 또는 false&lt;/li&gt;
&lt;li&gt;any : 명시적으로 모든 유형이 허용됨을 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;집합 유형&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;list&lt;/b&gt; [&amp;lt;유형&amp;gt;]: &lt;b&gt;인덱스&lt;/b&gt; 기반 집합&lt;/li&gt;
&lt;li&gt;&lt;b&gt;map&lt;/b&gt; (&amp;lt;유형&amp;gt;): &lt;b&gt;값 = 속성&lt;/b&gt; 기반 집합이며 키값 기준 정렬&lt;/li&gt;
&lt;li&gt;&lt;b&gt;set&lt;/b&gt; (&amp;lt;유형&amp;gt;): &lt;b&gt;값 기반&lt;/b&gt; 집합이며 정렬 키값 기준 정렬&lt;/li&gt;
&lt;li&gt;&lt;b&gt;object&lt;/b&gt; ({&amp;lt;인수 이름&amp;gt;=&amp;lt;유형&amp;gt;, &amp;hellip;})&lt;/li&gt;
&lt;li&gt;&lt;b&gt;tuple&lt;/b&gt; ([&amp;lt;유형&amp;gt;, &amp;hellip;])&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;list와 set은 선언하는 형태가 비슷하지만 참조 방식이 인덱스와 키로 각각 차이가 있고, map와 set의 경우 선언된 값이 정렬되는 특징을 가진다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719664771091&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;string_a&quot; {
  type        = string
  default     = &quot;myString&quot;
}

variable &quot;number_a&quot; {
  type    = number
  default = 123
}

variable &quot;boolean&quot; {
  default = true
}

# (Array) list , set , tuple - value , [ ] 사용
variable &quot;list_set_tuple_a&quot; {
  default = [&quot;aaa&quot;, &quot;bbb&quot;, &quot;ccc&quot;]
}

variable &quot;list_set_tuple_b&quot; {
  type    = list(string)
  default = [&quot;bbb&quot;, &quot;ccc&quot;, &quot;aaa&quot;]
}

variable &quot;list_set_tuple_c&quot; {
  type    = set(string)
  default = [&quot;bbb&quot;, &quot;ccc&quot;, &quot;aaa&quot;]
}

variable &quot;list_set_tuple_d&quot; {
  default = [&quot;aaa&quot;, 1, false]
}

variable &quot;list_set_tuple_e&quot; {
  type    = tuple([string, number, bool])
  default = [&quot;aaa&quot;, 1, false]
}


# (Object) map , object - key : value , { } 사용
variable &quot;map_object_a&quot; {
  default = {&quot;a&quot; : &quot;aaa&quot;, &quot;b&quot; : &quot;bbb&quot; , &quot;c&quot; : &quot;ccc&quot;}
}

variable &quot;map_object_b&quot; {
  type    = map(string)
  default = {&quot;b&quot; : &quot;bbb&quot; , &quot;c&quot; : &quot;ccc&quot;, &quot;a&quot; : &quot;aaa&quot;}
}

variable &quot;map_object_c&quot; {
  default = {&quot;name&quot; : &quot;gasida&quot;, &quot;age&quot; : 27 }
}

variable &quot;map_object_d&quot; {
  type = object({ name = string, age = number })
  default = {&quot;name&quot; : &quot;gasida&quot;, &quot;age&quot; : 27 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1719664947520&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 확인
terraform plan

#콘솔
terraform console
-----------------
# 여러 값 확인
var.list_set_tuple_a
type(var.list_set_tuple_a)
exit
-----------------&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;for_each : count 와 비교&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 는 index 로 몇번에 어떤 내용이 있는지를 알 수 없다, 중간 리소스 삭제나 추가 시 문제 발생 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; for_each 표현식을 사용하면 &lt;span data-token-index=&quot;1&quot;&gt;리스트&lt;/span&gt; &lt;span style=&quot;color: #787774;&quot; data-token-index=&quot;3&quot;&gt;lists&lt;/span&gt;, &lt;span data-token-index=&quot;5&quot;&gt;집합&lt;/span&gt; &lt;span style=&quot;color: #787774;&quot; data-token-index=&quot;7&quot;&gt;sets&lt;/span&gt;, &lt;span data-token-index=&quot;9&quot;&gt;맵&lt;/span&gt; &lt;span style=&quot;color: #787774;&quot; data-token-index=&quot;11&quot;&gt;maps&lt;/span&gt; 를 사용하여 전체 &lt;span data-token-index=&quot;13&quot;&gt;리소스&lt;/span&gt;의 여러 &lt;span data-token-index=&quot;15&quot;&gt;복사본&lt;/span&gt; 또는 리소스 내 &lt;span data-token-index=&quot;17&quot;&gt;인라인&lt;/span&gt; 블록의 여러 복사본, &lt;span data-token-index=&quot;19&quot;&gt;모듈&lt;/span&gt;의 복사본을 생성 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;for_each&lt;/span&gt; 를 사용해 &lt;span data-token-index=&quot;2&quot;&gt;리소스&lt;/span&gt;를 &lt;span data-token-index=&quot;4&quot;&gt;맵&lt;/span&gt;으로 처리하면 &lt;span data-token-index=&quot;6&quot;&gt;컬렉션&lt;/span&gt; 중간의 항목도 안전하게 제거할 수 있어서, &lt;span data-token-index=&quot;8&quot;&gt;count&lt;/span&gt; 로 &lt;span data-token-index=&quot;10&quot;&gt;리소스를 배열 처리&lt;/span&gt;보다 이점이 크다.&lt;/p&gt;
&lt;pre id=&quot;code_1719665332299&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;&amp;lt;PROVIDER&amp;gt;_&amp;lt;TYPE&amp;gt;&quot; &quot;&amp;lt;NAME&amp;gt;&quot; {
  for_each = &amp;lt;COLLECTION&amp;gt;

  [CONFIG ...]
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1719665312945&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 예제. 3명의 IAM 사용자 생성
provider &quot;aws&quot; {
  region = &quot;ap-northeast-2&quot;
}

resource &quot;aws_iam_user&quot; &quot;myiam&quot; {
  for_each = toset(var.user_names)
  name     = each.value
}

variable &quot;user_names&quot; {
  description = &quot;Create IAM users with these names&quot;
  type        = list(string)
  default     = [&quot;gasida&quot;, &quot;akbun&quot;, &quot;ssoon&quot;]
}

output &quot;all_users&quot; {
  value = aws_iam_user.myiam
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 리소스의 구별되는 여러 복사본을 만들 때는 count 대신 for_each 를 사용하는 것이 바람직하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;for Expressions&lt;/span&gt; : 복합 형식 값의 형태를 변환하는 데 사용&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNVYOh/btsIhhSWTTL/WyLfsgs0qqCbkkRDdkDeuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNVYOh/btsIhhSWTTL/WyLfsgs0qqCbkkRDdkDeuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNVYOh/btsIhhSWTTL/WyLfsgs0qqCbkkRDdkDeuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNVYOh%2FbtsIhhSWTTL%2FWyLfsgs0qqCbkkRDdkDeuK%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;1143&quot; height=&quot;136&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;136&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;예를 들어 list 값의 포맷을 변경하거나 특정 접두사 prefix를 추가할 수도 있고, output에 원하는 형태로 반복적인 결과를 표현할 수 도 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;list&lt;/b&gt; 타입의 경우 &lt;b&gt;값&lt;/b&gt; 또는 &lt;b&gt;인덱스와 값&lt;/b&gt;을 반환&lt;/li&gt;
&lt;li&gt;&lt;b&gt;map&lt;/b&gt; 타입의 경우 &lt;b&gt;키&lt;/b&gt; 또는 &lt;b&gt;키와 값&lt;/b&gt;에 대해 반환&lt;/li&gt;
&lt;li&gt;&lt;b&gt;set&lt;/b&gt; 타입의 경우 &lt;b&gt;키 값&lt;/b&gt;에 대해 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;jsonencode&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 후 확인 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;jsonencode&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Function -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.hashicorp.com/terraform/language/functions/jsonencode&quot;&gt;링크&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jsonencode&amp;nbsp;&lt;b&gt;encodes&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;a given value to a string using&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;JSON syntax&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719679317258&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
  default = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]
}

resource &quot;local_file&quot; &quot;abc&quot; {
  content  = jsonencode(var.names) # 결과 : [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;] # list의 내용을 담는 리소스를 생성, var.name의 내용이 결과 파일에 content로 기록됨
  content  = jsonencode([for s in var.names : upper(s)]) # 내부값 일괄 변경 # 결과 : [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;]
  filename = &quot;${path.module}/abc.txt&quot;
}

output &quot;file_content&quot; {
  value = local_file.abc.content
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;for 구문을 사용하는 몇 가지 규칙은 다음과 같다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;list&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;/b&gt;를 반환하고 뒤의 인수가 &lt;b&gt;값&lt;/b&gt;을 반환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관용적으로 &lt;b&gt;인덱스는 i, 값은 v&lt;/b&gt;로 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;map&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;/b&gt;를 반환하고 뒤의 인수가 &lt;b&gt;값&lt;/b&gt;을 반환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관용적으로 &lt;b&gt;키는 k, 값은 v&lt;/b&gt;로 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결과 값은 for 문을 묶는 기호가 [ ]인 경우 &lt;b&gt;tuple&lt;/b&gt;로 반환되고 { }인 경우 &lt;b&gt;object&lt;/b&gt; 형태로 반환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m83W9/btsIhwCw8PN/wHdugsAkKer3PvZGubZed1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m83W9/btsIhwCw8PN/wHdugsAkKer3PvZGubZed1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m83W9/btsIhwCw8PN/wHdugsAkKer3PvZGubZed1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm83W9%2FbtsIhwCw8PN%2FwHdugsAkKer3PvZGubZed1%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;922&quot; height=&quot;154&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;object&lt;/b&gt; 형태의 경우 &lt;b&gt;키&lt;/b&gt;와 &lt;b&gt;값&lt;/b&gt;에 대한 쌍은 &amp;rArr; 기호로 구분&lt;/li&gt;
&lt;li&gt;{ } 형식을 사용해 object 형태로 결과를 반환하는 경우 키 값은 고유해야 하므로 값 뒤에 &lt;b&gt;그룹화 모드 심볼&lt;/b&gt;(&amp;hellip;)를 붙여서 키의 중복을 방지(SQL의 group by 문 또는 Java의 MultiValueMap과 같은 개념)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;if&lt;/b&gt; 구문을 추가해 &lt;b&gt;조건&lt;/b&gt; 부여 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719679667402&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# for문 예시
variable &quot;names&quot; {
  type    = list(string)
  default = [&quot;a&quot;, &quot;b&quot;]
}

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

output &quot;B_index_and_value&quot; {
  value = [for i, v in var.names : &quot;${i} is ${v}&quot;]
}

output &quot;C_make_object&quot; {
  value = { for v in var.names : v =&amp;gt; upper(v) }
}

output &quot;D_with_filter&quot; {
  value = [for v in var.names : upper(v) if v != &quot;a&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1719679721924&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# map 예시
variable &quot;members&quot; {
  type = map(object({
    role = string
  }))
  default = {
    ab = { role = &quot;member&quot;, group = &quot;dev&quot; }
    cd = { role = &quot;admin&quot;, group = &quot;dev&quot; }
    ef = { role = &quot;member&quot;, group = &quot;ops&quot; }
  }
}

output &quot;A_to_tupple&quot; {
  value = [for k, v in var.members : &quot;${k} is ${v.role}&quot;]
}

output &quot;B_get_only_role&quot; {
  value = {
    for name, user in var.members : name =&amp;gt; user.role
    if user.role == &quot;admin&quot;
  }
}

output &quot;C_group&quot; {
  value = {
    for name, user in var.members : user.role =&amp;gt; name...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dynamic: 리소스 내부 속성 블록을 동적인 블록으로 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;count 나 for_each 구문을 사용한 리소스 전체를 여러 개 생성하는 것 이외도 &lt;b&gt;리소스 내&lt;/b&gt;에 선언되는 &lt;b&gt;구성 블록&lt;/b&gt;을 다중으로 작성해야 하는 경우가 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들면 AWS Security Group 리소스 구성에 ingress, egress 요소가 리소스 선언 내부에서 블록 형태로 여러 번 정의되는 경우다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;리소스 내의 블록 속성(Attributes as Blocks)은 리소스 자체의 반복 선언이 아닌 내부 속성 요소 중 블록으로 표현되는 부분에 대해서만 반복 구문을 사용해야 하므로, 이때 &lt;b&gt;dynamic 블록을 사용해 동적인 블록을 생성&lt;/b&gt; 할 수 있다.&lt;/li&gt;
&lt;li&gt;dynamic 블록을 작성하려면, 기존 블록의 속성 이름을 dynamic 블록의 이름으로 선언하고 기존 블록 속성에 정의되는 내용을 content 블록에 작성한다.&lt;/li&gt;
&lt;li&gt;반복 선언에 사용되는 반복문 구문은 for_each를 사용한다. 기존 for_each 적용 시 each 속성에 key, value가 적용되었다면 dynamic에서는 dynamic에 지정한 이름에 대해 속성이 부여된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719679995202&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 일반 생성
resource &quot;provider_resource&quot; &quot;name&quot; {
  name = &quot;some_resource&quot;

  some_setting {
    key = a_value
  }

  some_setting {
    key = b_value
  }

  some_setting {
    key = c_value
  }

  some_setting {
    key = d_value
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1719679983527&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# dynamic 블록 적용
resource &quot;provider_resource&quot; &quot;name&quot; {
  name = &quot;some_resource&quot;

  dynamic &quot;some_setting&quot; {
    for_each = {
      a_key = a_value
      b_key = b_value
      c_key = c_value
      d_key = d_value
    }

    content {
      key = some_setting.value
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.10 조건문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Conditional Expressions] 테라폼에서의 &lt;span data-token-index=&quot;1&quot;&gt;조건식&lt;/span&gt;은 &lt;span data-token-index=&quot;3&quot;&gt;3항 연산자&lt;/span&gt; 형태를 갖는다. 조건은 &lt;span data-token-index=&quot;5&quot;&gt;true&lt;/span&gt; 또는 &lt;span data-token-index=&quot;7&quot;&gt;false&lt;/span&gt;로 확인되는 모든 표현식을 사용할 수 있다 - &lt;a style=&quot;color: #000000;&quot; href=&quot;https://developer.hashicorp.com/terraform/language/expressions/conditionals&quot; data-token-index=&quot;9&quot;&gt;&lt;span&gt;링크&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 비교, 논리 연산자를 사용해 조건을 확인한다.&lt;/li&gt;
&lt;li&gt;조건식은 &lt;b&gt;?&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;/b&gt; 기호를 기준으로 &lt;b&gt;왼쪽&lt;/b&gt;이 조건에 대해 &lt;b&gt;true&lt;/b&gt;가 반환되는 경우이고 &lt;b&gt;오른쪽&lt;/b&gt;이 &lt;b&gt;false&lt;/b&gt;가 반환되는 경우다.&lt;/li&gt;
&lt;li&gt;다음의 예에서 var.a가 빈 문자열이 아니라면 var.a를 나타내지만, 비어 있을 때는 &amp;ldquo;default-a&amp;rdquo;를 반환한다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719680074000&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# &amp;lt;조건 정의&amp;gt; ? &amp;lt;옳은 경우&amp;gt; : &amp;lt;틀린 경우&amp;gt;
var.a != &quot;&quot; ? var.a : &quot;default-a&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건식의 각 조건은 비교 대상의 형태가 다르면 테라폼 실행 시 조건 비교를 위해 형태를 추론하여 자동으로 변환하는데, &lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;명시적인 형태 작성&lt;/span&gt;&lt;/b&gt;을 권장한다.&lt;/p&gt;
&lt;pre id=&quot;code_1719680135037&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 조건식 형태 권장 사항 
var.example ? 12 : &quot;hello&quot;            # 비권장
var.example ? &quot;12&quot; : &quot;hello&quot;          # 권장
var.example ? tostring(12) : &quot;hello&quot;  # 권장&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건식은 단순히 특정 속성에 대한 정의, 로컬 변수에 대한 재정의, 출력 값에 대한 조건 정의 뿐만 아니라&lt;b&gt; &lt;span data-token-index=&quot;1&quot;&gt;리소스 생성 여부&lt;/span&gt;에 응용&lt;/b&gt;할 수 있다. count에 조건식을 결합한 경우 다음과 같이 특정 조건에 따라 리소스 생성 여부를 선택할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1719680172491&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 예제
variable &quot;enable_file&quot; {
  default = true
}

resource &quot;local_file&quot; &quot;foo&quot; {
  count    = var.enable_file ? 1 : 0
  content  = &quot;foo!&quot;
  filename = &quot;${path.module}/foo.bar&quot;
}

output &quot;content&quot; {
  value = var.enable_file ? local_file.foo[0].content : &quot;&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;3.11 함수&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테라폼은 프로그래밍 언어적인 특성을 가지고 있어서, 값의 유형을 변경하거나 조합할 수 있는&lt;b&gt; 내장 함수&lt;/b&gt;를 사용 할 수 있다. - &lt;a style=&quot;color: #000000;&quot; href=&quot;https://developer.hashicorp.com/terraform/language/functions&quot; data-token-index=&quot;1&quot;&gt;&lt;span&gt;링크&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;단, 내장된 함수(upper 등) 외에 사용자가 구현하는 별도의 사용자 정의 함수를 지원하지는 않는다.&lt;/li&gt;
&lt;li&gt;함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환이 있다.&lt;/li&gt;
&lt;li&gt;테라폼 코드에 함수를 적용하면 변수, 리소스 속성, 데이터 소스 속성, 출력 값 표현 시 작업을 동적이고 효과적으로 수행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Cloud Architect/IaC</category>
      <author>나리 집사</author>
      <guid isPermaLink="true">https://nali.tistory.com/380</guid>
      <comments>https://nali.tistory.com/380#entry380comment</comments>
      <pubDate>Sat, 29 Jun 2024 20:22:49 +0900</pubDate>
    </item>
  </channel>
</rss>