[doik2] Percona Operator for MongoDB

xgro·2023년 11월 11일
0

DOIK2

목록 보기
4/5
post-thumbnail

📌 Notice

Database Operator In Kubernetes study (=DOIK)
데이터베이스 오퍼레이터를 이용하여 쿠버네티스 환경에서 배포/운영하는 내용을 정리한 블로그입니다.

CloudNetaStudy 그룹에서 스터디를 진행하고 있습니다.
Gasida님께 다시한번 🙇 감사드립니다.

EKS 및 KOPS 관련 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.
[AEWS] AWS EKS 스터디
[PKOS] KOPS를 이용한 AWS에 쿠버네티스 배포



📌 Summary

  • K8s의 Operator를 통해 어플리케이션을 패키징-배포-관리할 수 있습니다.

  • 데이터베이스 오퍼레이터를 이해하기 위해 반드시 알아야하는 쿠버네티스 관련 지식을 정리합니다.

  • Nosql에 대해서 알아보며 대표적인 nosql db인 MongoDB를 쿠버네티스에 배포하는 실습을 진행 합니다.

  • Percona Operator를 통해 MongoDB를 쿠버네티스 환경에서 배포해보며 작동방식에 대해서 알 수 있습니다.



📌 Study

👉 Step 01. Nosql 소개

NoSQL 이해 : Not Only SQL

  • RDBMS 처럼 고정된 스키마 및 JOIN 이 존재하지 않음, 스키마 변경? ALERT 등 필요 없음
  • NoSQL 종류 : Key-Value Store(Redis), Wide Column Store(HBase, Cassandra), Document Store(mongoDB, CouchDB), Graph Store(Neo4j)

📚 MongoDB 소개

확장 기능 : 보조 인덱스 secondary index , 범위 쿼리 range query , 정렬 sorting , 집계 aggregation , 공간 정보 인덱스 geospatial index 등

손쉬운 사용 : 도큐먼트 지향 데이터베이스 document-oriented database. 관계형 모델을 사용하지 않은 주된 이유는 분산 확장 scale-out 을 쉽게 하기 위함

  • 행 개념 대신에 보다 유연한 모델인 도큐먼트 document 를 사용.
  • 내장 도규먼트와 배열을 허용함으로써 도큐먼트 지향 모델은 복잡한 계층 관계 hierarchical relationship 를 하나의 레코드로 표현할 수 있다.
  • 도큐먼트의 키와 값을 미리 정의 하지 않음. 따라서 고정된 스키마가 없다. 고정된 스키마가 없으므로 필요할 때마다 쉽게 필드를 추가하거나 제거할 수 있다.

확장 가능한 설계 : 몽고DB는 분산 확장을 염두에 두고 설계됨, 도큐먼트 지향 데이터 모델은 데이터를 여러 서버에 더 쉽게 분산하게 해줌.

다양한 기능 : CRUD 이외도 DBMS 의 대부분의 기능을 제공

  • 인덱싱 : 보조 인덱스를 지원하며 고유 unique, 복합 compound, 공간 정보, 전문 full-text 인덱스 기능도 제공. 중첩된 도큐먼트 nested document 보조 인덱스도 지원
  • 집계 : 집계 파이프라인 aggregation pipeline 은 데이터베이스 최적화를 활용해, 서버 측에서 간단히 데이터를 처리하여 복잡한 분석 엔진 analytics engine 을 구착하게 해줌.
  • 특수한 컬렉션 유형 : 로그와 같은 최신 데이터를 유지하고자 세션이나 고정 크기 컬렉션(제한 컬렉션 capped collection)과 같이 특정 시간에 만료해야 하는 데이터에 대한 유효 시간(TTL) 컬렉션을 지원함. 또한 기준 필터 criteria filter 와 일치하는 도큐먼트에 한정된 부분 인덱스 partial index 를 지원함으로써 효율성을 높이고 필요한 저장 공간을 줄임.
  • 파일 스토리지 : 큰 파일과 파일 메타데이터를 편리하게 저장하는 프로토콜을 지원.

고성능 : 동시성 concurrency 과 처리량을 극대화하기 위해 와이어드타이거 WiredTiger 스토리지 엔진에 기회적 락 opportunistic locking 을 사용함.

  • 따라서 캐시처럼 제한된 용량의 램으로 퀴리에 알맞은 인덱스를 자동으로 선택할 수 있다 → 요약하면 모든 측면에서 고성능을 유지하기 위해 설계됨.

