태그 보관물: mysql

Kubernetes MariaDB Resource Limits and Health Probes

개요

MariaDB 리소스 제한 설정과 헬스체크(liveness/readiness probe) 구성을 자체 관리형 Kubernetes 클러스터에 반영했습니다. 기존 MariaDB Deployment는 리소스 request/limit이 전혀 설정되지 않은 상태(resources: {})로 2년 넘게 운영되고 있었고, liveness/readiness probe도 없어 mysqld 프로세스가 응답 없이 멈추더라도 Kubernetes가 이를 감지하고 자동으로 재시작할 방법이 없는 구조였습니다.

이번 글에서는 ① 현재 구성 점검 ② 운영 중인 리소스의 YAML 추출 및 Git 반영 ③ 리소스 제한/프로브 설계와 적용, 이렇게 세 가지 작업을 순서대로 정리합니다.

환경

  • Kubernetes v1.33.7 (Control-plane 3대, Worker 3대 HA 구성)
  • Container Runtime: Docker + cri-dockerd
  • Storage: Rook-Ceph RBD (StorageClass rook-ceph-block, ReadWriteOnce)
  • MariaDB: 10.11, mariadb-system 네임스페이스, Deployment 단일 replica
  • PVC: 10Gi, PV ReclaimPolicy: Retain
  • 서비스 노출: MetalLB LoadBalancer (내부망 전용)

단계별 절차

1. 현재 구성 형상 점검

먼저 운영 중인 Deployment, PVC, Service, ConfigMap 현황과 실제 리소스 사용량을 확인했습니다.

$ kubectl get all -n mariadb-system -o wide
$ kubectl get pvc -n mariadb-system -o wide
$ kubectl top pod -n mariadb-system

점검 결과 Deployment의 strategy는 이미 Recreate로 설정되어 있었습니다. MariaDB처럼 ReadWriteOnce 볼륨을 사용하는 단일 파드 워크로드는 RollingUpdate 방식으로 배포하면 이전 파드가 볼륨을 반납하기 전에 새 파드가 같은 볼륨을 마운트하려다 충돌하는 경우가 있어, Recreate 전략이 올바른 선택이었습니다. PV의 reclaimPolicyRetain으로 설정되어 있어 PVC가 실수로 삭제되더라도 실제 데이터는 보존되는 안전한 구조였습니다.

반면 컨테이너 리소스는 request/limit이 전혀 없는 상태였고, 실제 메모리 사용량은 약 1.4Gi 수준이었습니다. ConfigMap으로 주입한 커스텀 설정은 다음과 같았습니다.

[mysqld]
innodb_buffer_pool_size=512M
innodb_log_file_size=256M
max_connections=300

2. YAML 추출 및 Git 반영

운영 중인 리소스(PVC, ConfigMap, Deployment, Service)를 그대로 추출해 저장소에 매니페스트 파일로 문서화했습니다. Secret은 정책상 Git에 포함하지 않고, 클러스터 재구성 시 수동으로 생성할 수 있도록 커맨드만 주석으로 남겼습니다.

# root 비밀번호(mariadb-secret)는 Git에 없음 - 클러스터 재구성 시 수동 생성 필요
#   kubectl create secret generic mariadb-secret -n mariadb-system \
#     --from-literal=password='<ROOT_PASSWORD>'

추출한 매니페스트를 kubectl diff로 실제 클러스터 상태와 비교해, 새로 작성한 YAML이 운영 중인 리소스와 완전히 동일한지 먼저 확인했습니다. 차이가 없다는 것을 확인한 뒤에야 Git에 커밋했습니다.

3. 리소스 Request/Limit 및 Probe 설계

실제 메모리 사용량(~1.4Gi)과 innodb_buffer_pool_size(512M) 설정, 그리고 노드의 여유 용량을 함께 고려해 다음과 같이 값을 산정했습니다.

resources:
  requests:
    cpu: 250m
    memory: 1Gi
  limits:
    cpu: "1"
    memory: 2Gi
livenessProbe:
  exec:
    command:
      - sh
      - -c
      - mysqladmin ping -uroot -p"$MYSQL_ROOT_PASSWORD" --silent
  initialDelaySeconds: 30
  periodSeconds: 20
  timeoutSeconds: 5
  failureThreshold: 3
readinessProbe:
  exec:
    command:
      - sh
      - -c
      - mysqladmin ping -uroot -p"$MYSQL_ROOT_PASSWORD" --silent
  initialDelaySeconds: 10
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

Probe는 이미 컨테이너 환경변수로 주입되어 있던 MYSQL_ROOT_PASSWORD를 그대로 활용하는 mysqladmin ping exec 방식을 선택했습니다. 별도 Secret을 추가로 마운트할 필요 없이, 실제로 mysqld가 인증까지 정상 처리하는지 확인할 수 있는 방식입니다. Limit 산정 시에는 노드 전체 할당량 대비 여유가 충분한지(CPU 할당 비율, 메모리 할당 비율) 함께 확인한 뒤 값을 확정했습니다.

