diff --git a/Gemfile b/Gemfile index 3be9c3cd812e..be841e6f6204 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,5 @@ source "https://rubygems.org" -gemspec +gemspecs +gem "minimal-mistakes-jekyll" +gem 'tzinfo' +gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] \ No newline at end of file diff --git a/_config.yml b/_config.yml index af7d57221b02..eb8112de87f3 100644 --- a/_config.yml +++ b/_config.yml @@ -12,31 +12,31 @@ # theme : "minimal-mistakes-jekyll" # remote_theme : "mmistakes/minimal-mistakes" -minimal_mistakes_skin : "default" # "air", "aqua", "contrast", "dark", "dirt", "neon", "mint", "plum", "sunrise" +minimal_mistakes_skin : "air" # "air", "aqua", "contrast", "dark", "dirt", "neon", "mint", "plum", "sunrise" # Site Settings -locale : "en-US" +locale : "ko-KR" rtl : # true, false (default) # turns direction of the page into right to left for RTL languages -title : "Site Title" -title_separator : "-" -subtitle : # site tagline that appears below site title in masthead -name : "Your Name" -description : "An amazing website." -url : # the base hostname & protocol for your site e.g. "https://mmistakes.github.io" +title : "Scorpius" +title_separator : "|" +subtitle : "Better today than yesterday" # site tagline that appears below site title in masthead +name : "LewisJLee" +description : "Sophisticating DevOps Technician" +url : "https://lewisjlee.github.io" baseurl : # the subpath of your site, e.g. "/blog" repository : # GitHub username/repo-name e.g. "mmistakes/minimal-mistakes" teaser : # path of fallback teaser image, e.g. "/assets/images/500x300.png" -logo : # path of logo image to display in the masthead, e.g. "/assets/images/88x88.png" +logo : "/assets/logo/scorpius.jpg" # path of logo image to display in the masthead, e.g. "/assets/images/88x88.png" masthead_title : # overrides the website title displayed in the masthead, use " " for no title -breadcrumbs : # true, false (default) +breadcrumbs : "true" # true, false (default) words_per_minute : 200 enable_copy_code_button : # true, false (default) copyright : # "copyright" name, defaults to site.title copyright_url : # "copyright" URL, defaults to site.url comments: - provider : # false (default), "disqus", "discourse", "facebook", "staticman", "staticman_v2", "utterances", "giscus", "custom" + provider : "disqus" # false (default), "disqus", "discourse", "facebook", "staticman", "staticman_v2", "utterances", "giscus", "custom" disqus: - shortname : # https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname- + shortname : "scorpius" # https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname- discourse: server : # https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963 , e.g.: meta.discourse.org facebook: @@ -84,7 +84,7 @@ google: # SEO Related google_site_verification : bing_site_verification : -naver_site_verification : +naver_site_verification : "de6b84793d74bf5d4bd7786bcad5550fe0b15427" yandex_site_verification : baidu_site_verification : @@ -105,60 +105,44 @@ social: # Analytics analytics: - provider : # false (default), "google", "google-universal", "google-gtag", "custom" + provider : "google-gtag" # false (default), "google", "google-universal", "google-gtag", "custom" google: - tracking_id : - anonymize_ip : # true, false (default) + tracking_id : "G-VL61527HF6" + anonymize_ip : false # true, false (default) # Site Author author: - name : "Your Name" + name : "LewisJLee" avatar : # path of avatar image, e.g. "/assets/images/bio-photo.jpg" - bio : "I am an **amazing** person." - location : "Somewhere" - email : + bio : "SRE/DevOps Engineer | System Administrator" + location : "Sungnam South Korea" links: - label: "Email" icon: "fas fa-fw fa-envelope-square" - # url: "mailto:your.name@email.com" - - label: "Website" - icon: "fas fa-fw fa-link" + url: "mailto:earthlive1102@gmail.com" + #- label: "Website" + # icon: "fas fa-fw fa-link" # url: "https://your-website.com" - - label: "Twitter" - icon: "fab fa-fw fa-twitter-square" - # url: "https://twitter.com/" - - label: "Facebook" - icon: "fab fa-fw fa-facebook-square" - # url: "https://facebook.com/" - label: "GitHub" icon: "fab fa-fw fa-github" - # url: "https://github.com/" - - label: "Instagram" - icon: "fab fa-fw fa-instagram" - # url: "https://instagram.com/" + url: "https://github.com/lewisjlee" + #- label: "Instagram" + # icon: "fab fa-fw fa-instagram" + # url: "https://www.instagram.com/11gntlee02" # Site Footer footer: links: - - label: "Twitter" - icon: "fab fa-fw fa-twitter-square" - # url: - - label: "Facebook" - icon: "fab fa-fw fa-facebook-square" - # url: - label: "GitHub" icon: "fab fa-fw fa-github" + url: "https://github.com/lewisjlee" + #- label: "Bitbucket" + # icon: "fab fa-fw fa-bitbucket" # url: - - label: "GitLab" - icon: "fab fa-fw fa-gitlab" - # url: - - label: "Bitbucket" - icon: "fab fa-fw fa-bitbucket" - # url: - - label: "Instagram" - icon: "fab fa-fw fa-instagram" - # url: + #- label: "Instagram" + # icon: "fab fa-fw fa-instagram" + # url: "https://www.instagram.com/11gntlee02" # Reading Files @@ -227,7 +211,7 @@ sass: # Outputting permalink: /:categories/:title/ -timezone: # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# timezone: Asia/Seoul # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones # Pagination with jekyll-paginate @@ -290,16 +274,16 @@ tag_archive: type: liquid path: /tags/ # https://github.com/jekyll/jekyll-archives -# jekyll-archives: -# enabled: -# - categories -# - tags -# layouts: -# category: archive-taxonomy -# tag: archive-taxonomy -# permalinks: -# category: /categories/:name/ -# tag: /tags/:name/ +jekyll-archives: + enabled: + - categories + - tags + layouts: + category: archive-taxonomy + tag: archive-taxonomy + permalinks: + category: /categories/:name/ + tag: /tags/:name/ # HTML Compression @@ -320,6 +304,10 @@ defaults: layout: single author_profile: true read_time: true - comments: # true + comments: true share: true related: true + show_date: true + toc: false + toc_sticky: false +date_format: "%Y-%m-%d" diff --git a/_data/navigation.yml b/_data/navigation.yml index 6f30866f3bed..13179dee3020 100644 --- a/_data/navigation.yml +++ b/_data/navigation.yml @@ -1,7 +1,19 @@ # main links main: - - title: "Quick-Start Guide" - url: https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/ + - title: "Categories" + url: /categories/ + - title: "Tag" + url: /tags/ + - title: "Search" + url: /search/ + +docs: + - title: "AWS" + children: + - title: "S3" + url: /categories/#s3 + - title: "EKS" + url: /categories/#eks # - title: "About" # url: https://mmistakes.github.io/minimal-mistakes/about/ # - title: "Sample Posts" diff --git a/_pages/404.md b/_pages/404.md new file mode 100644 index 000000000000..2448e349dc8e --- /dev/null +++ b/_pages/404.md @@ -0,0 +1,12 @@ +--- +title: "Page Not Found" +excerpt: "Page not found. Your pixels are in another canvas." +sitemap: false +permalink: /404.html +--- + + + \ No newline at end of file diff --git a/_pages/category-archive.md b/_pages/category-archive.md new file mode 100644 index 000000000000..1696093e61ec --- /dev/null +++ b/_pages/category-archive.md @@ -0,0 +1,9 @@ +--- + +title: "Categories" +layout: categories +permalink: /categories/ +author_profile: true +sidebar_main: true + +--- \ No newline at end of file diff --git a/_pages/search.md b/_pages/search.md new file mode 100644 index 000000000000..9a05649273fd --- /dev/null +++ b/_pages/search.md @@ -0,0 +1,5 @@ +--- +title: Search +layout: search +permalink: /search/ +--- \ No newline at end of file diff --git a/_pages/tag-archive.md b/_pages/tag-archive.md new file mode 100644 index 000000000000..adf8c24c2925 --- /dev/null +++ b/_pages/tag-archive.md @@ -0,0 +1,9 @@ +--- + +title: "Tag" +layout: tags +permalink: /tags/ +author_profile: true +sidebar_main: true + +--- \ No newline at end of file diff --git a/_posts/2024-11-09-nlb_debug.md b/_posts/2024-11-09-nlb_debug.md new file mode 100644 index 000000000000..32bcdb2b56ae --- /dev/null +++ b/_posts/2024-11-09-nlb_debug.md @@ -0,0 +1,71 @@ +--- +layout: single +title: "Load Balancer Controller를 통한 NLB 배포 중 오류가 발생한다고?" +categories: EKS +tag: [AWS, aws, 쿠버네티스, Kubernetes, EKS, eks] +author_profile: false +sidebar: + nav: "docs" +--- + +**[공지사항]** +이전부터 학습하고 연구한 내용들을 함께 나누기 위해 최근 기술블로그를 개설하고 지속적으로 업로드하고 있습니다. 많은 관심과 피드백 부탁드립니다! 감사합니다 :) +{: .notice--success} + +## 공식 문서대로 진행했는데 오류가 발생한다면 + +새로운 플랫폼이나 오픈 소스를 도입할 때 흔히 공식 문서를 참고하곤 합니다. 하지만 사용하려는 기술의 벤더가 제공하는 공식 문서대로 따라했는데 원하는 방식대로 동작하지 않았던 경험을 다들 한번쯤 겪어보셨을 수도 있습니다. + +저는 AWS EKS 클러스터를 공부하던 중 Load Balancer를 배포하기 위해 관련 AWS Documentation들을 참고하여 sample 실습을 진행하였습니다. CSP가 직접 제공하는 매뉴얼이라서 순서대로 따라하기만 하면 될 줄 알았지만, 결과는 그렇지 않았습니다. NLB를 배포하기 위해 Load Balancer Controller를 설치하고 로드밸런서 서비스 매니페스트를 적용했는데, 서비스와 NLB 자체는 생성됐지만 고유의 external-ip 주소를 가져야 할 서비스에 주소가 mapping되지 않았고 대상 그룹에 sample 디플로이먼트 내 파드들 또한 추가되지 않았습니다. 당연히 외부에서 서비스에 접근할 수도 없었고요. + +![스크린샷 2024-11-18 133938.png](../../images/2024-11-09-nlb_debug/6297016d372b587b610a7efb8260a6a20c7e4544.png) + +![스크린샷 2024-11-18 134433.png](../../images/2024-11-09-nlb_debug/9135e185725429890b082bb9f8fcbac8daa02f11.png) + +### Load Balancer Controller 설치 및 NLB 배포 공식 문서 링크 + +[AWS Load Balancer Controller](https://docs.aws.amazon.com/eks/latest/userguide/lbc-helm.html) + +[Network Load Balancing](https://docs.aws.amazon.com/eks/latest/userguide/network-load-balancing.html) + +##### AWS Load Balancer Controller + +EKS 클러스터 내 LoadBalancer 서비스를 생성하면 AWS API를 통해 ELB를 생성하고 통합할 수 있도록 지원하는 컨트롤러입니다. 정의된 LoadBalancer 매니페스트 내 Annotations 값을 참조하여 ELB를 생성하는 역할을 합니다. + +##### Network Load Balancer + +외부 요청을 L4 기반으로 각 타겟에 분산하는 로드밸런서로 주로 게임 혹은 미디어 스트리밍 서비스에 사용되고 있습니다. 고정 IP를 갖고 특정한 형식의 도메인과 mapping할 수 있습니다. + +### 원인 분석 + +공식적으로 제공된 문서를 따라했는데 오류가 발생하였다는 점이 당황스러웠지만 차근차근 원인을 분석해보기로 했습니다. 설치한 Load Balancer Controller 파드 로그와 로드밸런서 서비스를 describe했을 때 출력되는 이벤트를 확인한 결과 다음과 같은 메시지를 공통적으로 출력하는 것을 확인할 수 있었습니다. + +```bash +kubectl logs -n nlb-sample-app [Load Balancer Controller 파드명] +``` + +```bash +kubectl describe svc -n nlb-sample-app nlb-sample-service +``` + +**"is not authorized to perform: elasticloadbalancing:DescribeListenerAttributes because no identity-based policy allows the elasticloadbalancing:DescribeListenerAttributes action"** + +Load Balancer Controller를 설치할 때 첫번째로 NLB를 배포하기 위한 IAM 역할(ServiceAccount)을 생성하게 됩니다. 이 역할에 연결하기 위한 정책을 공식 문서에 명시된 레포지토리에서 JSON 파일을 다운로드 받은 다음 생성하게 되는데, + +정책 파일에 'elasticloadbalancing:DescribeListenerAttributes' 액션이 허용되어 있지 않아 리스너의 정보를 읽어올 수 없다는 메시지를 확인할 수 있었습니다. + +### 해결 + +![20241109_AWSLoadBalancerControllerIAMPolicy_수정.png](../../images/2024-11-09-nlb_debug/bf1bab5ac2feef46e2ceceff264396796aba56c8.png) + +AWS 콘솔에서 직접 연결된 IAM 정책에 해당 액션을 추가해주니 정상적으로 external-ip가 mapping되고 리스너가 트래픽을 전달하는 대상 그룹에 sample 파드가 추가되는 것을 확인할 수 있었습니다. + +![loading-ag-237](../../images/2024-11-09-nlb_debug/45863d12b409acb0e4c107a5ff0e7f4a79b02c5f.png) + +![스크린샷 2024-11-18 135602.png](../../images/2024-11-09-nlb_debug/a7d33bdf6c72e2b951172d0dc3aa68bf8cddd0b0.png) + +### 결론 + +때로는 공식 문서가 정답이 아닌 경우가 생길 수 있습니다. 가장 신뢰할 만한 지침서가 되지만 기술 벤더의 사정에 따라 Deprecate된 기능의 문서를 여전히 지원하기도 하고 완전한 표준이 될 만한 문서 또한 개인 환경에 따라 정상적으로 적용되지 않기도 합니다. 각자 프로젝트를 진행하는 환경에서 매뉴얼을 정확하게 따라했음에도 불구하고 오류가 발생한다면 그럼에도 불구하고 이를 해결할 수 있는 방법은 스스로 아는 관련 배경 지식이나 공식 문서 상의 다른 페이지, 다른 팀원과의 토의로 충분히 해결하고 적용해보면 좋은 경험치가 될 수 있습니다. + + diff --git a/_posts/2024-12-05-dns_theory.md b/_posts/2024-12-05-dns_theory.md new file mode 100644 index 000000000000..eb79ad5ce41c --- /dev/null +++ b/_posts/2024-12-05-dns_theory.md @@ -0,0 +1,131 @@ +--- +layout: single +title: "DNS 동작 원리 - 도메인과 URL을 IP 주소로 해석하는 과정" +categories: Network +tag: [DNS, Network, 네트워크] +author_profile: false +sidebar: + nav: "docs" +--- + +**[공지사항]** +이전부터 학습하고 연구한 내용들을 함께 나누기 위해 최근 기술블로그를 개설하고 지속적으로 업로드하고 있습니다. 많은 관심과 피드백 부탁드립니다! 감사합니다 :) +{: .notice--success} + +네트워크 통신에서 목적지에 도달하기 위해서는 목적지의 IP 주소가 필요합니다. 하지만 IP는 8bit와 4개의 옥텟으로 구성된 2진수를 10진수 형태로 보여주는 고유값이기 때문에 이러한 모든 사이트의 IP 주소를 일일이 기억하고 입력하기는 굉장히 불편할 것입니다. 그래서 좀 더 알아보기 쉬운 고유의 영문 이름 형태의 주소를 IP 주소와 mapping하고 해당 영문 이름 주소를 입력하여 목적지에 연결될 수 있도록 하면 어떨까 하는 발상에서, 도메인과 URL, DNS 라는 개념이 등장했습니다. + +본 포스팅에서는 우리가 흔히 사용하는 도메인의 구조와 이를 IP 주소로 해석하여 목적지 서버에 접속하는 과정에 대해서 다뤄보려고 합니다. + +## 도메인(Domain)과 URL의 구조 + +도메인은 특정 영역이나 범위를 지칭하는 고유 명사를 뜻하는 말로, IT에서는 사용자의 네트워크 요청을 받고 응답하는 서버 혹은 인프라 집단에 공통적으로 부여하는 고유의 영문 이름을 의미합니다. 반면 URL은 인터넷상에서 얻고자 하는 리소스의 위치를 지칭하는 말로, 해당 리소스를 저장하고 응답하는 서버의 도메인에 어떤 형태의 응답을 받을 것인가를 나열한 Query String과 프로토콜을 붙인 형태로 나타납니다. + +웹 브라우저는 자동으로 https 프로토콜이 적용되므로 우리가 특정 웹 사이트의 홈페이지에 접속하기 위해 입력하는 것이 그 사이트의 도메인이 됩니다. 그리고 홈페이지 내에서 사이트맵이나 게시글 등 버튼을 누르면 출력되는 화면이나 콘텐츠 자체를 지칭할 땐 URL을 사용해요. 따라서 별도의 웹 서핑 없이 사이트에 저장된 콘텐츠를 바로 불러오기 위해서는 도메인이 아닌 그 콘텐츠의 URL을 입력해서 불러오는 것이 훨씬 편리할 수 있습니다. + + + +##### Root + +위의 이미지는 최근 화제가 되었던 '흑백요리사'를 구글 사이트에 검색했을 때의 URL입니다. 이 때 사용한 구글 사이트의 도메인은 www.google.com인데 이러한 도메인은 끝에 **온점(.)** 하나를 생략한 형태입니다. 즉 사실은 **www.google.com.** 이 되는 것입니다. 이 온점(.)은 Root 네임 서버부터 도메인 이름 해석을 시작한다는 의미를 갖고 있는데, 모든 도메인은 이러한 이름 해석 방식으로 mapping된 IP 주소를 해석하므로 대체로 생략됩니다. + +##### TLD + +Top-Level Domain의 약자로, 모든 도메인이 흔히 갖고 있는 도메인의 가장 오른쪽 부분, 즉 최상위 도메인을 의미합니다. TLD는 **일반 TLD(gTLD)** 및 **국가 코드 TLD(ccTLD)** 로 구분되는데, 일반 TLD는 **.net, .com, .org** 등 특정 국가에 종속되지 않은 사이트의 도메인에 사용되고 국가 코드 TLD는 국내 기업이나 관공서 등 특정 국가에 종속된 기관의 사이트의 도메인에 사용되는 최상위 도메인입니다. 예시로 우리나라의 경우는 **.kr**, 미국은 **.us**, 중국은 **.cn**가 있습니다. + +##### SLD + +Second-Level Domain의 약자로, 도메인의 가장 오른쪽에서 두번째 부분입니다. + +SLD에는 **naver**, **google**과 같이 사이트가 가진 고유 이름이나 사이트를 운영하는 회사의 이름이 되는 경우가 있고 **co**, **or**과 같이 기업이나 비영리 기관 등 사이트 운영 단체의 유형을 뜻하는 SLD가 있습니다. + +후자의 경우는 **co.kr**, **or.kr**과 같이 앞서 설명한 국가 코드 TLD 앞에 붙이는 경우가 많아서 이러한 조합 자체가 하나의 TLD(ccSLD)로 간주되어 처리되기도 합니다. + +##### Root Domain + +SLD와 TLD를 합친 형태로, 사이트의 고유한 이름과 운영 목적, 기관 등의 정보가 포함된 고유 도메인 입니다. 특정 사이트를 개설할 경우 이러한 루트 도메인을 도메인 등록 기관(Domain Registrar)으로부터 구매 및 등록, 소유할 수 있습니다. 도메인 등록 기관으로는 가비아, GoDaddy, 아마존 Route 53이 있으며 이들 기관에서 도메인을 구입한 후에는 주기적으로 기간을 연장해야 계속 사용할 수 있습니다. + +##### Sub Domain + +루트 도메인으로부터 용도별로 파생되는 도메인을 말합니다. 앞서 예시를 든 구글 도메인의 경우 구글 드라이브는 **drive.google.com**, Gmail은 **mail.google.com**, 일반 홈페이지는 **www.google.com**의 도메인을 갖는데요. 이 때 각 용도별 접두사인 **drive.**, **mail.**, **www.** 가 서브 도메인이 됩니다. 이 외에도 사용자가 접근할 수 있는 다양한 서브 도메인이 존재할 수 있고 **api.**, **user.** 등 시스템 내부적으로 사용할 수 있는 서브 도메인을 둘 수도 있습니다. + +##### Query String + +쿼리 스트링은 사용자가 웹 사이트에서 로그인을 하거나 정보를 검색하는 등의 다양한 기능을 사용할 때 시스템 내부에 저장된 적절한 데이터와 양식을 응답받을 수 있는 질의문입니다. 사진에 첨부된 URL은 구글 사이트에서 '흑백요리사'를 검색했을 때 검색 결과를 응답받는 쿼리 스트링이 구글 도메인 뒤에 붙어 있는 것을 확인할 수 있습니다. + +## 도메인 이름 해석 과정 + + + +##### Pre) hosts 파일에 질의 + +사용자가 웹 브라우저에 "www.google.com"라는 도메인을 입력한다고 가정해 보겠습니다. + +위의 이미지에서 확인할 수 있는 단계는 아니지만, 사용자의 PC는 정식으로 DNS 서버에 도메인 이름 해석을 질의하기 전에 hosts 라는 설정 파일을 확인합니다. 정확히는 Unix 계열의 OS에서는 /etc/hosts, Windows 계열에서는 C:\Windows\System32\drivers\etc\hosts 파일을 확인하죠. 이 파일에 요청하는 도메인에 mapping하는 IP 주소가 있으면 해당 IP 주소를 응답받아 접속하게 됩니다. 파일에 기재된 IP 주소가 해당 도메인의 실제 IP 주소가 아니더라도 hosts 파일에 적혀 있다면 무조건 해당 IP 주소를 응답해요. 꽤 중요한 역할을 하는 파일이기 때문에 hosts 파일은 관리자 권한을 가져야만 접근할 수 있도록 설정되어 있습니다. 이 파일은 주로 개발자나 엔지니어 분들이 PROD 환경의 실제 도메인을 갖고 DEV 혹은 TEST 환경에 테스트 목적으로 접속할 때 사용되곤 합니다. + + + + + +각각 윈도우와 우분투 리눅스 OS에서 hosts 파일을 위와 같이 편집하면 통상적인 구글 사이트의 IP 주소가 아닌 기재된 주소 1.2.3.4로 접속하게 됩니다. + +##### 1) Local DNS에 질의 + +hosts 파일에 정보가 없으면 사용자 PC에 설정된 Local DNS 서버에 **UDP 기반 dns 프로토콜**을 통해 질의합니다. 캐시된 데이터가 있을 경우 이를 응답하고, 없을 경우 Root/TLD/SLD 네임 서버에 차례로 질의하며 도메인을 해석하는 과정을 수행하게 됩니다. 바로 이 과정을 **Recursive Query**라고 합니다. + +Local DNS는 ISP 업체가 설정한 DNS 서버 혹은 사내에서 운영하는 DNS 서버를 지정하거나 구글 DNS 서버(8.8.8.8)와 같이 개인이 원하는 DNS 서버를 지정할 수 있습니다. + +유닉스 계열의 OS에서는 /etc/resolv.conf 라는 파일이나 resolvectl 명령어에서, 윈도우에서는 제어판이나 설정 탭에서 Local DNS 서버를 설정할 수 있습니다. + +##### 2) Root NS에 질의 + +Local DNS는 요청받은 도메인을 가장 먼저 Root 네임 서버에 질의합니다. Root 네임 서버는 다양한 TLD에 대한 DNS Zone을 보유하고 이를 바탕으로 .com TLD에 대한 네임 서버 주소를 응답합니다. + +Root 네임 서버는 전 세계에 13개가 분포해 있으며, ICANN(국제인터넷주소관리기구)에 의해 관리됩니다. + +##### 3) TLD NS에 질의 + +Local DNS는 응답받은 .com TLD 네임 서버에 질의합니다. .com TLD 네임 서버는 .com 이라는 TLD를 가진 수많은 SLD에 대한 DNS Zone을 보유하고 이를 바탕으로 google.com SLD에 대한 네임 서버 주소를 응답합니다. + +TLD 네임 서버는 ICANN 산하의 IANA(인터넷 할당 번호 관리 기관이라는 뜻으로 직역되는 최상위 도메인 관리 기관)에서 관리되며, TLD에 대한 DNS zone을 관리하는 역할을 합니다. + +##### 4) SLD NS에 질의 + +Local DNS는 응답받은 google.com SLD 네임 서버에 질의합니다. 이 때 google.com 도메인 자체가 고유한 루트 도메인이 되고 www. 라는 서브 도메인이 포함된 최종 FQDN(Fully Qualified Domain Name)의 주소를 응답받는 순서만 남게 됩니다. SLD 네임 서버는 Local DNS가 요청하는 도메인에 대한 최종 IP를 응답합니다. + +이러한 루트 도메인의 DNS Zone을 보유한 SLD 네임 서버는 해당 도메인을 발급하고 등록한 기관에서 관리는 경우가 있고, 도메인 등록 기관에서 발급받은 도메인을 다른 네임 서버의 DNS Zone에 등록하여 Local DNS에 응답하도록 할 수 있습니다. + +##### 5) 최종적으로 해석된 IP 전달 및 캐시 + +Local DNS는 응답받은 www.google.com도메인의 IP를 사용자에 응답하고 사용자는 해당 IP를 통해 구글 웹 서버에 HTTP 프로토콜로 접근할 수 있게 됩니다. 비로소 구글 웹 사이트에 접속하게 되는 것이죠. 구글 웹 사이트에서 여러 가지 기능들을 사용하게 될 텐데 그럴 때마다 일일히 동일한 Recursive Query를 수행하게 되면 Local DNS에 부하가 가중될 뿐만 아니라 응답을 받는 데에도 상당한 시간을 소모하게 됩니다. 이러한 문제를 고려하여 네임 서버는 Local DNS에 사용자가 요청한 도메인의 IP 주소를 일정 시간 동안 캐시합니다. 이렇게 캐싱되는 시간을 TTL(Time To Live)이라고 부르고, 이 TTL 값은 네임 서버에서 지정합니다. + +## 도메인 이름 해석 과정의 유동성 + +그렇다면 특정 도메인 이름 해석을 통해 응답받을 수 있는 IP는 고정적일까요? 그렇지 않습니다. 특히 전 세계에서 접근하는 대규모 사이트의 경우 많은 수의 서버를 보유하기 때문에 사용자는 지역별 데이터 거버넌스와 물리적 거리, 요청 리소스 등을 고려하여 최적의 주소를 응답받을 수 있어야 합니다. 그래서 Local DNS는 적절한 네임 서버에 질의하고, 네임 서버는 여러 개의 IP 주소 레코드를 저장하였다가 자체적으로 적용된 정책에 따라 적합한 레코드를 응답으로 제공하기도 합니다. 따라서 설정된 Local DNS와 네임 서버 존 및 레코드, 도메인의 규모에 따라 응답받는 주소가 다를 수 있습니다. + +## DNS Zone과 레코드 + + + +이미지는 example.com라는 도메인에 대한 DNS Zone 및 내부 레코드를 예시로 표현한 것입니다. 이처럼 특정 도메인과 용도별 서브도메인에 대한 mapping 정보를 테이블 형태로 저장한 것을 DNS Zone 이라고 합니다. 그리고 DNS Zone 내 각각의 mapping 데이터 row를 레코드 라고 부릅니다. + +- A : 도메인에 대한 IPv4 주소 + +- AAAA : 도메인에 대한 IPv6 주소 + +- MX : 도메인이 메일을 수신할 수 있는 서버를 식별하기 위한 주소 혹은 도메인 + +- NS : 도메인의 네임 서버 주소 혹은 도메인 + +- CNAME : 도메인의 별칭, 해당 도메인이 가질 수 있는 또 다른 도메인이라고 볼 수 있다. + +- 이 외 외부에서 참조하기 위한 텍스트를 작성하는 TXT, IP 주소를 역으로 조회할 때 도메인 이름을 제공하기 위한 PTR, 도메인에서 이메일을 보낼 수 있는 주소를 작성하는 SPF 레코드가 있다. + +DNS Zone은 IETF 표준을 준수하기 위해 위의 레코드 외 SOA라는 정보를 필수로 갖고 있습니다. SOA는 Start Of Authority의 약자로 권한의 시작을 의미하며 도메인 관리자 주소, 기본 네임 서버, 요청 재시도 시간, DNS 캐싱 시간(TTL) 등 중요한 정보를 저장하고 있습니다. 아래와 같이 nslookup 명령어 조합을 통해 쉽게 확인해볼 수 있습니다. + + + +### DNS 운영 시 주의 사항 + +SOA에 포함되는 정보인 TTL을 길게 설정하면 변경된 레코드를 클라이언트가 DNS에서 전달받기까지 그만큼 오랜 시간이 걸릴 수 있습니다. 반면 TTL을 짧게 설정할 경우 DNS를 통해 Recursive Query를 수행하는 빈도가 잦아 트래픽이 과도하게 증가할 수 있습니다. + +그리고 다수의 좀비 PC를 동원하여 DNS 서버에 동일한 혹은 몇 가지 도메인 이름 해석을 대량으로 시도하는 DNS Query Flooding 공격 사례 또한 나타나고 있습니다. 이러한 공격을 효과적으로 방어하기 위해서는 기준 시간 내 임계치 이상의 도메인 질의 요청을 일정 시간 동안 차단하도록 설정할 수 있고, 존재하지 않는 도메인이나 서브 도메인만 랜덤하게 변경하여 질대량으로 질의하는 IP를 차단하는 방법이 있습니다. diff --git a/_posts/2024-12-08-3_tier.md b/_posts/2024-12-08-3_tier.md new file mode 100644 index 000000000000..3162853f0ff7 --- /dev/null +++ b/_posts/2024-12-08-3_tier.md @@ -0,0 +1,106 @@ +--- +layout: single +title: "초기 2-tier에서 3-tier Architecture로의 진화 여정" +categories: Network +tag: [Network, 네트워크, Infra] +author_profile: false +sidebar: + nav: "docs" +--- + +**[공지사항]** +이전부터 학습하고 연구한 내용들을 함께 나누기 위해 최근 기술블로그를 개설하고 지속적으로 업로드하고 있습니다. 많은 관심과 피드백 부탁드립니다! 감사합니다 :) +{: .notice--success} + +초기 프로젝트를 진행할 때는 불필요한 초기 비용과 오버 엔지니어링을 방지하기 위해 서비스 규모와 SLO(Service Level Objective)를 적절하게 예측해야 합니다. 이미 대중에 널리 알려진 브랜드 IP를 가진 기업이 신규 소프트웨어 프로젝트를 진행할 경우 상대적으로 높은 사양의 인프라를 초기 구축해야 하지만, 그렇지 않을 경우 단순한 구조로 시작하여 점진적으로 Architecture를 업그레이드할 수 있습니다. + +본 포스팅에서는 제가 현업에서 쇼핑몰 솔루션 인프라를 운영하면서 사용자 유입량이 증가하면서 초기 2-tier Architecture에서 3-tier Architecture까지 업그레이드한 경험에 대해서 소개해 드리려고 합니다. + +## 초기 2-tier 구성 + + + +**웹 서버 - DB 서버**로 구성된 2-tier 아키텍쳐입니다. WEB 대역과 DB 대역이 별도의 LAN으로 분리되어 있고, 라우팅 및 방화벽 정책을 UTM이라고 하는 장비에서 중앙 관리합니다. 전자상거래 사업자에게 손쉽게 PG 및 개인 쇼핑몰 개설 서비스를 제공하는 솔루션 특성 상 웹 서버는 많은 수의 서브 도메인 쇼핑몰을 호스팅할 수 있어야 하고 상품 이미지, 고객 정보, 주문 정보 등 데이터를 저장할 수 있어야 합니다. 주로 동시 접속자 수 50명 정도의 개인 사업자 혹은 중소 규모의 쇼핑몰들을 호스팅하기 때문에 1 ~ 2TB 용량의 데이터베이스 한 대에 모든 데이터를 저장하되, 쇼핑몰 아이디인 서브 도메인과 고객명, 상품명 등 컬럼을 키로 지정하고 인덱스 또한 적절하게 설정해 주었습니다. + +또한 그림에는 나오지 않았지만 이미지와 DB 데이터를 백업하기 위한 백업 서버 또한 배치했습니다. 모니터링 또한 빼놓을 수 없는 부분이기 때문에 모든 서버에 **glance**를 설치하고 성능 데이터를 **influxdb**라는 시계열 데이터베이스에 전송하여 그래프로 출력하는 **grafana** Dashboard를 구성했습니다. + +## Load Balancer 및 Redis 도입 + + + +더 많은 사이트와 사용자를 수용하기 위해서는 단순히 서버 대수를 늘리는 것이 아닌 한층 더 진화한 구조의 Architecture가 필요했습니다. 따라서 많은 쇼핑몰의 트래픽을 여러 회선과 서버에서 처리할 수 있도록 L4 Load Balancer를 도입한 새로운 인프라를 증설하였습니다. 이를 통해 하나의 공인 VIP로 유입되는 사용자 트래픽을 3대의 서버에 적절하게 분산하고, 설령 하나의 서버가 다운되더라도 자연스럽게 다른 2대의 서버에 forwarding할 수 있는 구조로 고가용성을 보장했습니다. 또한 자주 불러오는 데이터를 cache하기 위해 Redis를 도입하고 이미지 데이터를 저장하기 위해 NFS 스토리지를 같은 대역에 설치하였습니다. + +그 결과 당해 총 5000개의 신규 가입 쇼핑몰들을 새 인프라에 호스팅하고 LoadAvg 1 이하의 안정적인 성능으로 서비스를 제공했습니다. + +## 노드 Scale up 및 IDC 최적화 + +초기 호스팅한 쇼핑몰들의 규모가 조금씩 성장하면서 서버 당 평균 동시 접속자 수가 300, 분당 요청 수 또한 2000 이상까지 늘어났습니다. 공교롭게도 같은 시기에 기존의 CentOS7가 EOS를 앞두고 같은 레드햇 계열의 Rocky Linux가 출시되었는데, 저희 팀에서는 새로운 OS를 설치하고 memory 용량과 CPU core 수를 scale up한 노드에 쇼핑몰 도메인을 이전하는 방안으로 의견을 모았습니다. + +기존에는 다수의 12 CPU Core와 Memory 64G의 서버가 주를 이뤘다면, 더 많은 쇼핑몰 도메인을 호스팅하고 최대 부하를 문제 없이 소화하기 위해 24 CPU Core와 Memory 128G의 서버들을 추가 도입하고 32 CPU Core와 Memory 256G의 서버를 신규로 도입했습니다. 하드웨어 사양이 높아졌기 때문에 운영 서버 대수가 줄었고 IDC 내 여유 공간을 30%가량 확보할 수 있었습니다. + +## 트래픽 최적화에 대한 고민 + +앞선 방법을 통해 시스템 성능을 최적화 하였지만 Grafana Dashboard는 여전히 높은 수치의 네트워크 트래픽을 보여주고 있었습니다. 실제로 웹 서버는 외부 http(s) 트래픽과 DB mysql 트래픽을 하나의 회선에서 처리하고 있었기 때문에 최대 500mbps까지 웃돌기도 했어요. 많은 패킷을 단일 회선에서 처리하는 만큼 Load와 별개로 병목 현상을 초래할 수도 있다고 판단했고, 웹 트래픽과 DB 트래픽을 분리하는 방안을 고민하게 되었습니다. + + + +처음 생각했던 방안은 그림과 같습니다. 웹 서버에 두개의 이더넷에 회선을 연결하여 하나는 외부 http(s) 트래픽을 처리하고 다른 하나는 데이터베이스에 연동하는 방식입니다. 라우팅 테이블 또한 별도로 설정하고요. 그림대로 물리적 인프라를 구축하고 telnet을 통해 라우팅 및 mysql 접속 상태를 확인하는 것으로 연결 테스트를 성공적으로 마쳤습니다. 이제 실제 서비스 테스트와 PROD 환경 배치만 남았다고 생각했던 찰나, 또 하나 고려해야 할 관점이 생기게 됩니다. + +### Frontend를 처리하는 서버에서 데이터베이스에 직접 접근할 수 있다고? + +당시 ISMS 인증 심사를 함께 준비하고 있어 컨설팅을 받는 도중 이러한 구조가 보안상 위험할 수 있다는 사실을 알게 되었습니다. 최소한의 허용 정책만 추가한다는 방침 아래 시스템 방화벽을 UTM과 서버 firewalld를 통해 이중으로 운영하고 있었지만, 최대한 **Zero Trust**를 실현하기 위해서는 DB에 접근할 수 있는 별도의 애플리케이션 레이어가 필요하다는 권고였습니다. + +**3-tier 아키텍쳐**, 답은 하나였습니다. + +## 3-tier 아키텍쳐 구축 + + + +그림처럼 데이터베이스와 Backup 네트워크를 별도로 구성하고, 같은 스위치에 두 개의 VLAN을 구성하여 WEB/WAS tier를 분리합니다. WAS 레이어에 NFS Storage와 Redis를 함께 배치하고 상호 연동이 꼭 필요한 구간만 접근을 허용하도록 방화벽 정책을 설정해 줍니다. +그리고 쇼핑몰 이벤트 진행으로 평소보다 트래픽이 크게 증가할 때 간혹 데이터베이스에 주문 데이터를 Write하는 트래픽의 병목 현상이 발생하여 일부 주문이 누락되는 경우가 있었는데요. 이를 개선하고자 L4와 L2 사이의 회선을 광케이블로 연결하여 물리적인 속도를 향상시켜 주었습니다. + +그리고 웹 서버에서 WAS에 요청을 forwarding할 수 있도록 nginx에 'proxy_pass' 구문을 담은 location을 추가했고, **sticky session**이 적용되도록 'ip_hash' 를 설정했습니다. + +``` +location ~ \.php { + proxy_pass http://backend; +} +``` + +``` +upstream backend { + ip_hash; + + server was1:80; + server was2:80; + server was3:80; +} +``` + +웹 서버는 외부 http 요청에 대한 트래픽만, 애플리케이션은 백엔드 스레드를 처리하는데 집중해 시스템 부담을 대폭 완화할 수 있다는 3-tier 구조의 장점을 몸소 체감할 수 있었던 작업이었습니다. 이후 가장 높은 트래픽을 발생시키는 중형 쇼핑몰 하나를 3-tier 인프라에 이전하고 모니터링한 결과, 노드 당 트래픽을 60% 절감할 수 있었고 Load 또한 1 밑으로 안정적으로 유지되었습니다. + +## 더욱 향상된 Architecture를 만들기 위해서는 + +이상적인 시스템 아키텍쳐를 위해서는 안정성 뿐만 아니라 확장성과 신뢰성 또한 보장해야 합니다. 위의 과정을 거치면서 중단 없이 서비스가 동작할 수 있도록 지속적으로 자산을 scale up 하고 새로운 툴과 구조를 도입해 봤지만 여전히 개선해야 할 문제점이 많이 보입니다. On-Premise 인프라 특성상 자원을 손쉽게 scale out할 수 없어 처음부터 최대 트래픽을 고려한 설계를 해야 했고, 이중화되지 않은 데이터베이스를 포함하여 재해 복구 전략을 Backup & Restore 방식에만 의존하다 보니 상당한 시스템 Downtime을 소모할 우려 또한 있었습니다. 높은 생산성을 바탕으로 서비스를 한계없이 성장시키기 위해서는 더욱 특수한 여러가지 방식들을 고려해야 합니다. + +### 클라우드 인프라로의 이전 + +AWS, GCP, Azure 등 퍼블릭 클라우드 환경으로 이전하면 손쉬운 서버 scale out이 가능하기 때문에 처음부터 대형 자원을 선정할 필요가 없습니다. 평상시의 트래픽을 소화할 수 있는 최소한의 자원을 운용하다가 피크 타임에 동일한 자원을 프로비저닝하는 **Auto Scaling** 정책을 생성할 수 있어요. EFS, S3와 같이 무제한에 가깝게 확장할 수 있는 스토리지 서비스와 RDS, Aurora, ElastiCache 같은 데이터베이스 서비스, CloudFront 같은 CDN 서비스 또한 제공합니다. 부가적인 기능을 추가하기 위한 서버리스 Lambda나 API Gateway를 사용할 수도 있고, 특정 레이어에서 장애가 확산되지 않도록 도입할 수 있는 SQS 같은 큐 서비스 또한 제공하고요. 거의 모든 CSP에서 아키텍쳐를 설계하고 구축하는데 안되는 게 없을 정도의 많은 서비스를 제공하기 때문에 이제는 사용하지 않을 수가 없다고 생각됩니다. + +제가 담당했던 쇼핑몰 서비스에서 상품을 결제할 때 사용했던 자체 PG 솔루션 혹은 금융 관련 서비스의 경우 데이터 거버넌스나 규정 준수 문제에 부딪혀 클라우드로의 이전이 어려울 수 있습니다. 이럴 경우 프론트엔드만 클라우드로 이전하는 방식으로 퍼블릭 클라우드와 온프레미스 환경을 상호 연동하는 하이브리드 클라우드 환경을 고려해볼 수 있습니다. + +### IaC 기반 인프라 형상 관리 + +이전에는 Shell Script를 통해 서버를 빠르게 구성하였지만 멱등성을 보장하지 못한다는 점에서 한계가 있다는 생각이 들었습니다. 그래서 IaC를 고려하게 되었는데, 대표적으로 **terraform과 ansible, packer** 등이 언급됩니다. 이러한 IaC 도구를 사용하면 작성한 코드대로 서비스 인프라를 휴먼 에러 없이 프로비저닝할 수 있다는 장점이 있습니다. 여러 차례 반복 실행해도 특별한 변경 사항이 없다면 처음에 의도한 대로 인프라를 유지할 수 있고요. 이러한 코드와 state 파일은 Github 같은 형상 관리 도구에 저장하고 여러 사람에게 공유할 수 있습니다. + +### 컨테이너 + +애플리케이션과 의존성 파일을 하나의 컨테이너 이미지로 빌드하여 배포할 수 있습니다. 여러 대의 서버를 직접 일일이 구성하다 보면 오래 걸릴 뿐더러 빌드가 잘못되거나 의존성이 누락될 수있는 단점이 있는데요. 이를 컨테이너 이미지로 묶어서 보관하였다가 새로운 시스템을 프로비저닝하면 해당 이미지를 다운받아 컨테이너를 생성하여 배치하면 더욱 빠르고 정확하게 서버를 구성할 수 있습니다. 각각의 컨테이너는 서로 다른 네임스페이스로 격리되어 특정 워크로드의 장애가 다른 워크로드나 노드에 영향을 미치지 않습니다. + +대표적인 컨테이너 런타임에는 **Docker**가 있고, Docker로 생성한 이미지를 containerd나 cri-o 런타임에서 컨테이너를 실행하고 오케스트레이션할 수 있는 **쿠버네티스**를 도입해볼 수 있겠습니다. 하지만 쿠버네티스는 많은 학습량을 요구하는 데다 대규모 클러스터에 서비스를 효율적으로 분산하기 위한 기술이므로 자칫 잘못 도입하게 되면 운영 관리가 어려운 오버엔지니어링이 될 수 있습니다. 중소 규모의 프로젝트에서는 오케스트레이션 기술 없이 컨테이너 런타임만 도입해도 충분한 효과를 볼 수 있습니다. + +컨테이너 기술을 사용하게 되면 성능 Metric 수집을 노드 수준에서만 할 수 있는 glance가 아닌 **Prometheus**를 사용하여 노드 뿐만 아니라 파드와 컨테이너 단위로도 Metric을 수집하여 모니터링할 수 있도록 구성해야 한다고 생각됩니다. + +### CI/CD 도입 + +**Jenkins**, **Github Action**, **Argo CD**와 같은 CI/CD 도구를 도입하면 수동으로 진행하던 빌드, 테스트, 배포 작업을 빠르게 자동화하여 소프트웨어 개발 생산성을 높일 수 있습니다. 개발자가 형상 관리 도구에 소스를 commit하고 push하면 자동으로 Docker 이미지를 생성하고 테스트할 수 있는 코드(yml)를 작성하여 설정할 수 있고, 환경 변수를 안전하게 저장하였다가 배포 시 적용할 수 있습니다. CI/CD를 통해 개발자는 정해진 시간 안에 더 많은 커밋을 테스트할 수 있고 버그나 오류를 빠르게 수정하고 배포할 수 있습니다. diff --git a/_posts/2024-12-14-for_each_vs_count.md b/_posts/2024-12-14-for_each_vs_count.md new file mode 100644 index 000000000000..c2d20c297b24 --- /dev/null +++ b/_posts/2024-12-14-for_each_vs_count.md @@ -0,0 +1,145 @@ +--- +layout: single +title: "count와 for_each의 차이?" +categories: Terraform +tag: [Terraform] +author_profile: false +sidebar: + nav: "docs" +--- + +**[공지사항]** +이전부터 학습하고 연구한 내용들을 함께 나누기 위해 최근 기술블로그를 개설하고 지속적으로 업로드하고 있습니다. 많은 관심과 피드백 부탁드립니다! 감사합니다 :) +{: .notice--success} + +count와 for_each는 Terraform에서 동일한 리소스를 프로비저닝할 때 유용하게 쓰이는 구문입니다. 0.12 버전부터는 모듈을 개발할 때도 for_each와 같은 반복문을 활용할 수 있게 되었죠. 코드의 반복을 줄일 수 있다는 공통된 장점은 쉽게 알 수 있지만 이 두 가지는 어떤 차이점이 있을까요? 어떤 상황에 주로 쓰일 수 있을까요? 본 포스팅에서는 count와 for_each의 차이점과 적절한 사용 방법에 대해 작성해 보았습니다. + +## 정해진 순서를 번호로 인덱스를 부여하는 count + +``` +variable "AWS_REGION" { + default = "ap-northeast-2" +} + +variable "project" { + default = "projectA" +} + +variable "ec2_instances" { + type = list(map(string)) + default = [ + { suffix = "instance1"}, + { suffix = "instance2"}, + { suffix = "instance3"} + ] +} + +variable "PATH_TO_PUBLIC_KEY" { + default = "mykey.pub" +} +``` + +``` +module "ec2_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + + count = length(var.ec2_instances) + + instance_type = "t2.micro" + key_name = aws_key_pair.mykeypair.key_name + monitoring = true + vpc_security_group_ids = ["sg-0f4e8438a0abe27e5"] + subnet_id = "subnet-06d6e8c3835a71d8f" + + name = "${var.project}_${var.ec2_instances[count.index].suffix}" + + tags = { + Terraform = "true" + Environment = "dev" + } +} + +resource "aws_key_pair" "mykeypair" { + key_name = "mykeypair" + public_key = file(var.PATH_TO_PUBLIC_KEY) +} +``` + +각 인스턴스의 suffix를 ec2_instances 라는 list에 저장하고 ec2_instance 모듈을 연동하여 인스턴스를 생성해 보겠습니다. count는 **지정된 수만큼 리소스를 프로비저닝할 때** 사용하는 구문으로 정수값만 취급합니다. 그래서 ec2_instances 라는 list의 element 개수를 매개변수로 전달해 총 3대의 인스턴스를 suffix를 붙여 생성합니다. + + + + + +terraform state list를 조회하면 ec2_instance 모듈을 통해 생성된 리소스가 0부터 2까지의 인덱스가 부여되었음을 알 수 있습니다. 이처럼 count는 0부터 시작하는 숫자를 인덱스로 부여하여 순서대로 생성하고 관리합니다. 그럼 여기서 두번째 인스턴스를 삭제하려고 하면 어떤 일이 발생할까요? 이를 위해 instance2 라는 suffix의 map element를 주석처리한 다음 코드를 전달받은 AWS provider가 어떤 식으로 처리하는지 확인해보겠습니다. + +``` +variable "ec2_instances" { + type = list(map(string)) + default = [ + { suffix = "instance1"}, +# { suffix = "instance2"}, + { suffix = "instance3"} + ] +} +``` + + + + + +오히려 projectA-instance3라는 이름을 가진 마지막 인스턴스를 삭제하고 두번째 인스턴스의 이름이 projectA_instance3으로 변경됩니다. 다시 state list를 살펴보면 + + + +0부터 1까지의 총 두 개의 인덱스가 부여되어 있습니다. + +이렇게 count를 사용하면 중간의 element를 지우고 apply하게 될 때 인덱스를 0부터 다시 부여하게 됩니다. 따라서 instance1 suffix는 그대로 0을 갖게 되어 기존의 첫번째 인스턴스 naming을 그대로 유지하고, instance3 suffix가 1로 이동하여 두 번째 인스턴스를 naming하는데 사용됩니다. 결과적으로 두번째 인스턴스가 아닌 마지막 인스턴스가 삭제되게 됩니다. + +## 고유한 key 값을 인덱스로 부여할 수 있는 for_each + +count를 사용하고 나니 운영상의 불편함을 어느 정도 체감할 수 있었습니다. 그렇다면 for_each를 사용하면 이러한 문제를 해결할 수 있을까요? **vars.tf를 그대로 두고 for_each 구문을 아래와 같이 ec2_instances 변수 내 element의 value값을 key로, element 자체를 value로 둔 자료 형태를 갖도록 한 상태에서** 리소스들을 프로비저닝합니다. + +``` +module "ec2_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + + for_each = { for instance in var.ec2_instances: instance.suffix => instance } + + name = "${var.project}_${each.value.suffix}" + + instance_type = "t2.micro" + key_name = aws_key_pair.mykeypair.key_name + monitoring = true + vpc_security_group_ids = ["sg-0f4e8438a0abe27e5"] + subnet_id = "subnet-06d6e8c3835a71d8f" + + tags = { + Terraform = "true" + Environment = "dev" + } +} + +resource "aws_key_pair" "mykeypair" { + key_name = "mykeypair" + public_key = file(var.PATH_TO_PUBLIC_KEY) +} +``` + + + + + +state list를 확인해보니 이번에는 고유한 key 값을 인덱스로 부여한 것을 확인할 수 있습니다. 여기서 projectA-instance2 라는 인스턴스를 다시 삭제해 보겠습니다. 동일하게 instance2 라는 suffix의 map element를 주석처리하고 terraform plan을 실행하면 다음과 같은 결과가 출력됩니다. + + + + + +이번에는 의도한 대로 instance2 라는 인덱스를 가진 두번째 인스턴스만 삭제됩니다. 이처럼 for_each는 순서 상관없는 고유한 key값을 리소스와 mapping하게 됩니다. 그래서 작업하려고 하는 리소스에 해당하는 element를 명확하게 지정할 수 있다는 장점을 확인할 수 있었습니다. + +## count vs for_each + +shared storage를 기반으로 동일한 인스턴스를 생성하거나 혹은 naming 규칙이 필요 없이 **특정 수량**의 리소스를 빠르게 scale-out할 경우 등 **stateless**한 리소스를 프로비저닝할 땐 복잡한 하드 코딩 없이 count를 사용할 수 있습니다. count는 정수값을 취급하기 때문에 적절한 리소스 수량만 입력하면 될 정도로 비교적 간단하게 쓰일 수 있는 구문입니다. + +for_each는 주로 **map** 자료형을 기반으로 리소스에 고유한 naming을 적용하고 관리할 때 사용됩니다. 그래서 SSM parameter나 iam 사용자 등 count보다 더 많은 사용 사례를 갖게 됩니다. count를 아얘 사용하지 않는 것은 아니지만 엄격한 naming 규칙 아래 **stateful**한 리소스를 운영해야 되는 사례가 많아 for_each를 더욱 선호하게 되는 것 같아요. 하지만 상대적으로 count보단 코딩이 간단하지 않아서 굳이 필요하지 않은 상황에는 사용하지 않고 비교적 간단한 count를 사용하는 것이 terraform 코드의 가독성을 높일 수 있습니다. diff --git a/_posts/2024-12-17-priority_pod_config.md b/_posts/2024-12-17-priority_pod_config.md new file mode 100644 index 000000000000..8d856f312b9e --- /dev/null +++ b/_posts/2024-12-17-priority_pod_config.md @@ -0,0 +1,114 @@ +--- +layout: single +title: "파드 내 설정값 적용 우선 순위" +categories: Kubernetes +tag: [쿠버네티스, Kubernetes] +author_profile: false +sidebar: + nav: "docs" +--- + +**[공지사항]** +이전부터 학습하고 연구한 내용들을 함께 나누기 위해 최근 기술블로그를 개설하고 지속적으로 업로드하고 있습니다. 많은 관심과 피드백 부탁드립니다! 감사합니다 :) +{: .notice--success} + +쿠버네티스에서 실행되는 애플리케이션에 설정값을 전달하는 방법은 여러가지가 있습니다. 컨테이너 이미지 자체에 설정값을 포함하여 실행시키는 방법이 있고, 매니페스트에 직접 정의하거나 ConfigMap, Secret을 사용하여 전달하는 방법이 있습니다. 하지만 때로는 테스트나 운영상의 목적으로 이미 정의된 설정값을 다른 값으로 치환해야 하거나 모종의 이유로 두 가지 이상의 방법으로 동일한 설정값을 중복 적용하는 일이 발생합니다. 이럴 경우 어떤 방법으로 적용한 설정값이 애플리케이션에 우선적으로 반영될까요? + +이번 포스팅에서는 파드에서 구동되는 컨테이너에 설정값을 적용하는 다양한 방법과 우선 순위에 대해 작성하려고 합니다. + +## 컨테이너 이미지에 설정값 정의 + +우선 busybox 이미지를 기반으로 간단한 이미지를 하나 빌드하고 하나의 파드를 replica로 실행시키는 Deployment를 배포합니다. + +```docker +FROM busybox:1.28 +ENV TIME=60 +``` + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: busybox + labels: + app: bash +spec: + replicas: 1 + selector: + matchLabels: + app: bash + template: + metadata: + labels: + app: bash + spec: + containers: + - name: busybox + image: lewisjlee/sleep:v1 + command: [ 'sh', '-c', 'while true; do echo "Sleeping for $TIME"; sleep $TIME; done' ] +``` + +60 이라는 값을 가진 TIME 환경 변수를 포함한 신규 이미지를 빌드하고 저의 Repository에 push합니다. 그리고 실행되는 파드가 이미지에 정의된 TIME 환경 변수를 가지고 60초동안 sleep을 실행하고 sleep을 실행하는 시간을 표준 출력하도록 Deployment 매니페스트를 정의합니다. 해당 Deployment를 배포하고 표준 출력을 살펴보면 이미지에 반영된 TIME 환경 변수가 정상 적용되었음을 확인할 수 있습니다. + + + +## ConfigMap을 통한 설정값 적용 + +그렇다면 위의 이미지와 매니페스트에 ConfigMap을 통해 설정값을 중복 정의하면 어떻게 될까요? 이번에는 TIME 환경 변수를 45로 설정한 ConfigMap을 생성하고 매니페스트에 추가하여 실행해 보도록 하겠습니다. + +```bash +kubectl create configmap time-configmap --from-literal=TIME=45 +``` + +```yaml +spec: + containers: + - name: busybox + image: lewisjlee/sleep:v1 + envFrom: + - configMapRef: + name: time-configmap + command: [ 'sh', '-c', 'while true; do echo "Sleeping for $TIME"; sleep $TIME; done' ] command: [ 'sh', '-c', 'while true; do echo "Sleeping for $TIME"; sleep $TIME; done' ] +``` + +컨테이너 이미지에 이미 60이라는 값을 가진 TIME 환경 변수가 설정되어 있는 상태에서 45라는 값을 가진 TIME 환경 변수를 담은 ConfigMap을 적용했습니다. 그 결과 애플리케이션은 ConfigMap에 적용된 TIME 값을 사용하는 것으로 확인됩니다. + + + +애플리케이션은 환경 변수를 읽어들이기도 하지만 대부분 특정 configuration 파일에서 설정값을 읽어들이는 경우가 많습니다. 그래서 ConfigMap은 컨테이너 내부에 환경 변수의 형태로 설정값을 전달하기도 하지만 ConfigMap 자체를 하나의 configuration 파일로 컨테이너 내부에 저장하도록 하는 매니페스트 정의도 있습니다. + +**파드로 실행되는 대부분의 애플리케이션은 최초로 읽어들인 ConfigMap 설정값을 메모리에 저장하여 사용하고, 이후에 일어난 ConfigMap의 변경은 반영하지 않습니다.** 그렇기 때문에 ConfigMap 내 변경 사항을 반영하기 위해서는 파드를 재시작하거나 컨트롤러를 Rollout 해야 합니다. + +## 매니페스트 내 env 자체 정의 + +설정값을 전달하기 위해 리소스 매니페스트 내 env 필드를 자체 정의하는 방법이 있습니다. 이번에는 ConfigMap을 사용하여 설정값을 전달한 매니페스트에 30이라는 값을 가진 TIME 변수를 env 필드로 추가하여 실행해보겠습니다. + +```yaml +spec: + containers: + - name: busybox + image: lewisjlee/sleep:v1 + env: + - name: TIME + value: "30" + envFrom: + - configMapRef: + name: time-configmap + command: [ 'sh', '-c', 'while true; do echo "Sleeping for $TIME"; sleep $TIME; done' ] +``` + +이미 컨테이너 이미지에 60이라는 값이 TIME이라는 환경 변수에 설정되어 있고 이를 ConfigMap에 정의된 45라는 값으로 치환했었습니다. 그러나 동일한 매니페스트에서 env 필드를 이용해 값을 30으로 설정하고 배포해보니 env 필드에 정의된 TIME 변수값이 사용되는 것을 확인할 수 있었습니다. + + + +## 설정값 적용 우선 순위 + +###