MongoDB 기본

기본 개념

  • 몽고DB 데이터의 기본 단위는 도큐먼트이며, 이는 관계형 데이터베이스의 과 유사함
  • 같은 맥락에서 컬렉션 collection 은 동적 스키마 dynamic schema 가 있는 테이블과 같다
  • 몽고DB의 단일 인스턴스는 자체적인 컬력션을 갖는 여러 개의 독립적인 데이터베이스를 호스팅한다
  • 모든 도큐먼트는 컬렉션 내에서 고유한 특수키인 “_id” 를 가진다
  • 몽고DB는 몽고 셸 The mongo Shell 이라는 간단하지만 강력한 도구와 함께 배포된다
    • mongo 셸은 몽고DB 인스턴스를 관리하고 몽고DB 쿼리 언어로 데이터를 조작하기 위한 내장 지원을 제공
    • 또한 사용자가 다양한 목적으로 자신의 스크립트를 만들고 로드할 수 있는 완전한 기능의 자바스크립트 해석기 JavaScript interpreter 다

도큐먼트

  • 몽고DB의 핵심은 정렬된 키와 연결된 값의 집합으로 이뤄진 도큐먼트다. 표현 방식은 맵 map, 해시 hash, 딕셔너리 dictionary 와 같은 자료 구조를 가짐.
  • 예를 들어 자바스크립트에서 도큐먼트는 객체로 표현된다.
# 키는 \0(null 문자)을 포함하지 않는다. \0은 키의 끝을 나타내는 데 사용
# .과 $ 문자는 몇 가지 특별한 속성을 가지며, 특정 상황에서 사용되며 보통 예약어 reserved word 로 취급됨
{"greeting" : "Hello, world!"}
{"greeting" : "Hello, world!", "views" : 3}

# 데이터형과 대소문자를 구별함
{"count" : 5} 와 {"count" : "5"} 는 다르다
{"count" : 5} 와 {"Count" : 5} 는 다르다

# 키가 중복될 수 없다, 아래는 올바른 도큐먼트가 아니다
~~{"greeting" : "Hello, world!", "greeting" : "Hello, world!"}~~

컬렉션

  • 도큐먼트모음. 컬렉션은 RDBMS 의 테이블에 대응됨
  • 컬렉션은 동적 스키마를 가진다. 하나의 컬렉션 내 도큐먼트들이 모두 다른 구조를 가질 수 있다는 의미다. 예를 들어 다음 도큐먼트들을 하나의 컬렉션에 저장할 수 있다
{"greeting" : "Hello, world!", "views" : 3}
{"signoff" : "Good night, and good luck"}
  • 도큐먼트들의 키, 키의 개수, 데이터형의 값은 모두 다르다. 다른 구조의 도큐먼트라도 같은 컬렉션에 저장할 수 있는데 “왜 별도의 컬렉션이 필요하지?” 합당한 이유가 있다
    • 같은 컬렉션에 다른 종류의 도큐먼트를 저장하면 개발자와 관리자에게 번거로운 일이 생길 수도 있다. 각 퀴리가 특정 스키마를 고수하는 도큐먼트를 반환하는지, 혹은 쿼리한 코드가 다른 구조의 도큐먼트를 다룰 수 있는지 확실히 확인하자.
    • 컬렉션별로 목록을 뽑으면 한 컬렉션 내 특정 데이터형별로 쿼리해서 목록을 뽑을 때보다 휠씬 빠르다.
    • 같은 종류의 데이터를 하나의 컬렉션에 모아두면 데이터 지역성 data localoty 에도 좋다.
    • 인덱스를 만들면 도큐먼트는 특정 구조를 가져야 한다 (고유 인덱스일 경우 특히 더 그렇다)
  • 스키마를 만들고 관련된 유형의 도큐먼트를 그룹화하는 데는 타당한 이유가 있다. 애플리케이션 스키마는 기본적으로 필요하지는 않지만 정의하면 좋다.