4. 적용 및 롤아웃 검증

kubectl apply 전에 반드시 kubectl diff로 변경 범위를 먼저 확인했습니다. Deployment의 strategyRecreate이기 때문에 적용 시 기존 파드가 먼저 종료되고 새 파드가 그 자리에 생성되는, 짧은 다운타임이 있는 변경이라는 점을 미리 인지하고 진행했습니다.

$ kubectl diff -f manifests/mariadb/mariadb.yaml
$ kubectl apply -f manifests/mariadb/mariadb.yaml
deployment.apps/mariadb configured

$ kubectl rollout status deployment/mariadb -n mariadb-system --timeout=120s
Waiting for deployment "mariadb" rollout to finish: 0 of 1 updated replicas are available...
deployment "mariadb" successfully rolled out

$ kubectl get pods -n mariadb-system -l app=mariadb
NAME                       READY   STATUS    RESTARTS   AGE
mariadb-xxxxxxxxxx-xxxxx   1/1     Running   0          12s

새 파드가 정상적으로 Ready 상태가 되었고, readiness probe가 통과하는 것을 확인했습니다. 로그에도 mariadbd: ready for connections가 정상 출력되어 별다른 문제 없이 전환이 완료되었습니다.

트러블슈팅

mysql.event 테이블 정의 불일치

현상: 파드 재시작 로그에 Incorrect definition of table mysql.event 에러와 함께 Event Scheduler가 비활성화된다는 메시지가 출력되었습니다.

원인: 과거 MariaDB 마이너 버전 업그레이드 이후 mysql_upgrade를 실행하지 않아, 시스템 테이블(mysql.event)의 컬럼 정의가 현재 바이너리 버전이 기대하는 스키마와 어긋난 상태로 남아있었습니다.

해결: 현재 Event Scheduler(예약 이벤트) 기능을 사용하고 있지 않아 서비스에는 영향이 없는 것으로 확인해, 이번 작업 범위에서는 별도 조치 없이 별도 후속 작업으로 분리했습니다. Event Scheduler를 사용할 계획이라면 mysql_upgrade 실행이 선행되어야 합니다.

결과 확인

최종적으로 다음 항목들을 확인해 이번 MariaDB 리소스 제한 및 헬스체크 반영 작업을 마무리했습니다.

$ kubectl get deployment mariadb -n mariadb-system \
  -o jsonpath='{.spec.template.spec.containers[0].resources}'
{"limits":{"cpu":"1","memory":"2Gi"},"requests":{"cpu":"250m","memory":"1Gi"}}

$ kubectl get pod -n mariadb-system -l app=mariadb
NAME                       READY   STATUS    RESTARTS   AGE
mariadb-xxxxxxxxxx-xxxxx   1/1     Running   0          2m
  • 리소스 request/limit 적용 완료 (request 250m/1Gi, limit 1core/2Gi)
  • liveness/readiness probe 정상 동작 확인
  • 클러스터 재기동 없이 무중단으로 서비스(LoadBalancer)가 재공지되어 애플리케이션 연결 영향 없음

참고

관련 포스트:

참고 문서: Kubernetes – Configure Liveness, Readiness and Startup Probes · Kubernetes – Resource Management for Pods and Containers

MySQL Operation, Maintenance

개요

MySQL Operation 및 유지관리에 자주 사용하는 명령어를 정리합니다. 데이터베이스/테이블 조회, 사용자 권한 관리, 프로세스 모니터링, 서비스 시작/중지 방법을 빠르게 참조할 수 있는 운영 레퍼런스입니다.

MySQL Operation

  • DB 구성 상태 확인
> show databases;
// 데이터베이스 목록 보기

> show tables;
// 테이블 목록 보기

> show columns from 'table name';
// 테이블 칼럼 목록 보기

> SHOW VARIABLES LIKE 'c%';
// 캐릭터셋 보기

MySQL Maintenance

  • DBMS 상태 확인
> show status;
// MySQL 데이타베이스의 현재 상황

> show Processlist;
// MySQL 프로세스 목록

> show variables
// 설정 가능한 모든 변수 목록

> SELECT table_schema "Database Name",  SUM(data_length + index_length) / 1024 / 1024 "Size(MB)"  FROM information_schema.TABLES  GROUP BY table_schema;
// DB별 사용량 확인

SELECT table_name, table_rows, round(data_length/(1024*1024),2) as 'DATA_SIZE(MB)', round(index_length/(1024*1024),2) as 'INDEX_SIZE(MB)' FROM information_schema.TABLES where table_schema = '데이터베이스이름' GROUP BY table_name ORDER BY data_length DESC LIMIT 20;
// 해당 DB의 테이블 사이즈 상위 20개 정렬
  • Connection 및 Client 상태 확인
