
📌 Notice
Database Operator In Kubernetes study (=DOIK)
데이터베이스 오퍼레이터를 이용하여 쿠버네티스 환경에서 배포/운영하는 내용을 정리한 블로그입니다.
CloudNetaStudy그룹에서 스터디를 진행하고 있습니다.
Gasida님께 다시한번 🙇 감사드립니다.EKS 및 KOPS 관련 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.
[AEWS] AWS EKS 스터디
[PKOS] KOPS를 이용한 AWS에 쿠버네티스 배포

K8s의 Operator를 통해 어플리케이션을 패키징-배포-관리할 수 있습니다.
데이터베이스 오퍼레이터를 이해하기 위해 반드시 알아야하는 쿠버네티스 관련 지식을 정리합니다.
Nosql에 대해서 알아보며 대표적인 nosql db인 MongoDB를 쿠버네티스에 배포하는 실습을 진행 합니다.
Percona Operator를 통해 MongoDB를 쿠버네티스 환경에서 배포해보며 작동방식에 대해서 알 수 있습니다.
NoSQL 이해 : Not Only SQL
- RDBMS 처럼 고정된 스키마
및 JOIN이 존재하지 않음, 스키마 변경? ALERT 등 필요 없음- NoSQL 종류 : Key-Value Store(Redis), Wide Column Store(HBase, Cassandra), Document Store(mongoDB, CouchDB), Graph Store(Neo4j)
확장 기능 : 보조 인덱스 secondary index , 범위 쿼리 range query , 정렬 sorting , 집계 aggregation , 공간 정보 인덱스 geospatial index 등
손쉬운 사용 : 도큐먼트 지향 데이터베이스 document-oriented database. 관계형 모델을 사용하지 않은 주된 이유는 분산 확장 scale-out 을 쉽게 하기 위함
확장 가능한 설계 : 몽고DB는 분산 확장을 염두에 두고 설계됨, 도큐먼트 지향 데이터 모델은 데이터를 여러 서버에 더 쉽게 분산하게 해줌.
다양한 기능 : CRUD 이외도 DBMS 의 대부분의 기능을 제공
고성능 : 동시성 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를 지원하지 않는 도구를 사용할 수 있기 때문이다. 워드프레스등 아직 관계형 데이터베이스 생태계를 따라가지 못한다.
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 - 링크
- 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
mongosquery 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, 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()
실습을 위한 터미널 접속 정보
# [터미널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
강제로 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()
강제로 프라이머리 파드가 있는 노드를 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
강제로 노드 2개를 drain : 직접 테스트 해보세요! → 1대만 정상일 경우, Read 가능한지? Write 가능한지?
강제로 노드 2개를 Drain할 경우 현재 생성되어 있는 매니지드 노드그룹이 3개이므로, PDB 정책에 의해 파드가 퇴거되지 않는 상황이 발생합니다.