네이밍

  • 컬렉션은 이름으로 식별된다. 컬렉션명은 어떤 UTF-8 문자열이든 쓸수 있지만 제약 조건이 있다.
  • 서브컬렉션 subcollection 의 네임스페이스 namespace 에 .(마침표) 문자를 사용해 컬렉션을 체계화한다.
  • 예를 들어 블로그 기능이 있는 애플리케이션은 blog.posts 와 blog.authors 라는 컬렉션을 가질 수 있다.
  • 이는 단지 체계화를 위함이며 blog 컬렉션이나 자식 컬렉션 child collection 과는 아무런 관계가 없다 (심지어 blog 컬렉션은 없어도 된다)

데이터베이스

  • 몽고DB는 컬렉션에 도큐먼트를 그룹화할 뿐 아니라 데이터베이스에 컬렉션을 그룹 지어 놓는다.
  • 몽고DB의 단일 인스턴스는 여러 데이터베이스를 호스팅할 수 있으며, 각 데이터베이스를 완전히 독립적으로 취급할 수 있다.
  • 데이터베이스는 컬렉션과 마찬가지로 이름으로 식별된다. 데이터베이스명은 어떤 UTF-8 문자열이든 쓸수 있지만 제약 조건이 있다.
  • 예약된 데이터베이스 이름 : admin , local ,
    • admin : 인증권한 부여 역할을 함.
    • local : 단일 서버에 대한 데이터를 저장. 복제 셋 replica set 에서 local 은 복제 프로세스에 사용된 데이터를 저장. local 데이터베이스 자체는 복제되지 않는다.
    • config : 샤딩 sharding 된 몽고DB 클러스터는 config 데이터베이스를 사용해 각 샤드 shard 의 정보를 저장한다.
  • 컬렉션을 저장하는 데이터베이스의 이름을 컬렉션명 앞에 붙이면 올바른 컬렉션명인 네임스페이스를 얻는다.

몽고DB를 사용을 권장하지 않는 경우

  • 다양한 유형의 데이터를 여러 차원에 걸쳐 조인하는 작업은 관계형 데이터베이스에 적합하다. 몽고DB는 이러한 작업을 잘 처리하지 못하며 거의 다룰 일이 없다.

  • 몽고DB보다 관계형 데이터베이스를 사용하는 가장 큰 이유는 몽고DB를 지원하지 않는 도구를 사용할 수 있기 때문이다. 워드프레스등 아직 관계형 데이터베이스 생태계를 따라가지 못한다.


👉 Step 02. Percona Operator for MongoDB

Percona Server for MongoDB (PSMDB) vs MongoDB

  • Percona Server for MongoDB 6.0 is based on MongoDB 6.0.
  • Percona Server for MongoDB extends MongoDB Community Edition to include the functionality that is otherwise only available in MongoDB Enterprise Edition.
  • nstalling Percona Server for MongoDB - 링크
  • Percona Memory Engine - 링크
  • Hot Backup - 링크
  • Authentication - 링크
  • HashiCorp Vault integration - 링크
  • Profiling rate limit - 링크
  • Tune parameters - 링크
  • (참고) 와이어드타이거 스토리지 엔진 WiredTiger Storage Engines : 몽고DB의 기본 스토리지 엔진
    • 서버가 시작되면 데이터 파일을 열고 체크포인팅 checkpointing 과 저널링 프로세스를 시작한다.
    • 운영체제와 함께 작동하며, 운영체제는 데이터를 디스크로 플러시하는 것뿐 아니라 데이터를 안팎으로 페이징하는 데 중점을 준다.
    • 압축컬렉션인덱스에 대해 기본적으로 설정돼 있다. 기본 압축 알고리즘은 구글의 스내피 snappy 다.
    • 압축은 데이터베이스의 스토리지 사용을 최소화하는 대신 추가적인 CPU 요구사항이 있다.
    • 도큐먼트 수준의 동시성은 컬렉션에 있는 여러 클라이언트의 서로 다른 도큐먼트를 동시에 갱신할 수 있게 한다.
    • 와이어드타이거는 다중 버전 동시성 제어 MultiVersion Concurrency Control (MVCC) 을 사용해 읽기와 쓰기 작업을 격리함으로써, 작업 시작 시 데이터의 일관된 특정 시점 뷰를 클라이언트가 볼 수 있게 한다.
    • 체크포인팅은 데이터의 일관된 특정 시점 스냅샷을 생성하며 60초마다 발생한다. 스냅샷의 모든 데이터를 디스크에 쓰고 관련 메타데이터를 갱신하는 작업이 포함한다.
    • 체크포인팅을 사용하는 저널링은 mongod 프로세스 실패 시 데이터가 손실되는 시점이 없게 한다. 와이어드타이거는 수정 사항을 적용하기 전에 저장하는 로그 선행 기입(저널)을 사용한다.

