회사에서 DataOps를 위해 Airflow를 구성하고, 안정적인 운영 환경을 제공해야 하는 태스크가 있었다. 요즘 같은 세상에 컨테이너 혹은 Kubernetes 차트 등을 이용하여 편하게 배포할 수 있지만, DevOps로써는 보다 많은 고민이 필요했고, 본 글은 Airflow 운영 3개월차가 작성하는 다른 누군가에게 도움이 되기를 기대하며, 또 미래의 내가 찾아 볼 수 있는 하나의 기록으로써 남기는 글이다.
Managed Workflow Apache Airflow vs EKS
SaaS 형태의 Apache Airflow를 이용하는 것은 실질적으로 Apache Airflow를 모르는 상태에서 운영하게 될것으로 예상했다.
놀랍게도 이는 SaaS를 이용하는 대다수의 이유기도 하지만, 우리 회사의 경우 데이터 사업 회사기 때문에 ETL 파이프라인을 잘 모르는 상태로 배포하는 것은… (이하생략)
또한 MWAA에 대해 찾아보니, 커스터마이징이 생각보다 까다롭고 문제가 발생했을 때 추적이 어렵다는 후기들을 보고 온프레미스로 배포하기로 결정했다.
그리고 비용…😂
Airflow의 Executor를 무엇으로 선택하는게 좋을까?
Celery Executor vs Kubernetes Executor
Celery Executor의 경우 등록된 워커의 리소스를 이용하여 워크플로우를 처리한다. 이 경우 “워커 관리”라는 관리 지점이 생기고 워커의 자원에 따라 태스크 실행이 제한될 수 있다.
N 대의 워커 운용
분산된 워커 환경에 대한 관리
워커 failover
concurrency 제한
이러한 이유로 인해 Kubernetes Executor를 선택하여 DAG를 처리하는 방안을 선택했다.
DAG 실행 시 각 Task에 대한 Pod가 생성되기 때문에,, 워커의 실패가 없다.
Cluster Autorscaler (Karpenter)를 통한 능동적인 워커 자원 관리
Airflow 환경 설정 = concurrency 설정 → 워커로 부터 자유롭다.
Kubernetes Executor, 조금 더 섬세하고 자유롭게 운영하고 싶은데..
Kubernetes Executor는 Pod Template을 통해 Task를 실행할 Pod의 스펙을 정할 수 있다. 이를 통해 목적별, 작업별, 심지어 그룹별로 Pod가 실행될 템플릿을 미리 정하고, executor_config을 통해 template을 지정하여 사용할 수 있다.
이에 따라서 Spot Instance, GPU, CPU, CPU Architecture, 그리고 자원량 까지 정할 수 있다.
Pod Template에 대해 Airflow Pool을 지정한다면, 더 멋진 관리가 가능하다.
Airflow를 Kubernetes와 이용한다면 Kubernetes Pod Operator라는 것을 접하게 되는데, 나는 사용하지 않기로 했다.
Kubernetes Pod Operator의 “격리된 실행 환경”을 보장하라는 철학에 따라 Kubernetes Executor의 작업을 처리하기 위해 생성된 Task Pod와는 별개로… 또 하나의 Pod가 생성되어 처리된다. 즉.. 하나의 Task에 두 개의 Pod가 생성된다. 이는 불필요한 자원낭비로 이어질 수 있다.
보기도 힘들고.. 심지어 둘 간의 의존성이 혹시라도 끊어진다면, 작업의 유실로 이어지기 때문에.. Kubernetes Executor만 사용하기로 했다.
그렇다고 KPO 자체에 의문을 갖는것은 아니다. 일반화 할순 없지만, 분명 더욱 세분화 해야 하는 작업이 있을것이고 이에 따라 따른 세밀한 제어가 필요할 때 사용할 수 있다.
컨테이너에 대해 공부를 해야겠다는 생각, 그리고 숙제로 미루어 왔던 Control Group에 대해 공부하자는 계획을 세운 뒤 꽤나 오랜 시간이 흘렀습니다. 링크드인의 글을 눈팅하던 중 우연히 “쿠버네티스가 쉬워지는 컨테이너이야기 시리즈” 게시글을 보게 되었고, 해당 글을 바탕으로 공부를 시작했습니다.
앞으로 작성할 cgroup 관련 게시글은 해당 글을 바탕으로 컨테이너에 대한 이해도를 높이고자 작성한 글 입니다.
컨테이너가 리눅스의 union filesystem, cgroup, namespace를 이용해 만들어지는 것을 알고 있는 사람
cgroup이 단순히 자원을 격리한다는 것만 알고 있는 사람
본 글의 마지막 부분에는 천강진 님의 이번 글의 대상이 아닌 독자에 대한 개인적인 견해를 작성해 놓았습니다.
컨테이너
컨테이너는 리눅스의 union fs (overlayfs), cgroup, namespace를 이용하여 host os의 메인 워크로드와 격리된 워크로드를 제공하는 가상화 솔루션입니다.
union filesystem을 이용하여 애플리케이션을 실행하기 위한 운영체제 환경부터 시작하여 서비스 바이너리까지 레이어로 나뉘어진 하나의 통합된 파일 시스템을 제공하여 언제 어디서든 동일한 환경을 제공하고 namespace를 이용하여 애플리케이션 프로세스를 격리하여 독립된 환경을 제공할 수 있다면,
control group은 이렇게 격리된 환경, 그리고 언제나 동일한 애플리케이션을 실행하기 위한 리눅스 프로세스 그룹에 시스템 자원을 격리, 할당하여 제어할 수 있도록 해 줍니다.
정리하자면,
Union Filesystem을 통해 low layer, upper layer, merged view를 통해 레이어화 된 애플리케이션 실행에 필요한 파일시스템을 제공하고
Namespace를 통해 애플리케이션의 실행 환경을 분리하며
Control Group을 통해 애플리케이션 실행에 필요한 하드웨어 자원을 격리, 제한하여 독립된 환경에서 안정적으로 실행될 수 있도록 하는 가상화 기술입니다.
“천강진”님의 시리즈를 읽으면서 Control Group을 이해한다면 컨테이너 환경에서의 안정적으로 하드웨어 자원을 제공할 수 있을 뿐 아니라, 애플리케이션에 어떤 현상 혹은 문제가 발생했을 때 조금 더 넓은 각도에서 원인을 분석하고 문제를 해결할 수 있을 것이라 느꼈습니다.
Control Group은 /sys/fs/cgroup 아래에 논리 제어 그룹을 확인할 수 있으며 루트 제어 그룹 아래에 자식 제어 그룹이 존재하는 형태입니다. 새로운 논리 그룹을 생성하려면 대상의 루트, 혹은 자식 제어 그룹에 폴더를 생성하면 됩니다.
출처: 쿠버네티스가 쉬워지는 컨테이너 이야기 — cgroup, cpu편, 천강진님
A라는 논리 그룹을 새롭게 만들 경우, 리눅스 시스템에 의해 해당 디렉토리 아래에 자동으로 다음과 같은 구조가 추가됩니다.
단순히 논리 그룹을 만듦으로써 프로세스 격리된 제어 그룹 공간을 활용하는 것은 아닙니다. 생성된 제어 그룹에 실행중인 프로세스의 ID를 할당함으로써 실질적인 제어 그룹을 이용하게 됩니다.
물론 아직 제어 그룹에 대해 자원에 대한 제한을 설정하지 않았기 때문에 아직은 루트 제어 그룹을 그대로 따르는 상태입니다.
Docker의 Control Group과 CPU
잠깐! macOS의 경우 /sys/fs/cgroup을 찾을 수 없습니다. 아래의 명령어를 통해 host의 /sys/fs/cgroup을 확인할 수 있는 컨테이너를 실행해 주세요.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b1a5dc151e15 justincormack/nsenter1 "/usr/bin/nsenter1" 3 minutes ago Up 3 minutes cgroup-host
e1277fe09be5 docker:27.3.1-dind-alpine3.20 "dockerd-entrypoint.…" 7 minutes ago Up 7 minutes 2375-2376/tcp cgroup
5d16df9b31de mysql:8.0 "docker-entrypoint.s…" 4 days ago Up 7 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql
ba834863df92 redis:7.4 "docker-entrypoint.s…" 4 days ago Up 7 minutes 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp redis
우리가 생성한 예제의 경우 e1277fe09be5로 실행되고 있는 것을 확인할 수 있으며, 현재 아무 자원 제한을 두지 않았기 때문에 cpu의 max, 가중치를 Host OS에서 확인해 봤을 때 모두 기본 값임을 확인할 수 있습니다.
control group에서, 다시 말해 컨테이너에서 cpu 자원을 제한한다는 의미는 CPU 스케줄링에 있어서 얼만큼 자원을 선점할 수 있느냐를 의미하는데, 다시 말해 --cpus 0.5를 설정하는 행위는 100ms 동안 50ms를 선점할 수 있다는 것을 의미하게 됩니다.
해당 부분이 이해되지 않으신다면, 운영 체제의 CPU 스케줄링에 대해 찾아보시면 좋을것 같습니다.
만약 1개를 넘어가면 어떻게 될까요? 간단하게 하기 위해 cpu 개수를 2로 할당한다면, 다음과 같은 결과가 나오게 됩니다.
실제 애플리케이션이 동작하는 환경은 다수의 CPU를 가지는 환경이기 때문에, 단일 CPU의 환경이 아닌 이상 여러 CPU를 통해 각각 100%를 선점하여 200ms를 달성하게 됩니다.
그렇기 때문에 컨테이너 플랫폼 혹은 오케스트레이션 환경에서 cpu 자원을 요청하거나 제한할 때 실제 cpu의 core 수를 넘어서 요청할 수 없게 됩니다.
쿠버네티스의 cpu.weight (cpu.shares)
cpu.weight은 control group을 통해 생성하는 프로세스의 cpu 가중치를 설정할 수 있습니다. 일반적으로 도커, crio 등 컨테이너 플랫폼 환경을 실행한다면 cpu.weight은 일반적으로 100으로 설정되는 것을 확인할 수 있습니다. 이 경우 “천강진”님의 예제를 통해 cpu.weight을 쉽게 이해하실 수 있습니다.
하지만 실제 컨테이너 오케스트레이션 환경(쿠버네티스 등)이나 SaaS, PaaS로 제공되는 환경의 경우 cpu.weight 값이 100으로 설정되어 있지 않는 경우도 있는데, 이 경우 실행하는 서비스 혹은 환경에 의해 cpu.weight 값이 계산되어 할당됩니다. 이는 실제 CPU 리소스를 할당할 때 상대적인 CPU 우선순위를 설정하는 cpu.weight의 특징이자 영향을 받았기 때문입니다.
아래는 온프레미스 환경에 쿠버네티스를 설치하고 제어 그룹 디렉토리로 이동했을 때의 상태입니다. 아직 아무 파드도 설치하지 않았으며, crio v1.31과 쿠버네티스 1.31.2를 설치한 직후의 상태입니다.
쿠버네티스의 경우 cpu.weight의 값이 79인 이유는 Control Group V1에서 V2로 넘어오면서 cpu.shares를 cpu.weight로 변환하는 과정에서 스케일링이 발생했기 때문입니다.
cpu.shares는 실질적으로 가중치를 할당하여 사용한다기 보다 cpu.shares가 설정된 제어 그룹 간의 비율을 바탕으로 CPU 자원을 나눠 사용할 수 있게 됩니다. 즉 cpu.shares가 512, 1024, 2048로 설정되어 있다면, 1:2:4의 비율로 cpu 자원을 할당받아 사용할 수 있게 됩니다. cpu.shares의 최대 값은 2^18 - 2인 262142를 갖습니다.
control group v1 에서 cpu.shares는 최대 값은 이론상 2^32를 갖지만, 시스템의 안정성 등을 위해 최대 값을 2^18로 제한하고 있습니다.
실제로 Ubuntu 등 Linux 계열의 cpu.shares의 옵션을 보면 [2, 262144] 값을 가지고 있습니다.
2를 빼는 이유는 cpu.shares의 최소값인 2를 보장하기 위함입니다.
이러한 cpu.shares를 [1, 10000]의 범위를 갖는 cpu.weight로 변환하기 위해서는 다음의 식이 사용됩니다.
위 yaml을 바탕으로 pod를 배포하게 되면 cpu.weight의 값은 얼마가 될지 한번 고민해 봅시다.
우리는 전체 CPU shares인 2048에 대해 250m을 요청했고, 2개의 코어를 갖는 노드에서 CPU Weight는 79를 갖고 있습니다. 즉 250 / 2048 만큼 요청했고, 79의 가중치를 갖기 때문에 다음과 같은 식을 갖는다고 예상할 수 있습니다.
1 + 250 / 2048 * 79 ~= 9.6 (10)
이를 직접 워커 노드의 /sys/fs/cgroup을 통해 확인해봅시다.
root@worker1:~# crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
**8a6b6f7c79ece 27a71e19c95622dce4d60d4a3760707495c9875f5c5322c5bd535214799593ce 29 minutes ago Running busybox 0 c063f7c199d23 a**
9f61aea39bdf1 registry.k8s.io/coredns/coredns@sha256:f0b8c589314ed010a0c326e987a52b50801f0145ac9b75423af1b5c66dbd6d50 13 hours ago Running coredns 0 fb79be5137b43 coredns-7c65d6cfc9-k65k4
89986ea9962fa registry.k8s.io/coredns/coredns@sha256:f0b8c589314ed010a0c326e987a52b50801f0145ac9b75423af1b5c66dbd6d50 13 hours ago Running coredns 0 220bc9ce4e46f coredns-7c65d6cfc9-hrx2k
f34df68b00d35 registry.k8s.io/kube-proxy@sha256:22535649599e9f22b1b857afcbd9a8b36be238b2b3ea68e47f60bedcea48cd3b 13 hours ago Running kube-proxy 0 90179e9aaf2f6 kube-proxy-mb9f7
root@worker1:~# crictl inspect 8a6b6f7 | grep "pid"
"pid": 16120,
root@worker1:~# find /sys/fs/cgroup -name cgroup.procs -exec grep -H 16120 {} \\;
/sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod10e59c76_03e0_447b_bbc0_d66f7ffe0583.slice/crio-8a6b6f7c79ece337d02b1ebdf1fe7984d18c2bb829aa2c75cfed19e18e166fbc.scope/container/cgroup.procs:16120
root@worker1:~# cd /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod10e59c76_03e0_447b_bbc0_d66f7ffe0583.slice/crio-8a6b6f7c79ece337d02b1ebdf1fe7984d18c2bb829aa2c75cfed19e18e166fbc.scope
root@worker1:/sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod10e59c76_03e0_447b_bbc0_d66f7ffe0583.slice/crio-8a6b6f7c79ece337d02b1ebdf1fe7984d18c2bb829aa2c75cfed19e18e166fbc.scope# cat cpu.weight
10
우리가 계산한 9.6 + 1 = 10.6에 대한 10의 값이 정상적으로 출력되는 것을 확인할 수 있습니다.
혹시 모르니 resources.requests.cpu를 500m으로 설정하고 다시 확인해 봅시다.
root@master1:~/k8s/pods# cat a.yaml
apiVersion: v1
kind: Pod
metadata:
name: a
namespace: default
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: busybox
resources:
requests:
cpu: 500m
restartPolicy: Always
nodeSelector:
kubernetes.io/hostname: worker1
root@master1:~/k8s/pods# kubectl apply -f a.yaml
pod/a created
---
root@worker1:~# crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
**1618ac0f6157d 27a71e19c95622dce4d60d4a3760707495c9875f5c5322c5bd535214799593ce 18 seconds ago Running busybox 0 cfbb9f68873cf a**
9f61aea39bdf1 registry.k8s.io/coredns/coredns@sha256:f0b8c589314ed010a0c326e987a52b50801f0145ac9b75423af1b5c66dbd6d50 13 hours ago Running coredns 0 fb79be5137b43 coredns-7c65d6cfc9-k65k4
89986ea9962fa registry.k8s.io/coredns/coredns@sha256:f0b8c589314ed010a0c326e987a52b50801f0145ac9b75423af1b5c66dbd6d50 13 hours ago Running coredns 0 220bc9ce4e46f coredns-7c65d6cfc9-hrx2k
f34df68b00d35 registry.k8s.io/kube-proxy@sha256:22535649599e9f22b1b857afcbd9a8b36be238b2b3ea68e47f60bedcea48cd3b 13 hours ago Running kube-proxy 0 90179e9aaf2f6 kube-proxy-mb9f7
root@worker1:~# crictl inspect 1618ac0f | grep "pid"
"pid": 16906,
"type": "pid"
"pids": {
root@worker1:~# find /sys/fs/cgroup -name cgroup.procs -exec grep -H 16906 {} \\;
/sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod089ebf2e_1909_430e_aad6_14f1ff19455b.slice/crio-1618ac0f6157df7b07558d8507856af3a5707cb941ef923786bd8d09ba259b4b.scope/container/cgroup.procs:16906
root@worker1:~# cd /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod089ebf2e_1909_430e_aad6_14f1ff19455b.slice/crio-1618ac0f6157df7b07558d8507856af3a5707cb941ef923786bd8d09ba259b4b.scope
root@worker1:/sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod089ebf2e_1909_430e_aad6_14f1ff19455b.slice/crio-1618ac0f6157df7b07558d8507856af3a5707cb941ef923786bd8d09ba259b4b.scope# cat cpu.weight
20
예상값: 1 + 500 / 2048 * 79 = 20.28 ( 20 )
실제값: 20
450m → 18 (17.35 + 1)
이번 글의 대상이 아닌 독자
k8s는 왜 CPU를 압축 가능한 자원으로 보는가?
CPU Core에 대한 실행시간, 즉 스케줄링에 대해 가중치를 바탕으로 할당하기 때문에 탄력적으로 조절 가능하기 때문.
쿠버네티스에서 CPU requests/limits는 어떤 의미인가?
CPU requests는 cpu weight을 기반으로 “상대적으로” 컨테이너가 보장받을 수 있는최소 CPU 자원을 설정한다.
예를 들어, 전체 가중치가 100일 때 두 애플리케이션에 각각 50의 가중치가 설정되면, 각 애플리케이션은 최소 50%의 CPU 시간을 보장받는다.
하나의 애플리케이션만 존재할 경우, 우리가 알고 있는 스케줄링 정책과 같이 자원을 더 많이 사용할 수 있다.
만약 50의 가중치를 가진 애플리케이션이 세 개 존재한다면, 피크 타임에 모두 50%의 CPU를 요청하여 병목 현상이 발생할 수 있다.
하지만 쿠버네티스는 스케줄링 정책에서 세 번째 Pod를 Pending 상태로 유지한다.
CPU limits는 컨테이너가 사용할 수 있는 최대 CPU 시간을 의미한다. CPU 사용량은 이 limits을 초과할 수 없다.
그렇기 때문에 쿠버네티스 환경에서는 CPU requests만 설정해도 충분한 경우가 많다.
이는 전체 가중치 중 각 애플리케이션의 requests 비율에 따라 CPU 시간이 할당되기 때문이다.
따라서 limits을 불필요하게 설정하여 애플리케이션에 인위적인 병목 현상을 만들 필요는 없다.
어차피 각 비율에 따라 가질수 있는 최대 CPU 타임은 정해져있다.
다만 쿠버네티스의 QoS 정책에 따라 Guaranteed로 사용하기 위해 limits을 설정하는게 좋다.
컨테이너의 CPU 사용률은 어떻게 측정되는가? ( by 천강진 님 )
/sys/fs/cgroup/C # cat cpu.stat
usage_usec 0 # CPU 총 사용 시간(마이크로초 단위)
user_usec 0 # 사용자 모드에서 실행된 시간
system_usec 0 # 커널 모드에서 실행된 시간
### 아래 값은 cpu.max에 MAX(QUOTA)가 설정된 경우 증가
nr_periods 0 # 스케줄러에 의해 CPU가 할당된 횟수
nr_throttled 0 # 사용 가능한 CPU 시간을 초과 또는 남은 시간이 부족하여 쓰로틀링에 걸린 횟수
throttled_usec 0 # 쓰로틀링 걸린 시간
### 아래 값은 cpu.max.burst가 설정된 경우 증가
nr_bursts 0 # CPU 버스트 모드로 전환된 횟수
burst_usec 0 # 버스트 모드에서 사용한 시간
클라우드는 보안이 매우 중요한 환경인데요, 오늘은 Google Cloud Platform에서 관리중인 여러 프로젝트간 서비스 접근을 어떻게 구성하는지 공유하려 해요.
클라우드는 서비스로 제공하는 XaaS에 대한 접근을 제어할 수 있도록 IAM을 제공하는데 AWS, GCP와 같은 거대 클라우드는 IAM Role for Service Account(IRSA)라 불리는 메커니즘을 통해 더 세밀한 제어를 가능하게 해요.
이름에서 유추할 수 있듯이 서비스 계정을 위한 IAM 역할을 부여함으로써 그 메커니즘이 동작해요.
GCP의 서비스에 등록된 Service Account는 구글의 메타데이터 서버를 통해 자격 증명에 접근하고, 필요한 토큰을 관리해요.
이전에 작성한 Github과 같이 GCP 외부에서 접근하는 경우에는 Security Token Services를 통해 단기 자격을 증명해주는 토큰을 발급받아야 하고, 이 때 roles/iam.serviceAccountTokenCreator과 같은 역할이 추가로 필요해요.
GCP는 구조를 계정 수준 리소스부터 서비스 수준 리소스로 나누어 관리할 수 있는데, 베스트 프랙티스로 GCP 내부에 생성할 그 구조를 회사의 조직 구조와 일치하게 만드는 것을 권장해요.
Bucket B에서 행할 작업에 따른 Storage 접근 권한, roles/storage.objectViewer
💡 roles/iam.serviceAccountUser의 경우, 특정 사용자 혹은 서비스 계정이
bucket-sa와 같이 IAM 역할을 갖는 서비스 계정으로 가장(impersonate)해야 할 때 필요해요.
이 경우에는 Application에 부착된 서비스 계정이 직접적으로 사용하기 때문에 필요하지 않아요.
여기까지 수행하고 나면 RBAC이 구성되고, Application A에서 Bucket B에 정상적으로 접근할 수 있어요.
그런데 만약 Project B에 Bucket B 말고 여러 버킷이 존재한다며면, bucket-sa 서비스 계정을 이용해서 모든 버킷에 접근 가능하게 돼요. 이는 보안상 매우 취약한 설정이며, 따라서 Attribute Based Access Control(ABAC)을 구성해야 해요.
ABAC은 이름에서 유추할 수 있듯 속성 기반의 접근 제어 메커니즘이에요. 기존의 bucket-sa 서비스 계정은 역할을 기반으로 Bucket B에 접근 가능하도록 구성했다면, 여기에 속성을 추가하여 더 세밀한 제어를 할 수 있어요.
우리에게 필요한 세밀한 제어는 다음과 같아요.
Bucket B에 접근할 수 있어야 한다.
Bucket B 내부의 오브젝트를 볼 수 있어야 한다.
Bucket B 내부의 디렉토리 구조를 볼 수 있어야 한다.
이를 해결하기 위해 GCP는 IAM 역할에 조건을 설정할 수 있고, 역할의 조건에 필요한 리소스 유형과 리소스 이름은 다음 링크에서 확인할 수 있어요.
# 허용할 서비스 계정
member = "bucket-sa@project-a.iam.gserviceaccount.com"
# 허용할 역할
roles = [
"roles/storage.objectViewer",
]
# 역할 + 대상 = 서비스 계정이 접근할 수 있는 대상
target_resources = [
# 버킷의 이름이 bucket-b와 같아야 한다.
{
type = "storage.googleapis.com/Bucket",
name = "projects/_/buckets/bucket-b"
option = "equal"
},
# 접근하고자 하는 오브젝트는 bucket-b의 objects여야 한다.
{
type = "storage.googleapis.com/Object",
name = "projects/_/buckets/bucket-b/objects/"
option = "startsWith"
},
# 접근하고자 하는 폴더는 bucket-b의 관리형 폴더여야 한다.
{
type = "storage.googleapis.com/ManagedFolder",
name = "projects/_/buckets/bucket-b/managedFolders/"
option = "startsWith"
}
]
버킷 뿐만 아니라 다른 XaaS에 대한 접근이 필요할 때도 위와 같이 서비스 계정을 구성한다면 프로젝트간 IRSA를 구성할 수 있어요.
오늘은 초기 스타트업인 회사에서 CI/CD 파이프라인을 도입하고 이에 대한 정책 결정에 대한 고민과 솔루션을 공유하려 해요.
우선 제가 입사하기 전까지 회사는 데브옵스 엔지니어가 없었기에 CI/CD 파이프라인 마찬가지로 없었고, 클라우드 내에 딱히 정해진 정책 없이 애플리케이션을 운영중하고 있었어요. 그렇기 때문에 현재 운영중인 애플리케이션의 버전을 알 수 없었고, 언제 어떻게 배포되었는지 추적하기 어려운 환경이었어요.
이러한 환경에서 제가 CI/CD 파이프라인을 도입하기 위해 회사의 현재 상황을 분석하고 마련한 예비 요구사항은 다음과 같아요.
Continuous Integration, Continuous Delivery, Continuous Deploy 각 단계에서 현재 상태와 결과를 알 수 있어야 한다.
현재 운영중인 애플리케이션의 버전과 상태를 알 수 있어야 한다.
자동화된 시스템을 통해 개발자는 CI/CD를 수행할 수 있어야 한다.
기존에는 CI ~ Deploy까지 한 번에 무조건 실행되어서 애플리케이션의 테스트 없이 곧바로 Production에 올라가는 문제가 있었어요. 이를 해결하기 위해 “적절한 자동화”를 통해 각자가 담당한 애플리케이션을 관리, 배포할 수 있게 하고자 했어요.
DevOps Engineer를 제외한 개발자는 CI/CD 각 단계가 어떻게 진행되는지 몰라도 된다.
DevOps Engineer가 생긴 만큼 개발자는 각자의 영역에 충실할 수 있게끔 하고자 했어요.
Git Ops와 융합되어야 한다.
회사에서 GitHub Enterprise를 이용중이었고, 따라서 GitOps와 융합하고자 했어요.
1, 2번을 묶어서 기존 시스템의 가장 큰 문제점인 현재 운영중인 애플리케이션의 버전을 알 수 없는 점과 각 단계에서의 결과와 상태를 앎으로써 배포 후보군이 되는 애플리케이션이 어떤 내용을 갖는지 추적할 수 있게 하고자 했어요.
GCP Artifact Registry에 대한 Git Actions 접근 허가하기
GCP의 경우 워크로드 아이덴티티 제휴(Workload Identity Federation)를 통해 외부 서비스와의 융합을 지원하는데요, Git Actions에서 GCP에 접근 또한 이를 활용하여 해결할 수 있어요.