<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>junh-kang.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 05 Jan 2024 05:23:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>junh-kang.log</title>
            <url>https://velog.velcdn.com/images/junh-kang/profile/068c0b08-b666-4252-802d-29a618c1b588/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. junh-kang.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/junh-kang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[놀라운 속도의 ASG Karpenter 적용기]]></title>
            <link>https://velog.io/@junh-kang/%EB%86%80%EB%9D%BC%EC%9A%B4-%EC%86%8D%EB%8F%84%EC%9D%98-AC-Karpenter-%EC%A0%81%EC%9A%A9%EA%B8%B0</link>
            <guid>https://velog.io/@junh-kang/%EB%86%80%EB%9D%BC%EC%9A%B4-%EC%86%8D%EB%8F%84%EC%9D%98-AC-Karpenter-%EC%A0%81%EC%9A%A9%EA%B8%B0</guid>
            <pubDate>Fri, 05 Jan 2024 05:23:34 GMT</pubDate>
            <description><![CDATA[<p>EKS를 사용할때 가장 먼저 작업하는것이 이 노드 그룹을 만드는 것이다</p>
<p>그러나 매번 새로운 서비스를 런칭할 때 마다 노드 그룹을 새로 만들거나</p>
<p>서비스의 리소스를 고려하지 못한 채 서버가 만들어져 문제가 생기면 서버를 수동으로 확장해야 한다</p>
<p>어느정도 AWS내에 있는 ASG는 이를 완벽히 대체할 수 없었고</p>
<p>찾아보던 중 amazon에서 개발한 karpenter가 있어 이를 적용한 방식과 성능을 이야기 하고자 한다</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/d4d561ef-0f59-436f-b0f9-dc1adb4b1f5d/image.png" alt="">
karpenter의 기본 동작 원리
<a href="https://aws.amazon.com/ko/blogs/aws/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/">https://aws.amazon.com/ko/blogs/aws/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/</a></p>
<p>우선 Karpenter의 기본 동작 원리는 매우 간단하다</p>
<ol>
<li><p>pod가 생성되었을 때 조건에 맞는 노드(서버)가 없어 pending 상태에 놓이면 karpenter가 감지해 원하는 조건을 확인해 서버를 늘려준다</p>
</li>
<li><p>pod가 제거 될 때 리소스를 측정해 노드(서버)에 남아있는 리소스를 체크한 후 노드 타입 혹은 개수를 줄이고 pod를 재배치 해준다</p>
</li>
</ol>
<p>다른 ASG는 한개의 노드에 pod를 몰아넣고 노드(서버)의 리소스가 부족하면 다른 노드가 생성되어 특정 노드는 리소스가 남을 수 있지만
karpenter는 pod의 리소스를 확인해 여러 노드에 서비스가 골고루 분포함과 동시에 한개의 pod만 내려갔다 올라갔다 해도 분주하게 리소스 손실 없이 바꾸려 한다</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/1935664c-4581-41a8-bd51-f05dc88ee6f9/image.png" alt="">
<a href="https://github.com/aws/karpenter-provider-aws/tree/main/charts/karpenter">https://github.com/aws/karpenter-provider-aws/tree/main/charts/karpenter</a>
설정 또한 간단하다</p>
<p>공식 github에 helm chart를 제공해주고 있으며</p>
<p>branch별 버전을 따 이를 설치해주기만 하면 된다</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/6202ece3-cc67-4b3e-8c04-9d8e8cbed2b1/image.png" alt="">
argo로 설치한 모습</p>
<p>설치 하면 karpenter의 crd를 볼 수 있고 이를 통해 설정하기만 하면 된다</p>
<p>이제부턴 커스텀의 영역이다</p>
<pre><code>apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: karpenter-node-pool
  annotations:
    kubernetes.io/description: &quot;Node pool service&quot;
spec:
  template:
    metadata:
      labels:
        scope: karpenter-service
    spec:
      nodeClassRef:
        name: default
      requirements:
        - key: &quot;karpenter.k8s.aws/instance-category&quot;
          operator: In
          values: [&quot;t&quot;, &quot;m&quot;]
        - key: &quot;karpenter.k8s.aws/instance-cpu&quot;
          operator: Lt
          values: [&quot;8&quot;]
        - key: &quot;karpenter.k8s.aws/instance-generation&quot;
          operator: In
          values: [&quot;4&quot;, &quot;6&quot;]
        - key: &quot;topology.kubernetes.io/zone&quot;
          operator: In
          values: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;]
        - key: &quot;kubernetes.io/arch&quot;
          operator: In
          values: [&quot;arm64&quot;, &quot;amd64&quot;]
        - key: &quot;karpenter.sh/capacity-type&quot;
          operator: In
          values: [&quot;on-demand&quot;, &quot;spot&quot;]
  disruption:
    consolidationPolicy: WhenUnderutilized
  limits:
    cpu: &quot;20&quot;
    memory: 100Gi
  weight: 50</code></pre><p>우선 NodePool이라는 CRD를 설정하는데
labels를 지정해 해당 label이 설정된 node들만 동작하도록 한다
그렇지 않으면 서버를 내릴때 치명적인 서비스가 있는 노드들도 영향을 받아 운영에 문제가 생길 수 있다</p>
<p>requirements는 원하는 대로 수정하면 되는데</p>
<p>위에서부터 instance 타입은 t와 m, cpu는 8코어 미만, 타입 세대는 4 또는 6, os와 type은 2가지 모두 선택할 수 있도록 했다</p>
<p>그리고 karpenter를 굴릴 모든 노드들의 코어와 메모리는 20, 100gi를 넘지 않도록 한다 (무분별한 확장을 막기 위함)</p>
<pre><code>apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
  annotations:
    kubernetes.io/description: &quot;General purpose EC2NodeClass for running Amazon Linux 2 nodes&quot;
spec:
  amiFamily: Bottlerocket
  role: &quot;AmazonEKSNodeRole&quot;
  subnetSelectorTerms:
    - id: subnet-01aaaaaaaaaaaaaaa
    - id: subnet-02aaaaaaaaaaaaaaa
  securityGroupSelectorTerms:
    - tags:
        kubernetes.io/cluster/my-cluster: &quot;owned&quot;
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeType: gp3
        volumeSize: 2Gi
        deleteOnTermination: true
    - deviceName: /dev/xvdb
      ebs:
        volumeType: gp3
        volumeSize: 20Gi
        deleteOnTermination: true</code></pre><p>그리고 Ec2NodeClass를 지정해 생성할 노드의 ami 템플릿을 지정한다</p>
<p>bottlerocket os를 사용해 subnet과 cluster, ebs로 gp3를 설정해 주었다</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - image: nginx
          name: nginx
          ports:
            - containerPort: 8080
          resources:
            limits:
              memory: &quot;0.5Gi&quot;
              cpu: &quot;0.25&quot;

      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - arm64
                  - key: &quot;karpenter.k8s.aws/instance-category&quot;
                    operator: In
                    values: [&quot;t&quot;]
                  - key: &quot;karpenter.k8s.aws/instance-cpu&quot;
                    operator: In
                    values: [&quot;2&quot;, &quot;4&quot;]
                  - key: &quot;karpenter.k8s.aws/instance-generation&quot;
                    operator: In
                    values: [&quot;4&quot;]
                  - key: &quot;topology.kubernetes.io/zone&quot;
                    operator: In
                    values: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2b&quot;]
                  - key: &quot;karpenter.sh/capacity-type&quot;
                    operator: In
                    values: [&quot;spot&quot;]</code></pre><p>적용할 서비스에는 requirements에 설정했던 값들중 해당 pod의 조건들을 넣어 주고 배포하면 된다