Percona Operator for MongoDB - 링크 Github RN
- Percona Operator 는 PSMDB 를 생성 변경 삭제를 관리 + Percona Server for MongoDB replica set.
- PSMDB 몽고 지원 버전 : MongoDB v4.4 , v5.0 , v6.0 - Link
- 공식 지원 플랫폼 : GKE 1.24~1.28 , AWS EKS 1.24~1.28, Azure AKS 1.25~1.28, Minikube 1.31.2 등 - Link
- 리소스 : 최소 3개 노드 - 샤딩 미사용(램 2GB, 2CPU, 볼륨 60GB) , 샤딩 사용(램 6GB, 4CPU)


📚 Design overview - 링크

  • 오퍼레이터는 Percona Server for MongoDB Replica set 혹은 Sharded cluster 으로 구성 가능
  • 클라이언트 애플리케이션은 mongo+srv URI 주소로 접속
  • Replicaset cluster 구성

  • one primary server and several secondary ones (two in the picture), and the client application accesses the servers via a driver.
  • Sharded cluster 구성

  • the case of a sharded cluster, each shard is a replica set which contains a subset of data stored in the database, and the mongos query router acts as an entry point for client applications - Link
  • 오퍼레이터 구성

  • 오퍼레이터는 PerconaServerMongoDB 오브젝트를 사용 하여, 각 Percona Server for MongoDB 를 관리하며 적절한 Replica Set 을 제공한다

✅ 오퍼레이터 설치

Install Percona server for MongoDB on Kubernetes
[EKS] [Generic] [파라미터] [이미지버전] [Chart]

# CRD 설치
kubectl apply --server-side -f https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/crd.yaml
kubectl get crd | grep psmdb

# namespace 생성
kubectl create ns psmdb

# 실습 편리를 위해서 네임스페이스 변경
kubectl get pod
kubectl ns psmdb       # 꼭! 꼭! psmdb 로 네임스페이스 변경을 하시고 실습 진행을 하셔야 됩니다!
kubectl get pod

# RBAC 설치
kubectl apply -f https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/rbac.yaml
kubectl get-all -n psmdb

# 오퍼레이터 설치
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/operator.yaml
cat operator.yaml | yh
kubectl apply -f operator.yaml
kubectl get deploy,pod
kubectl get-all -n psmdb

# 각자 닉네임 변수 지정 : 클러스터 이름으로 사용됨
MYNICK=<각자 자신의 닉네임>
echo "export MYNICK=<각자 자신의 닉네임>" >> /etc/profile
*MYNICK=gasida
echo "export MYNICK=gasida" >> /etc/profile*

# 계정 정보를 위한 secret 생성
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/secrets.yaml
cat secrets.yaml
cat secrets.yaml | sed -e "s/my-cluster-name/$MYNICK/" | kubectl apply -f -
cat secrets.yaml
kubectl get secret $MYNICK-secrets
kubectl get secret $MYNICK-secrets -o json | jq .data
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_DATABASE_ADMIN_USER | base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_DATABASE_ADMIN_PASSWORD| base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_CLUSTER_ADMIN_USER | base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_CLUSTER_ADMIN_PASSWORD| base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_USER_ADMIN_USER | base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_USER_ADMIN_PASSWORD| base64 -d ; echo

# 신규 터미널 : 모니터링
watch kubectl get psmdb,sts,pod,svc,ep,pvc

