📌 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
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, 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 정책에 의해 파드가 퇴거되지 않는 상황이 발생합니다.