타입은 t, cpu는 2,4코어, 4세대에 스팟 노드에만 해당 pod가 scheduling 하겠다는 뜻이다
메모리 조건이 없으므로 조건을 만족하는 인스턴스 타입은 t4gnano, t4gmicro, t4gsmall, t4gmedium, t4glarge, t4gxlarge의 spot 노드이다
조건을 만족하는 노드들에 scheduling 할 수 있는지 확인하고 없다면 리소스 상황을 고려해 karpenter가 생성해 준다</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/f5acd223-b09b-4aa3-b43f-c37bae162653/image.png" alt="">
적용후 argo에서 확인한 모습</p>
<p>적용 후 node pool은 pending된 pod들을 확인 후 관리하는 node를 생성한다</p>
<p>서버 생성주기도 들쭉날쭉한게 지속적으로 서버를 교체해주고 자리가 없으면 추가하고 제거하는 일을 계속 한다</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/fba4015e-3489-46bc-a839-7c0643fa95f5/image.png" alt=""></p>
<p>이를 변경하는 주기도 상당히 빠르다
1초만에 감지해 node pool을 scheduling 시키고
30초 만에 노드를 생성해 빠르게 creating 시켜준다</p>
<p>bottlerocket이 상당히 부팅속도가 빠른편이라 30초지만 그래도 정말 빠른 시간내에 해결한다</p>
<p><br><br><br><br><br><br><br><br><br><br><br>
karpenter 적용에 대해 알아 보았다</p>
<p>생각보다 간단한 설정과 좋은 성능으로 많은 곳에서 사용하지 않을까 싶다</p>
<p>이번에 Azure에서도 사용 가능하다고 하니 AKS를 사용하시는 분도 적용할 수 있을 것 같다
<a href="https://learn.microsoft.com/en-gb/azure/aks/node-autoprovision?tabs=azure-cli">https://learn.microsoft.com/en-gb/azure/aks/node-autoprovision?tabs=azure-cli</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ECS에서 EKS로 넘어간 후 비용을 40% 줄였습니다]]></title>
            <link>https://velog.io/@junh-kang/ECS%EC%97%90%EC%84%9C-EKS%EB%A1%9C-%EB%84%98%EC%96%B4%EA%B0%84-%ED%9B%84-%EB%B9%84%EC%9A%A9%EC%9D%84-40-%EC%A4%84%EC%98%80%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@junh-kang/ECS%EC%97%90%EC%84%9C-EKS%EB%A1%9C-%EB%84%98%EC%96%B4%EA%B0%84-%ED%9B%84-%EB%B9%84%EC%9A%A9%EC%9D%84-40-%EC%A4%84%EC%98%80%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Thu, 23 Mar 2023 07:42:45 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>10명도 되지않는 소규모 스타트업에서 ECS를 도입했다가 EKS(K8S)로 넘어가서 40%이상 비용을 줄인 방법을 알려드리려고 합니다.</p>