# 클러스터 생성 : 복제 세트(3개 파드) replsets(rs0, size 3)
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/cr.yaml
cat cr.yaml
grep ^[^#] cr.yaml | yh

curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/cluster1.yaml
cat cluster1.yaml | yh
cat cluster1.yaml | sed -e "s/my-cluster-name/$MYNICK/" | kubectl apply -f - && kubectl get psmdb -w

# 클러스터 생성 정보 확인 : 약자 psmdb
kubectl get perconaservermongodbs
kubectl get psmdb
NAME        ENDPOINT                                STATUS   AGE
gasida-db   gasida-db-rs0.psmdb.svc.cluster.local   ready    160m

## 클러스터 상세 정보 확인
kubectl get psmdb gasida -o yaml | kubectl neat | yh

# 클러스터 파드 정보 확인
kubectl get sts,pod -owide
kubectl get svc,ep
kubectl df-pv
kubectl get pvc,pv

# 노드 정보 확인 : affinity.antiAffinityTopologyKey: "kubernetes.io/hostname" 이해하기
# https://docs.percona.com/percona-operator-for-mongodb/constraints.html#affinity-and-anti-affinity
kubectl describe node | more
kubectl get node --label-columns=kubernetes.io/hostname,topology.kubernetes.io/zone

# mongodb 이미지 버전 확인
~~~~kubectl get perconaservermongodbs $MYNICK -o jsonpath={.spec.image} ; echo
percona/percona-server-mongodb:6.0.9-7

# (옵션) 설치 리소스 확인
kubectl get-all --namespace=psmdb
kubectl get-all --since 10m
kubectl get-all --only-scope=cluster

## (참고) psmdb 클러스터 삭제 시
cat cluster1.yaml | sed -e "s/my-cluster-name/$MYNICK/" | kubectl delete -f -

✅ 헤드리스 서비스 접속 확인

# 헤드리스 서비스 확인 : ClusterIP None
kubectl get svc,ep

# 엔드포인트 슬라이스 정보 확인 
kubectl describe endpointslices
kubectl get endpointslices

# netshoot 이미지로 netdebug 파드에 zsh 실행
kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh
--------------------
## 변수 지정
MYNICK=<각자 자신의 닉네임>
MYNICK=gasida

## 헤드리스 서비스 접속 도메인 확인
nslookup $MYNICK-rs0
nslookup -type=srv $MYNICK-rs0
nslookup $MYNICK-rs0-0.$MYNICK-rs0
nslookup $MYNICK-rs0-1.$MYNICK-rs0
nslookup $MYNICK-rs0-2.$MYNICK-rs0

exit
--------------------

✅ 클라이언트 배포

# myclient 데몬셋 배포
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/myclient.yaml
cat myclient.yaml | yh
VERSION=4.4.24-23 envsubst < myclient.yaml | kubectl apply -f -
kubectl get pod -l name=mongodb -owide

# 몽고 config 확인 : CLUSTER_USER로 접속 후 확인
kubectl get cm $MYNICK-rs0-mongod -o yaml | kubectl neat | yh

# [터미널1] 클러스터 접속(ADMIN_USER)
cat secrets.yaml | yh
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://userAdmin:userAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
----------------------
rs0:PRIMARY> show dbs
admin   0.000GB
config  0.000GB
local   0.001GB

rs0:PRIMARY> db
admin

rs0:PRIMARY> db.getUsers()
...

## 데이터베이스를 사용할 유저 생성
rs0:PRIMARY> db.createUser({user: "doik" , pwd: "qwe123" , roles: [ "userAdminAnyDatabase", "dbAdminAnyDatabase","readWriteAnyDatabase"]})

## 복제 정보 확인 시도
rs0:PRIMARY> rs.status()

# [터미널2] 클러스터 접속(CLUSTER_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> db
rs0:PRIMARY> rs.status()
rs0:PRIMARY> rs.status()['members']

# 몽고 config 적용 확인
rs0:PRIMARY> db.getProfilingLevel()

✅ 몽고DB CRUD

db, collection 으로 구성
데이터는 각 collection 에 document 형식(python dictionary) 로 저장, collection 들의 논리적인 집합이 database

# [터미널3] 클러스터 접속(doik)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://doik:qwe123@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
# doik 테이터베이스 선택(없으면 데이터베이스 생성됨) 및 test 콜렉션에 도큐먼트 1개 넣기
rs0:PRIMARY> use doik

# 콜렉션 확인
rs0:PRIMARY> show collections

# 콜렉션 생성
## (옵션) capped:true 최초 제한된 크기로 생성된 공간에서만 데이터를 저장하는 설정 (고성능, 저장공간차면 기존 공간 재사용, 일정시간만 저장하는 로그에 적합)
rs0:PRIMARY> db.createCollection("test", {capped:true, size:10000})

# 콜렉션 확인
rs0:PRIMARY> show collections
test

# 콜렉션에서 도큐먼트 조회
rs0:PRIMARY> db.test.find()

# 콜렉션에 도큐먼트 추가
rs0:PRIMARY> db.test.insertOne({ hello: 'world' })

# 콜렉션에서 도큐먼트 조회
rs0:PRIMARY> db.test.find()
rs0:PRIMARY> db.test.find({},{_id:0})
{ "hello" : "world" }

# 현재 데이터베이스의 콜렉션 통계 정보 확인
rs0:PRIMARY> db.test.stats()

# 콜렉션 삭제하기
rs0:PRIMARY> db.test.drop()

# 콜렉션 확인
rs0:PRIMARY> show collections

# 현재 데이터베이스의 통계 정보 확인
rs0:PRIMARY> db.stats()

👉 Step 03. 복제

실습을 위한 터미널 접속 정보

# [터미널1] 클러스터 접속(ADMIN_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://userAdmin:userAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"

# [터미널2] 클러스터 접속(CLUSTER_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"

# [터미널3] 클러스터 접속(doik)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://doik:qwe123@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"

복제 셋 확인 - 오피로그 확인
복제 셋 정보 상세 확인

# [터미널2] 클러스터 접속(CLUSTER_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY>

# 복제 셋 정보 확인 : 구성원 상태 확인
rs0:PRIMARY> rs.status()
rs0:PRIMARY> rs.status()['members']
[
	{
		"_id" : 0,
		"name" : "gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017",
		"health" : 1,
		"state" : 1,
		"stateStr" : "PRIMARY",
		"uptime" : 6217,
		"optime" : {
			"ts" : Timestamp(1654993085, 1),
			"t" : NumberLong(1)
		},
...

# 복제 셋 정보 확인* : 간략히 확인, 자주 확인하는 명령어
rs0:PRIMARY> db.isMaster()
{
	"topologyVersion" : {
		"processId" : ObjectId("62a5187aac216c48b5515735"),
		"counter" : NumberLong(8)
	},
	"hosts" : [
		"gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017",
		"gasida-db-rs0-1.gasida-db-rs0.psmdb.svc.cluster.local:27017",
		"gasida-db-rs0-2.gasida-db-rs0.psmdb.svc.cluster.local:27017"
	],
	"setName" : "rs0",
	"setVersion" : 120025,
	"ismaster" : true,
...

# 오피로그 정보 확인 : 크기, 연산 시간
rs0:PRIMARY> rs.printReplicationInfo()
configured oplog size:   1794.9248046875MB
log length start to end: 27832secs (7.73hrs)
oplog first event time:  Fri Jun 10 2022 04:23:06 GMT+0000 (UTC)
oplog last event time:   Fri Jun 10 2022 12:06:58 GMT+0000 (UTC)
now:                     Fri Jun 10 2022 12:07:02 GMT+0000 (UTC)

# 동기화 상태 확인 : 세컨더리 구성원이 프라이머리의 어디까지 정보를 동기화했는지 확인
rs0:PRIMARY> rs.printSecondaryReplicationInfo()
source: my-db-psmdb-db-rs0-1.my-db-psmdb-db-rs0.psmdb.svc.cluster.local:27017
	syncedTo: Fri Jun 10 2022 01:32:51 GMT+0000 (UTC)
	0 secs (0 hrs) behind the primary
source: my-db-psmdb-db-rs0-2.my-db-psmdb-db-rs0.psmdb.svc.cluster.local:27017
	syncedTo: Fri Jun 10 2022 01:32:51 GMT+0000 (UTC)
	0 secs (0 hrs) behind the primary

# 복제 옵션 정보 확인
rs0:PRIMARY> rs.conf()
{
	"_id" : "rs0",    # 복제 세트 이름
	"version" : 99912,
	"members" : [
		{
			"_id" : 0,
			"host" : "my-db-psmdb-db-rs0-0.my-db-psmdb-db-rs0.psmdb.svc.cluster.local:27017",   # 구성원 호스트
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 2,
			"tags" : {
				"podName" : "my-db-psmdb-db-rs0-0",
				"serviceName" : "my-db-psmdb-db"
			},
			"secondaryDelaySecs" : NumberLong(0),
			"votes" : 1
		},

oplog : 오피로그 상세 확인

# [터미널2] 클러스터 접속(CLUSTER_USER)# 
rs0:PRIMARY> use local
switched to db local

rs0:PRIMARY> db
local

# 오피로그 사이즈 확인
rs0:PRIMARY> db.oplog.rs.stats().maxSize
1882136320

# 오피로그 확인
rs0:PRIMARY> db.oplog.rs.find().limit(2).pretty()
{
	"op" : "n",
	"ns" : "",
	"o" : {
		"msg" : "initiating set"
	},
	"ts" : Timestamp(1654814641, 1),   # 오피로그의 타임스탬프 값, 동기화 기준값으로 중복되지 않음
	"v" : NumberLong(2),
	"wall" : ISODate("2022-06-09T22:44:01.228Z")
}
{
	"op" : "c",   # Operation Type 어떤 종류의 작업 수행, i 삽입, u 수정, d 삭제, c 명령, n 아무 작업도 안함
	"ns" : "config.$cmd",   # 이 작업으로 영향ㅂ다은 데이터베이스와 컬렉션
	"ui" : UUID("718f318e-1bf2-445e-bf17-dfc5d6e2b987"),
	"o" : {
		"create" : "transactions",
		"idIndex" : {
			"v" : 2,
			"key" : {
				"_id" : 1
			},
			"name" : "_id_"
		}
	},
	"ts" : Timestamp(1654814641, 3),
	"t" : NumberLong(1),              # Primary Term 현재 복제세트에서 몇 번째로 선출된 프라이머리인지 구분하는 값
	"v" : NumberLong(2),
	"wall" : ISODate("2022-06-09T22:44:01.259Z")
}
...
{
	"op" : "i",
	"ns" : "admin.system.keys",
	"ui" : UUID("b237feb9-2ab1-46c4-92f7-12b91ad5ece8"),
	"o" : {
		"_id" : NumberLong("7107462145146617861"),
		"purpose" : "HMAC",
		"key" : BinData(0,"2ynIIoLIe+kzTe7hG6ZhqeNOCFA="),
		"expiresAt" : Timestamp(1662610986, 0)
	},
	"ts" : Timestamp(1654834986, 10),
	"t" : NumberLong(1),
	"v" : NumberLong(2),
	"wall" : ISODate("2022-06-10T04:23:06.051Z")
}

복제 테스트 : rs0:PRIMARY> rs.status()['members'] 에서 프라이머리 파드 확인해두기

# [터미널1] 프라이머리 파드 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-0.$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> use doik
rs0:PRIMARY> db.test.insertOne({ reptest: 1 })
rs0:PRIMARY> db.test.find()
rs0:PRIMARY> db.test.count()

# [터미널2] 세컨더리 파드1 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- bash -il
--------------------------------------
# 변수 지정
MYNICK=<각자 자신의 닉네임>
*MYNICK=gasida*
echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-1.$MYNICK-rs0.psmdb.svc/admin?ssl=false"
while true; do echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-1.$MYNICK-rs0.psmdb.svc/admin?ssl=false" | grep -v Error; date; sleep 1; done
--------------------------------------

# [터미널3] 세컨더리 파드2 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- bash -il
--------------------------------------
# 변수 지정
MYNICK=<각자 자신의 닉네임>
MYNICK=gasida
echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-2.$MYNICK-rs0.psmdb.svc/admin?ssl=false"
while true; do echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-2.$MYNICK-rs0.psmdb.svc/admin?ssl=false" | grep -v Error; date; sleep 1; done
--------------------------------------

# [터미널1] 프라이머리 파드 접속(doik) : 대량의 도큐먼트 생성 및 복제 확인
rs0:PRIMARY> for (i=0; i<1000; i++) {db.test.insert({count: i, "created_at" : new Date()})}
rs0:PRIMARY> db.test.find({},{_id:0})
...
Type "it" for more

👉 Step 04. 장애 발생

🔥 [장애1] 프라이머리 파드 삭제

강제로 rs0-Y 프라이머리 파드 1개 삭제

# [터미널3] 세컨더리 파드2 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- bash -il
--------------------------------------
# 변수 지정
MYNICK=<각자 자신의 닉네임>
*MYNICK=gasida*
echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-2.$MYNICK-rs0.psmdb.svc/admin?ssl=false"
while true; do echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-2.$MYNICK-rs0.psmdb.svc/admin?ssl=false" | grep -v Error; date; sleep 1; done
--------------------------------------

# [터미널1] 모니터링
watch -d "kubectl get psmdb;echo; kubectl get pod,pvc -l app.kubernetes.io/component=mongod -owide"

# [터미널2] 클러스터 접속(CLUSTER_USER) : 프라이머리 파드 확인
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> rs.status()['members']
rs0:PRIMARY> db.isMaster()

# 오퍼레이터 로그
kubectl logs -l name=percona-server-mongodb-operator -f

# 프라이머리 파드가 배포 정보 확인
kubectl get pod -l app.kubernetes.io/instance=$MYNICK -owide

# 강제로 rs0-Y 프라이머리 파드 1개 삭제
kubectl delete pod $MYNICK-rs0-0
kubectl delete pod $MYNICK-rs0-1
kubectl delete pod $MYNICK-rs0-2

# [터미널2] 클러스터 접속(CLUSTER_USER) : 프라이머리 파드 확인
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> rs.status()['members']
rs0:PRIMARY> db.isMaster()

🔥 [장애2] Node 장애

강제로 프라이머리 파드가 있는 노드를 drain

# 오퍼레이터 로그
kubectl logs -l name=percona-server-mongodb-operator -f

# [터미널2] 클러스터 접속(CLUSTER_USER) : 프라이머리 파드 확인
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> rs.status()['members']
rs0:PRIMARY> db.isMaster()

# 프라이머리 파드가 배포 정보 확인
kubectl get psmdb
kubectl get pod -l app.kubernetes.io/instance=$MYNICK -owide

# 워커노드 drain
*# kubectl drain <<노드>> --ignore-daemonsets --delete-emptydir-data*
kubectl get node
NODE=*<각자 자신의 EC2 노드 이름 지정>*
NODE=*ip-192-168-3-96.ap-northeast-2.compute.internal*
kubectl drain $NODE --delete-emptydir-data --force --ignore-daemonsets && kubectl get node -w

# [터미널1] 클러스터 접속(CLUSTER_USER) : 장애 상태 확인
rs0:PRIMARY> rs.status()['members']
[
	{
		"_id" : 0,
		"name" : "gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017",
		"health" : 0,
		"state" : 8,
		"stateStr" : "(not reachable/healthy)",
		"uptime" : 0,
		"optime" : {
			"ts" : Timestamp(0, 0),
			"t" : NumberLong(-1)
		},
		"optimeDurable" : {
			"ts" : Timestamp(0, 0),
			"t" : NumberLong(-1)
		},
		"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
		"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
		"lastAppliedWallTime" : ISODate("2022-06-12T01:32:45.665Z"),
		"lastDurableWallTime" : ISODate("2022-06-12T01:32:45.665Z"),
		"lastHeartbeat" : ISODate("2022-06-12T01:33:37.678Z"),
		"lastHeartbeatRecv" : ISODate("2022-06-12T01:32:50.668Z"),
		"pingMs" : NumberLong(0),
		"lastHeartbeatMessage" : "Error connecting to gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017 :: caused by :: Could not find address for gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017: SocketException: Host not found (authoritative)",
		"syncSourceHost" : "",
		"syncSourceId" : -1,
		"infoMessage" : "",
		"configVersion" : 120025,
		"configTerm" : -1
	},

# 동작 확인 후 uncordon 설정
kubectl get psmdb
kubectl uncordon $NODE


🔥 [장애3] 임의의 두 노드를 drain

강제로 노드 2개를 drain : 직접 테스트 해보세요! → 1대만 정상일 경우, Read 가능한지? Write 가능한지?

강제로 노드 2개를 Drain할 경우 현재 생성되어 있는 매니지드 노드그룹이 3개이므로, PDB 정책에 의해 파드가 퇴거되지 않는 상황이 발생합니다.



📌 Reference

profile
안녕하세요! DevOps 엔지니어 이재찬입니다. 블로그에 대한 피드백은 언제나 환영합니다! 기술, 개발, 운영에 관한 다양한 주제로 함께 나누며, 더 나은 협업과 효율적인 개발 환경을 만드는 과정에 대해 인사이트를 나누고 싶습니다. 함께 여행하는 기분으로, 즐겁게 읽어주시면 감사하겠습니다! 🚀

0개의 댓글