INTRO
앞선 글에서 Statefulset을 사용하는 것이 일반적인 이유에 대해 적었었다.
본 포스팅은 Dockerize 된 mongoDB 이미지를 statefulset으로 배포할 때
초기 데이터를 삽입하기 위해 테스트 한 경험을 공유하고자 한다.
1. 환경
- AWS EKS(Kubernetes)
- Backend(Spring boot), Frontend(Vue.js+Nginx), DB(Mongodb)
2. 목적
- 고객에게 SaaS 형태로 위 Application을 제공해야한다.
- 여기서 Mongodb를 Statefulset 형태로 배포해야하는데,
- Application 에 기본적으로 필요한 초기 Data set을 넣어서 배포하길 원한다.
- 중요한 것은, 최초 배포시에만 초기 Data set이 들어가야 한다.
- 이미 배포되어 데이터가 쌓여가는 상태에서 초기 Data set을 밀어넣는다면, 큰 문제..
3. 계획
- 현재 배포 시 Dockerize 된 MongoDB 이미지를 배포중이다.
- Dockerize 시 초기 Data set을 함께 말아주고,
- Script하나도 함께 말아준다.
- 이 Script는 pod 생성 시 수행되며, Data set을 DB Path에 밀어넣을지 말지 결정하는 분기문과
- 넣어야 한다면 복사해서 넣어주는 cp 명령어를 적어줄것이다.
- 이 동작을 최초 생성되는 mongodb-0번 pod에서 실행해주면,
- 이후 생성되는 pod들은 mongo replication 의 복구기능으로 인해 0번에서 데이터를 복사해갈것이다.
(Statefulset 기능상 0번부터 순차적으로 pod생성)
4. 의문점
이 경우 mongodb-0부터 생성되는데, primary/secondary 상관 없이 다음 pod들이 데이터셋을 복사해갈까?
4-1. 테스트
- 확인을 위해 먼저 0번이 Primary든 Secondary든 상관없이 1,2번들이 데이터를 복사해가는 것을 테스트 해본다.
- 시나리오 #1
- mongodb 0(이하 0번) 을 제외한 나머지 pod들 다 내리고, pv에서 0번 을 제외한 나머지 디렉토리들을 삭제했다.
- 1,2 파드들을 다시 스케일링 하여 띄운다.
- 띄울 때 mkdir로 비어있는 폴더를 만들어주고, db path를 해당 폴더로 설정한다.
- 결과 : 1,2의 파드들의 DB Path 폴더 내용들이 0번의 폴더내용과 같아짐. 0번이 Secondary 로 생성되는데도 불구하고 정상적으로 1,2번들이 데이터를 복사해간다는 것을 확인했다.
참고했던 글 : https://www.mongodb.com/docs/manual/tutorial/restore-replica-set-from-backup/
또한 이 과정에서, Primary를 항상 특정 Host로 지정할 수 있는 방법도 알게 되었다.(Priority)
var config = rs.config()
config.members[0].priority=2
rs.reconfig(config)
5. 다음 해야할 일
- 이제 0번 path 에서 데이터들이 복제된다는것을 확인했다.
- 0번 path가 없으면, 경로 생성후 초기 데이터셋 폴더(/mongodb) 에서 데이터셋을 가져와 0번에 넣어주고,
- 경로가 있다면 데이터가 이미 있다는 것이므로 아무것도 하지 않는다.
- 1,2 path 경로는 생성해준다.
5-1. 테스트
- 시나리오 #1 - 0번 path가 없을 경우(초기 데이터 셋 넣는 스크립트 테스트)
- PV에 mount 된 폴더들 (/db/mongodb-0~2) 들을 전부 지운다.
- sts의 replicas를 1로 띄워본다.
- 초기 데이터셋이 잘 들어갔는지 확인한다.
- 결과 : 초기 데이터셋이 잘 들어감
- 스크립트 :
DB_PATH="db/$HOSTNAME"
echo =========
echo $HOSTNAME
echo =========
echo =========
echo $DB_PATH
echo =========
echo =========
echo $MONGO_REPLSET_NUM
echo =========
if [ ! -d $DB_PATH ];
then
if [ "$HOSTNAME" = "mongodb-0" ] ;
then
mkdir /db/$HOSTNAME
cp -r /mongodb_$MONGO_REPLSET_NUM/* /db/$HOSTNAME
else
mkdir /db/$HOSTNAME
fi
else
echo "folder already exist!"
fi
mongod --replSet replset --bind_ip_all --dbpath $DB_PATH
- 시나리오 #2 - 0번 path가 없을 경우(초기 데이터 셋 넣고, 나머지 폴더들이 복사해가는지 테스트)
- PV에 mount 된 폴더들 (/db/mongodb-0,1,2) 들을 전부 지운다.
- sts의 replicas를 3개로 띄워본다.
- 초기 데이터셋이 잘 들어갔는지 확인한다.
- 나머지 폴더들 (1,2)에도 0번과 같은 데이터들이 잘 들어갔는지 확인
- mongo 접속
- db, collections들(19개) 확인
- 결과 : 1~4의 파드들의 DB Path 폴더 내용들이 0번의 폴더내용과 같아짐. 몽고에 접속해서도 정상적으로 db(APIGW), collections들(19개) 확인. 해당 테스트는 위의 4-1에서 진행한 테스트를, 실제 데이터가 있는 상태에서 한번 더 확인하기 위함
시나리오 #3 - 0번 path가 있을 경우(초기 데이터 셋 안넣고 끝나는지 테스트)
- mongo에 접속하여 collection 하나를 지워본다(19개 -> 18개)
- sts의 replicas를 0로 띄워 모든 pod를 내린다.
- PV에 mount 된 폴더들 중 0번을 제외한 모든 폴더 제거
- sts의 replicas를 3으로 띄워 pod를 띄운다
- mongo 접속
- db, collections들(18개) 확인
결과 : 아래와 같이 wiredTiger error 발생.. 스토리지가 손상되었다는 오류라는데..?
E", "c":"STORAGE", "id":22435, "ctx":"initandlisten","msg":"WiredTiger error","attr":{"error":-31802,"message":"[1668663914:160705][11:0x7ff01a4b5cc0], file:collection-16--7917382233214276918.wt, txn rollback_to_stable: __rollback_to_stable_btree_apply, 1530: file:collection-16--7917382233214276918.wt: unable to open handle: WT_ERROR: non-specific WiredTiger error"}}
여기서 WiredTiger가 무엇인지 검색해보니 mongo 3.2부터 지원하는 mongodb Storage Engine이라고 한다.
스택오버플로 등 구글링 결과 해당 에러는 fatal 수준으로 간주하는 것으로 보임.
필자 생각에도 engine에서 에러를 뿜어내는거면.. 복구가 쉽진 않아보였다.
이리저리 조치 도중 분명 스크립트에, 이미 폴더가 있으면 cp를 수행하지 않는 분기문을 태웠는데
확인해보니 분기문 문법이 잘못되어 cp 명령어를 그대로 수행하고있었다.
컬렉션을 하나 지웠는데, cp 명령어로 다시 초기 데이터셋을 덮어씌우면 어떻게 될까?
(cp 명령어는 특정 옵션을 주지 않으면 (-i) 아무말 없이 같은 이름의 파일들을 덮어씌움)
이미 collection 하나를 지워버리는 순간 다른 Secondary 들에게 복제가 발생했을것이며,
또한 oplog라는, mongo가 제어하는 collection 에 이 기록이 남을 것이다.
내 생각엔 이 oplog에는 collection을 지운 기록이 있지만, 실제로는 데이터가 변경되었으므로 부조화가 발생하여 WireTiger 엔진에서 오류가 발생한것으로 생각된다.
따라서 스크립트 수정 후 진행해보니 예상 결과에 맞게 잘 동작한다.(pod 하나는 pv 제어를 위해 임시로 띄워놓음..)
6. Statefulset yaml 파일
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app: mongodb
release: mongodb
name: mongodb
namespace: mongodb
spec:
podManagementPolicy: OrderedReady
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: mongodb
release: mongodb
serviceName: mongodb
template:
metadata:
creationTimestamp: null
labels:
app: mongodb
release: mongodb
spec:
containers:
- command: # 이 부분이 pod 시작 시 script를 실행시켜주는 부분
- sh
- -c
- |
/bin/bash <<'EOF'
sh /script/initScript.sh
EOF
env:
- name: POD_ID
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: MONGO_REPLSET_NUM
value: '5'
image: **********
imagePullPolicy: Always
name: mongodb
ports:
- containerPort: 27017
name: service
protocol: TCP
resources: {}
securityContext:
privileged: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /db
name: mongodb
dnsPolicy: ClusterFirst
hostAliases:
- hostnames:
- aaa.com
ip: 127.0.0.1
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- name: mongodb
persistentVolumeClaim:
claimName: mongodb
updateStrategy:
rollingUpdate:
partition: 0
type: RollingUpdate
마무리
위와 같은 방법이, Statefulset으로 동작하는 Database에 초기 데이터셋을 넣어주는 방법의 '정답' 인지는 확신이 서지 않는다.
다만 이러한 방법으로도 초기 데이터를 넣어줄 수 있다는 것을 알아두면 좋을 것 같다.
-퍼가실 때는 출처를 꼭 같이 적어서 올려주세요!
'DevOps > [Kubernetes]' 카테고리의 다른 글
[Kubernetes] kubernetes 환경에서 Database 구축 시 Statefulset을 사용해야 하는 이유 (0) | 2022.11.19 |
---|---|
[Kubernetes] 쿠버네티스(Kubernetes) 란? (0) | 2022.11.18 |