<h1 id="ecs-도입">ECS 도입</h1>
<p>먼저 Beanstalk, EKS, ECS 중 ECS를 먼저 도입하게된 이유부터 설명드리겠습니다.</p>
<p>인스턴스 환경에 따라 서비스가 영향을 받지 않게 하고 문제 발생시 빠르게 롤백 시키기 위해 Container 기반 서비스를 도입하기로 했습니다.</p>
<p>소규모 회사인 만큼 EKS 까지는 불필요하다 느꼈고 Beanstalk은 이전 회사에서 사용 시 너무 느리고 버그도 많았었기 때문에 ECS를 채택했습니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/05142479-94e4-4f7a-acf0-2429dbd49a16/image.png" alt="">
AWS ECS
<img src="https://velog.velcdn.com/images/junh-kang/post/ab8c6830-9120-45fa-8e54-b6af5aa1fc4b/image.png" alt="">
AWS beanstalk
<br><br><br>
처음 도입 시에는 굉장히 만족 했습니다.</p>
<p>task definition을 수정할 때마다 버저닝 해주고 롤백도 쉬웠고 알아서 인스턴스와 서비스 상태까지 관리해주었죠</p>
<p>하지만 1달이 지나고 바로 문제가 터집니다.</p>
<h1 id="load-balancer의-문제">Load balancer의 문제</h1>
<p>AWS ECS는 서비스간 호출을 위해 ELB를 사용해야 했습니다 </p>
<p>EC2를 사용해 올린다면 ELB가 필수였고 상태체크 또한 ELB를 통해서만 가능했습니다.</p>
<p>그러다보니 같은 인스턴스임에도 불구하고 서로 서비스를 호출하기 위해선 ELB를 호출해야했고 해당 서비스를 외부에서도 호출해야했기 때문에 해당 ELB는 internet으로 열릴 수 밖에 없었습니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/4f9ba8b8-5eab-46b9-8f15-c4d2ded274fc/image.png" alt="">
inbound는 문제가 없으나 outbound는 nat 를 거쳐 public dns를 찾기 위해 internet을 호출해야 했습니다</p>
<p>단순히 네트워크 구조가 맘에 들지 않아서가 문제가 아닙니다</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/bd1d6335-6b38-4083-8d71-6075f4789927/image.png" alt=""></p>
<p>Cloud map을 통한 이러한 구조가 가능한줄 알았고 해당 방식을 도입하려고 했습니다</p>
<p>그러나 여러 문제와 함께 위 그림은 불가능한 영역이었습니다.</p>
<h3 id="1-a-record의-dns만-가지고-load-balancing이-불가능하다">1. A record의 dns만 가지고 load balancing이 불가능하다</h3>
<p>cloud map을 ECS에 도입 시 제공해주는 서비스별 A record는 fargate에만 제공되었습니다.</p>
<p>그 이유는 외부에서 load balancer로 서비스에 접근하기 위해서는 EC2의 host network로 port별 구분을 통해 서비스를 생성할 수 밖에 없는데 그럼 제공하는 private dns는 해당 인스턴스의 ip를 한개만 저장해 접근할 수 밖에 없으므로 여러 인스턴스에 서비스가 등록 되어있는 경우 한쪽에만 접속할 수 밖에 없는 구조 였습니다.</p>
<p>즉 저 여러 인스턴스에 교차해 접근해주는 service discovery를 cloud map만 가지고는 진행하지 못 하는 구조였습니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/072cf17e-23e1-491c-936a-6e8cf1b0c450/image.png" alt="">
단순히 A record로만 호출할 때는 하나의 ip만 알 수 밖에 없으므로 한쪽으로만 접속하게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/dcdc1c96-5b4d-4ee4-a845-f170fd5de6ec/image.png" alt="">
제가 생각한 고정 dns를 balancing하는 dns는 SRV record로 제공해주는 형태였습니다</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/4900c462-f892-4952-96ff-42b395076c03/image.png" alt="">
결국 srv record를 해석해 변경하는 load balancer가 필요했고 cloudmap은 제외한채 수동으로 private load balancer dns를 만들게 되었습니다.</p>
<h3 id="2-비용">2. 비용</h3>
<p>이러다보니 외부 호출을 위한 ELB, 내부 호출을 위한 ELB까지 필요한 상황이었습니다.</p>
<p>결국 비용을 아끼기 위해 최소한의 ELB로 모든 서비스를 돌리려 했었으나 포트를 서비스별로 모두 분리하고 target group에 등록해야 했으며 인스턴스가 변경되거나 삭제 되더라도 target group을 수동 매칭 해야했고 (실제로 ECS와 연결된 것이 아니기에) 서비스를 삭제할 때 별도로 삭제해야 하는 등
사실상 모든 것을 수동 설정을 진행해야 했습니다.</p>
<p>그리고 총합 ELB만 한달에 $151가 나가고 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/de1eed72-89b2-473f-b8c4-8fd853b2e010/image.png" alt=""></p>
<h1 id="단점">단점</h1>
<h3 id="외부-서비스-사실상-사용-불가">외부 서비스 사실상 사용 불가</h3>
<p>오픈 소스 서비스(filebeat 등)를 사용하려면 내 private ECR에 image를 수동으로 만들고 등록하고 설정 맞추고 테스트를 해야 하는데 이 마저도 docker compose를 지원하지 않으므로 task definition으로 일일이 맞추어야 합니다.</p>
<p>진짜 너무 너무 불편해서 그냥 외부 서비스는 안 썼습니다.</p>
<h3 id="자잘한-운영-리소스">자잘한 운영 리소스</h3>
<ol>
<li>target group 수동 매칭</li>
<li>blue green 배포 code deploy 따로 봐야함</li>
<li>rolling update event bridge 따로 봐야함</li>
<li>auto scale group은 수동 매칭 후 따로 봐야함</li>
<li>git 배포 시 task definition을 맞추려면 파일 형태로 저장되어야 하는데 이를 sync하려면 서비스를 배포해야함</li>
<li>cloudformation에 의존적이고 실패 hang이 걸렸을 때 cloudformation을 보고 수정해야함</li>
<li>load balancer target group deregistration delay 및 health check delay로 인해 배포가 너무 길고 서비스 등록 할 때마다 delay 수정 해주어야 함</li>
</ol>
<p>결국 이 모든 것을 하기 위해 cloudformation + terraform 을 사용합니다. 이럴거면 ecs 보다 ec2에 code deploy 쓰는게 낫지 않을까 라는 생각이 들었습니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/ccaa6e6f-70b8-41d6-b2dd-60be6f39e928/image.png" alt=""></p>
<p>다른 서비스 합쳐서 AWS 핵심 서비스 비용에 $1500달러, 총 $1900 이상 나가고 있는 상황이었습니다</p>
<p>사실상 ECS만 무료이고 다른 서비스를 다 등록시켜서 비용이 배로 나가고 있었습니다</p>
<h1 id="eks-이전">EKS 이전</h1>
<p>이런저런 자잘한 이유 다 빼더라도 K8S를 도입할 이유는 충분했습니다.</p>
<p>EKS에 고정으로 나가는 비용 $72달러는 ELB 비용의 반밖에 안되는 상대적 혜자로 보였죠</p>
<p>결론 부터 말씀 드리면 $1900 -&gt; $1200 약 40%까지 비용을 절감했습니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/1e4ab5d7-6079-4105-a2ff-e6871bade2c4/image.png" alt="">
RDS도 중간에 중지해서 비용이 청구되었지만 다음달 부터는 0원입니다</p>
<br>