> show variables like '%max_connection%';
// 최대 커넥션 가능 수량 확인

> show status like '%connect%';
// 커넥션 연결 상태 확인

> show status like '%clients%';
// 클라이언트 연결 상태 확인

> show status like '%thread%';
// 쓰레드 상태 확인
Topic Desc
Aborted_clients 클라이언트 프로그램이 비 정상적으로 종료된 수
Aborted_connects MySQL 서버에 접속이 실패된 수
Max_used_connections 최대로 동시에 접속한 수
Threads_cached Thread Cache의 Thread 수
Threads_connected 현재 연결된 Thread 수
Threads_created 접속을 위해 생성된 Thread 수
Threads_running Sleeping 되어 있지 않은 Thread 수
wait_timeout 종료전까지 요청이 없이 기다리는 시간 (TCP/IP 연결, Shell 상의 접속이 아닌 경우)
thread_cache_size thread 재 사용을 위한 Thread Cache 수로써, Cache 에 있는 Thread 수보다 접속이 많으면 새롭게 Thread를 생성한다.
max_connections 최대 동시 접속 가능 수
참고값

사용자 및 권한 관리

MySQL/MariaDB 운영에서 계정 관리는 보안의 핵심입니다. 애플리케이션별로 전용 계정을 만들고 필요한 권한만 부여하는 최소 권한 원칙(Principle of Least Privilege)을 따르는 것이 중요합니다. 아래는 자주 사용하는 사용자 생성, 권한 부여, 확인 명령어입니다.

-- 사용자 생성 (특정 호스트에서만 접속 허용)
CREATE USER 'app_user'@'x.x.x.x' IDENTIFIED BY '<REDACTED>';

-- DB 전체 권한 부여
GRANT ALL PRIVILEGES ON mydb.* TO 'app_user'@'x.x.x.x';

-- 특정 권한만 부여 (읽기 전용 계정)
GRANT SELECT ON mydb.* TO 'readonly_user'@'%';

-- 모니터링 계정 권한 부여
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';

-- 권한 목록 확인
SHOW GRANTS FOR 'app_user'@'x.x.x.x';

-- 권한 즉시 반영
FLUSH PRIVILEGES;

-- 사용자 삭제
DROP USER 'old_user'@'%';

계정의 접속 호스트를 '%'(모든 호스트)로 지정하면 편리하지만 보안 위협이 증가합니다. 내부 서비스 계정은 가능하면 특정 IP 또는 서브넷으로 제한하고, 외부에서의 직접 DB 접근은 방화벽으로 차단하는 것이 권장됩니다.

서비스 관리

MySQL/MariaDB 서비스의 시작, 중지, 재시작, 상태 확인은 운영 중 가장 자주 실행하는 명령입니다. 서비스 재시작 전에는 반드시 현재 접속된 세션 수(SHOW STATUS LIKE '%connect%')를 확인하여 영향을 최소화해야 합니다.

# systemd 기반 (RHEL 7+, Ubuntu 16.04+)
systemctl start mariadb      # 시작
systemctl stop mariadb       # 중지
systemctl restart mariadb    # 재시작
systemctl status mariadb     # 상태 확인
systemctl enable mariadb     # 부팅 시 자동 시작 등록

# 설정 파일 변경 후 재시작 없이 일부 변수 반영
mysql -u root -p -e "SET GLOBAL max_connections = 500;"

데이터 디렉토리 기본 경로는 /var/lib/mysql이며, 용량이 부족하면 서비스가 비정상 종료될 수 있습니다. df -h /var/lib/mysql로 주기적으로 용량을 확인하고, slow query log(slow_query_log = 1)를 활성화하면 성능 병목 쿼리를 사전에 파악할 수 있습니다.

Slow Query 및 성능 모니터링

MySQL/MariaDB에서 성능 문제를 진단할 때 가장 먼저 확인하는 것이 Slow Query Log입니다. 설정된 임계값(long_query_time, 기본 10초)을 초과하는 쿼리를 자동으로 기록합니다. 운영 서버에서는 1~2초로 설정하여 느린 쿼리를 사전에 파악합니다. 설정 파일(my.cnf)에서 활성화하거나 서비스 재시작 없이 동적으로 설정할 수 있습니다.

-- Slow Query 설정 확인
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 동적으로 Slow Query Log 활성화 (재시작 불필요)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- 현재 실행 중인 쿼리 중 오래 걸리는 것 확인
SELECT * FROM information_schema.PROCESSLIST WHERE TIME > 5 ORDER BY TIME DESC;

Slow Query 로그는 mysqldumpslow 도구로 분석하면 가장 오래 걸린 쿼리, 가장 많이 호출된 쿼리를 빠르게 파악할 수 있습니다. 식별된 쿼리에 인덱스를 추가하거나 쿼리를 개선하여 성능을 향상시킵니다.

참고

참고 문서: MySQL SQL 문법 공식 문서 · MySQL 계정 관리 문법