<h2 id="비용-절감-방법">비용 절감 방법</h2>
<p>일단 비용 절감의 핵심은 2가지 였습니다.</p>
<ol>
<li>규모에 맞지 않게 비싸게 운영중인 RDS와 Elasticloud를 on-premise로 변경</li>
<li>elb를 최소화하고 nat gateway를 최대한 타지 않도록 수정</li>
</ol>
<p>여기서 주목해봐야 할 부분은 on-premise로 변경되면서 ec2의 비용이 증가하였으나 그 증가폭이 rds에 들어가는 비용에 비해 매우 적다는 것입니다.
실제로 rds+escloud의 비용은 약 $900 였는데 instance 4대에 추가하면서 약 $350달러로 낮추었다는 점입니다. (소규모이기에 운영이나 성능에 영향이 없습니다)</p>
<p>사진상으로는 중간에 중지되어 $100이지만 ELB를 반으로 줄이면서 $150 -&gt; $80달러로 줄였습니다.</p>
<p>소규모 스타트업이기에 절대적인 비용이 작지만 규모와 관계 없이 비용이 줄여진다는 점이 핵심입니다.</p>
<h2 id="편해진점">편해진점</h2>
<h4 id="1-argo-gitops를-도입하면서-aws-서비스-전체를-관리하는-스트레스가-없어졌습니다">1. argo (gitops)를 도입하면서 aws 서비스 전체를 관리하는 스트레스가 없어졌습니다.</h4>
<p>argo 페이지만으로 rolling과 auto scale 모두 관리가 가능하고 mysql, es 와 같은 외부 서비스도 함께 확인이 가능해졌습니다.</p>
<p>특히 git action은 image만 올리도록 의존성을 분리함으로써 문제가 생긴다고 git action을 다시 돌리는 일이 없어졌습니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/4b408f13-211d-4e07-8c95-c7eabc645e55/image.png" alt=""></p>
<p>mysql + efs를 통해 안정적으로 데이터를 관리하고 사내에서 사용하는 리소스를 파악해 유동적으로 늘릴 수 있게 되었습니다.
<br><br></p>
<h4 id="2-내부-pod에-영향을-주지-않고-infra-의-테스트가-가능합니다">2. 내부 pod에 영향을 주지 않고 infra 의 테스트가 가능합니다.</h4>
<p>인프라에 문제가 발생 시 다른 서비스에 영향을 미칠까봐 테스트를 할 때 망설여집니다. 자칫 큰 장애로 이어질 수도 있습니다.</p>
<p>특히 ingress나 svc의 수정은 대부분 load balancer만 변경될 뿐 pod의 재시작이 필요 없습니다.</p>
<p>Beanstalk이나 ECS는 설정 수정을 하면 서비스를 재배포 해야했기에 망설여진 부분이 있었던 점을 생각하면 아주 큰 차이입니다.</p>
<br>

<h4 id="3-multicloud가-가능해지고-aws를-들어갈-필요가-없어졌습니다">3. Multicloud가 가능해지고 AWS를 들어갈 필요가 없어졌습니다.</h4>
<p>이젠 굳이 AWS를 쓰지 않아도 됩니다. k8s multi cluster를 도입하거나 크레딧을 많이주는 다른 cloud에서 똑같이 다시 올리면 됩니다.</p>
<p>AWS에 굳이 들어가지도 않습니다. eks는 당연히 잘 돌아갈 것이고 cloudtrail이나 비용 정도는 확인하지만 이 마저도 타사 계약을 통해 다른 웹페이지에서 보고 있습니다.</p>
<br>
많은 장점이 있지만 제일 좋은건 이젠 argo도 보지 않고 모든게 자동으로 잘 돌아간다는 점입니다.

<p>서버를 모르는 개발자분들도 쉽게 설정을 수정하실 수 있습니다. 이 설정도 git repository로 바로 sync하고 versioning 하고 있습니다.</p>
<h1 id="마치며">마치며</h1>
<p>많은 분들이 소규모 스타트업에서 K8S를 도입하는 것에 대해 굳이? 라는 말을 합니다.</p>
<p>하지만 소규모에서도 infraless 를 위한 완전 자동화에 거의 근접하였고 훨씬 적은비용으로 가능하게 했다는 점을 말씀드리고 싶습니다.</p>
<p>감사합니다.</p>
<h1 id="요약">요약</h1>
<ol>
<li>ECS는 무료이지만 다른 서비스와 연계되어 사용하면 생각보다 비싸다</li>
<li>K8S로 전환하면서 비용을 40% 줄였으며 infraless가 가능한 수준까지 설정하였다.</li>
<li>소규모 스타트업에서 사용하는 것이 비용 낭비가 아니며 눈에 보이는 비용과 휴먼비용 모두 훨씬 줄일 수 있었던 것 같다</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[LoadBalancer, K8S Client를 이용한 ACL 서비스]]></title>
            <link>https://velog.io/@junh-kang/LoadBalancer-K8S-Client%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-ACL-%EC%84%9C%EB%B9%84%EC%8A%A4</link>
            <guid>https://velog.io/@junh-kang/LoadBalancer-K8S-Client%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-ACL-%EC%84%9C%EB%B9%84%EC%8A%A4</guid>
            <pubDate>Mon, 13 Mar 2023 05:51:38 GMT</pubDate>
            <description><![CDATA[<p>외부 접속 허용을 관리하기 위해 ACL를 관리하는게 일반적입니다.</p>
<p>VPN을 사용하면 되지만 작은 스타트업에서는 일반적으로 너무너무 비싸서 사내 직원분들에게 외부에서도 쉽게 ip 등록하는 서비스를 제공해 운영했던 방법을 알려드립니다.</p>
<p>AWS EKS를 사용하고 있으며 K8S의 지식을 어느정도 알고 있다는 전제로 작성합니다.</p>
<h1 id="작동-원리">작동 원리</h1>
<p>먼저 Load balancer를 생성 합니다.</p>
<p>어떤 방식으로든 상관없습니다 ingress로 만드셔도 됩니다.</p>
<p>여기서 저는 ingress-nginx를 통해 만들었습니다. (<a href="https://github.com/kubernetes/ingress-nginx">https://github.com/kubernetes/ingress-nginx</a>)</p>
<p>(ALB 를 사용하는 경우 아래 링크를 참조)
<a href="https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/ingress/annotations/">https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/ingress/annotations/</a></p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/7290faec-a57d-4f0d-966c-980ea5d9abe5/image.png" alt=""></p>
<p>저는 AWS를 사용하고 있어 aws 에 load balancer가 만들어졌습니다.</p>
<p>그리고 K8S에서 지원하는 loadBalancerSourceRanges 옵션을 활용합니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/55aeea00-ea9b-4d65-9893-17919749273c/image.png" alt=""></p>
<p>service에서 사용이 가능합니다.</p>
<p>해당 ranges에 cidr를 추가하면 whitelist가 활성화되어 해당 ip만 원하는 pod로 접근하는게 가능합니다. (security group에 의해 제어)</p>
<p>원리는 알았으니 등록하는 서비스만 만들면 됩니다.</p>
<h1 id="ip-등록-서비스">ip 등록 서비스</h1>
<p>K8S client (<a href="https://github.com/kubernetes-client/java">https://github.com/kubernetes-client/java</a>) 로 등록 서비스를 만듭니다.
(k8s client 사용법은 따로 설명하지 않겠습니다.)</p>
<p>K8S는 기본적으로 http api 를 지원하므로 다른 방식으로도 충분히 구현할 수 있습니다.</p>
<pre><code class="language-kotlin">@RestController
@RequestMapping(&quot;/acl&quot;)
class AclController (private val aclService: AclService) {

    @GetMapping
    fun registration(httpServletRequest: HttpServletRequest): String {
        return aclService.registrationIp(httpServletRequest)
    }
}</code></pre>
<p>spring web으로 외부에서 접근할 수 있는 Controller를 만듭니다.</p>
<p>Load balancer로 들어오는 client ip를 알기위해 httpservletrequest 파라미터도 가져옵니다.
nginx가 있으므로 헤더값에서 client ip를 가져올 수 있습니다. (alb는 바로 가져올 수 있음)</p>
<pre><code class="language-kotlin">@Service
class AclService(private val coreV1Api: CoreV1Api, private val slackService: SlackService) {
    private val namespace = &quot;ingress&quot;
    private val ingressNginx = &quot;ingress-nginx-controller&quot;
    private val apiClient = coreV1Api.apiClient

    fun registrationIp(httpServletRequest: HttpServletRequest): String {
        println(&quot;registration start&quot;)
        val ingressService = coreV1Api.readNamespacedService(ingressNginx, namespace, null)
        val ip = httpServletRequest.getHeader(&quot;X-Forwarded-For&quot;) ?: httpServletRequest.remoteAddr
        val ranges = ingressService.spec!!.loadBalancerSourceRanges!!
        val cidr = &quot;$ip/32&quot;

        if (ranges.contains(cidr)) return &quot;이미 등록된 ip 입니다.&quot;

        ranges.add(cidr)

        patch(ingressService.metadata!!.name!!, V1Patch(apiClient.json.serialize(ingressService)))
        println(&quot;patch success: $cidr&quot;)
        slackService.sendMessage(&quot;$cidr 가 등록 되었습니다.&quot;)

        val executors = Executors.newScheduledThreadPool(1)
        executors.schedule({
            removeIp(cidr)
        }, 1, TimeUnit.HOURS)

        return &quot;정상 등록되었습니다.&quot;
    }

    fun removeIp(cidr: String) {
        println(&quot;remove start&quot;)

        val ingressService = coreV1Api.readNamespacedService(ingressNginx, namespace, null)
        val ranges = ingressService.spec!!.loadBalancerSourceRanges!!
        ranges.remove(cidr)

        patch(ingressService.metadata!!.name!!, V1Patch(apiClient.json.serialize(ingressService)))

        println(&quot;remove ip: $cidr&quot;)
    }

    private fun patch(name: String, patch: V1Patch) {
        PatchUtils.patch(
            V1Service::class.java,
            { coreV1Api.patchNamespacedServiceCall(name, namespace, patch, null, null, null, null, null, null) },
            V1Patch.PATCH_FORMAT_STRATEGIC_MERGE_PATCH, apiClient
        )
    }
}</code></pre>
<p>저는 등록과 함께 슬랙을 보내는 부분이 존재하지만 이부분은 상황에 맞게 조정 하시면 됩니다.</p>
<p>그리고 1시간 후 자동으로 ip가 제거되도록 executor thread를 만들어줍니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/61e543e3-1564-4e62-885b-230518a1ca43/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/589016d1-6c3f-4117-b23e-03edd8f6ac97/image.png" alt=""></p>
<p>등록이 되면 svc의 옵션에 ip가 추가됩니다. (cidr 16은 예시)</p>
<h1 id="접근-권한">접근 권한</h1>
<p>만약 ip 등록 서비스가 올라간 ns에 ingress nginx의 접근 권한이 없다면 role을 추가해 주면 됩니다.</p>
<pre><code class="language-yaml">apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: service-patch
rules:
  - apiGroups: [&quot;&quot;]
    resources: [&quot;services&quot;]
    verbs: [&quot;patch&quot;]</code></pre>
<pre><code class="language-yaml">apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: service-patch
subjects:
  - kind: ServiceAccount
    name: ingress-nginx
roleRef:
  kind: Role
  name: service-patch
  apiGroup: rbac.authorization.k8s.io</code></pre>
<p>Ingress nginx에는 sa가 존재하므로 해당 sa에 role 권한을 k8s에 올릴때 ip 등록 서비스에 같이 주시면 됩니다.</p>
<h1 id="외부-접근-보안">외부 접근 보안</h1>
<p>저는 spring security를 통해 로그인으로 접속할 수 있도록 했습니다.</p>
<p>다른 여러 방법으로 security를 추가하면 될 것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/junh-kang/post/8d0aa199-78f4-4fc3-bf6c-10d0fff6ab1d/image.png" alt=""></p>
<p>감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Bitnami mysql 운영 도중 password 에러가 발생 시]]></title>
            <link>https://velog.io/@junh-kang/Bitnami-mysql-%EC%9A%B4%EC%98%81-%EB%8F%84%EC%A4%91-password-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%B0%9C%EC%83%9D-%EC%8B%9C</link>
            <guid>https://velog.io/@junh-kang/Bitnami-mysql-%EC%9A%B4%EC%98%81-%EB%8F%84%EC%A4%91-password-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%B0%9C%EC%83%9D-%EC%8B%9C</guid>
            <pubDate>Mon, 27 Feb 2023 07:51:28 GMT</pubDate>
            <description><![CDATA[<p>K8S 혹은 가상 환경의 서버에서 mysql 을 손쉽게 운영하고 싶을 때 bitnami를 많이 사용합니다.</p>
<p>K8S 환경의 bitnami mysql 운영 도중 발생한 이슈에 대해 해결 하는 방법에 대해 서술합니다.</p>
<p>(Helm chart 기준)</p>
<h1 id="access-denied">Access denied</h1>
<pre><code class="language-text">error: &#39;Access denied for user &#39;root&#39;@&#39;localhost&#39; (using password: YES)&#39;</code></pre>
<p>운영 도중 혹은 재시작 도중 mysql 을 생성 시 해당 에러가 뜨며 서비스가 정상적으로 동직하지 않는 경우가 발생했습니다.</p>
<p>(root password 를 그 누구도 변경하지 않았을 뿐더러 k8s의 &#39;Secret&#39; 이나 &#39;Configmap&#39; 에도 해당 password 가 변경된 이력이 전혀 없었다)</p>
<p>원인은 나중에 찾고 우선 해결부터 진행했습니다.
<br><br></p>
<ol>
<li>skip grant tables 설정으로 root password 를 무시하고 서비스가 실행되도록 한다</li>
</ol>
<pre><code class="language-yaml">primary:
  configuration: |-
    [mysqld]
    skip-grant-tables
    ...</code></pre>
<p>Helm chart의 values에서 추가합니다
<br><br></p>
<ol start="2">
<li>Mysql Container의 bash shell로 attach 한다<pre><code class="language-shell">kubectl exec -it pods/bitnami/mysql bash -n mysql</code></pre>
예시로 위와 같이 mysql 이 실행되고 있는 pod 에 들어갑니다
<br><br></li>
</ol>
<pre><code class="language-shell">I have no name!@mysql:/$ mysql</code></pre>
<p>mysql client를 바로 실행한다 grant 모드여서 바로 접근이 가능합니다
<br><br></p>
<pre><code class="language-mysql">mysql&gt; FLUSH PRIVILEGES;</code></pre>
<p>grant 모드의 테이블 권한을 사용할 수 있도록 서버에 리로드 명령어를 날립니다
<br><br></p>
<pre><code class="language-mysql">mysql&gt; ALTER USER &#39;root&#39;@&#39;%&#39; IDENTIFIED BY &#39;mypassword&#39;;
or
mysql&gt; ALTER USER &#39;root&#39;@&#39;localhost&#39; IDENTIFIED BY &#39;mypassword&#39;;</code></pre>
<p>root password를 기존에 설정해두었던 비밀번호로 다시 변경합니다
<br><br></p>
<pre><code class="language-yaml">primary:
  configuration: |-
    [mysqld]
    #skip-grant-tables
    ...</code></pre>
<p>mysqld 옵션에서 &#39;skip-grant-tables&#39;을 제거하고 다시 시작 후 정상 동작을 확인합니다
<br><br><br>
혹여나 이러한 설정으로 문제 해결이 안될 시 다음과 같은 상황을 확인이 필요합니다.</p>
<ol>
<li>secret 은 변경사항을 sync 하지 않고 pod와 configmap만 sync 해본다.</li>
<li>root 계정의 접근 dns localhost 가 pod 내의 dns에 등록되어 접근 가능한지 확인 한다.</li>
<li>password를 설정하는 파일의 액세스 권한을 확인한다.</li>
</ol>
<p><br><br>
발생 원인은 정확히 찾지 못하였으나 재시작 시 root password 가 변경되는 것으로 보입니다</p>
<p>우선 db가 재시작되는게 치명적일 수 있으니 readniess와 liveness probe를 disabled해서 어느정도 막았으나 정말 문제가 생겼을 때 대처가 될지는 확인이 필요합니다</p>
<p>감사합니다.</p>
]]></description>
        </item>
    </channel>
</rss>