Helm Chart를 이용한 웹서버 및 DB(MySQL) 배포, 연동

박재하·2024년 1월 7일
1

DevOps 기초

목록 보기
5/9

Helm Chart

Helm이란?

Helm은 쿠버네티스 오픈 소스 패키지 매니저임. 쿠버네티스용으로 구축된 소프트웨어를 제공, 공유 및 사용할 수 있는 기능을 제공함.

chart라는 패키지 형식을 사용하며, chart는 쿠버네티스 리소스 집합을 설명하는 파일 모음임.
단일 차트를 사용해 memcached 파드같은 단순한 것 부터 HTTP 서버, 데이터베이스, 캐시 등이 포함된 전체 웹 앱 스택과 같은 복잡한 것도 배포할 수 있음.

chart는 하나의 디렉토리 내부에 파일 모음으로 구성되며, 디렉토리 이름이 각 chart의 이름임. npm, pip와 같이 helm chart를 저장하고 공유하는 데 사용할 수 있는 chart repository를 지원하는데, 대표적으로 Artifact Hub가 있다.

Helm 설치 (macOS)

brew install helm

mysql 패키지 설치

이후 ArtifactHub에서 mysql 패키지를 검색해 설치 방법을 확인한다.

스크린샷 2024-01-08 오후 2 52 38

실제로는 학습메모 3을 참고해 저장소를 등록하고 파라미터로 패스워드등을 설정해 설치해보자.

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install [릴리즈명] --set auth.rootPassword=[루트비밀번호] bitnami/mysql
스크린샷 2024-01-08 오후 2 56 59

마지막으로 정상적으로 deploy됐는지 helm ls로 확인한다.

helm ls
스크린샷 2024-01-08 오후 2 58 56

설치한 mysql 패키지로 접속

스크린샷 2024-01-08 오후 4 49 31

간단한 실행법은 위에서 helm install할 때 위 그림과 같이 나온다.

kubectl run kube-test-mysql-client --rm --tty -i --restart='Never' --image  docker.io/bitnami/mysql:8.0.35-debian-11-r2 --namespace default --env MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD --command -- bash

mysql -h kube-test-mysql.default.svc.cluster.local -uroot -p"$MYSQL_ROOT_PASSWORD"

$MYSQL_ROOT_PASSWORD 대신 위에서 등록한 비밀번호를 넣어주면 됨.

스크린샷 2024-01-08 오후 4 53 40

잘 접속됨!

웹서버 MySQL 연동

NestJS TypeORM + MySQL 패키지 설치 및 연동

MySQL 패키지와 웹서버 패키지를 함께 올려서 실행하는 Helm Chart를 만들고 실행하는 것이 목표.

따라서 우선 웹서버를 MySQL DB연동을 하도록 개선해줘야 한다.

npm install typeorm @nestjs/typeorm mysql2 dotenv

typeorm과 mysql2, dotenv 패키지를 설치해주고

// typeorm.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { configDotenv } from 'dotenv';
configDotenv();

const typeOrmConfig: TypeOrmModuleOptions = {
  type: process.env.MYSQL_TYPE as any,
  host: process.env.MYSQL_HOST,
  port: parseInt(process.env.MYSQL_PORT),
  username: process.env.MYSQL_USERNAME,
  password: process.env.MYSQL_PASSWORD,
  database: process.env.MYSQL_DATABASE,
  entities: [__dirname + '/../**/*.entity{.ts,.js}'],
  synchronize: true,
};

export default typeOrmConfig;

환경설정 파일을 src/board/configs/typeorm.config.ts에 만들어준다. .env 파일로 secret 처리를 해주자.

MYSQL_TYPE=mysql
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USERNAME=root
MYSQL_PASSWORD=kube-test-mysql-password
MYSQL_DATABASE=kube-test

우선 포트랑 호스트는 모르니까 localhost:3306으로 해놓고 Helm Chart에서 설정 되는지 보자.

// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { BoardsModule } from './boards/boards.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import typeOrmConfig from './boards/configs/typeorm.config';

@Module({
  imports: [BoardsModule, TypeOrmModule.forRoot(typeOrmConfig)],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

app.module.ts에 TypeORM 모듈 등록해주고

// board.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Board {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  content: string;
}

아주 간단하게 Board 엔티티 만들어주고

// boards.module.ts
import { Module } from '@nestjs/common';
import { BoardsService } from './boards.service';
import { BoardsController } from './boards.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Board } from './entities/board.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Board])],
  controllers: [BoardsController],
  providers: [BoardsService],
})
export class BoardsModule {}

모듈에 엔티티 등록해주고

// board.service.ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { BoardsService } from './boards.service';
import { CreateBoardDto } from './dto/create-board.dto';
import { UpdateBoardDto } from './dto/update-board.dto';

@Controller('boards')
export class BoardsController {
  constructor(private readonly boardsService: BoardsService) {}

  @Post()
  create(@Body() createBoardDto: CreateBoardDto) {
    return this.boardsService.create(createBoardDto);
  }

  @Get()
  findAll() {
    return this.boardsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.boardsService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateBoardDto: UpdateBoardDto) {
    return this.boardsService.update(+id, updateBoardDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.boardsService.remove(+id);
  }
}

마지막으로 아~주 간단한 CRUD 서비스 코드를 작성해주면 끝.

테스트

.env 파일을 미리 설치해놨던 VM 속 mysql로 연결되도록 설정을 바꾸고 이상 없는지 Postman으로 테스트해봤다.

스크린샷 2024-01-08 오후 5 50 19 스크린샷 2024-01-08 오후 5 50 28 스크린샷 2024-01-08 오후 5 50 49 스크린샷 2024-01-08 오후 5 50 58 스크린샷 2024-01-08 오후 5 53 35

모두 잘~ 동작한다. 이제 백엔드 코드는 죄가 없는거다.

Docker build & push

FROM node:20-alpine AS builder

WORKDIR /app

COPY . /app
RUN cd /app && \
    npm run build

FROM node:20-alpine AS app

WORKDIR /app

USER node

COPY --chown=node:node --from=builder /app/dist /app/dist
COPY --chown=node:node --from=builder /app/node_modules /app/node_modules

RUN cd /app

EXPOSE 3000

CMD ["node", "dist/main"]

이제 기존에 작성했던 Dockerfile을 이용해서 ver2 태그의 서버 이미지를 만들어 push해두자.

docker build -t qkrwogk/kube-test:ver2 .
docker push qkrwogk/kube-test:ver2
스크린샷 2024-01-08 오후 6 28 16 스크린샷 2024-01-08 오후 6 28 59

준비끝

Helm Chart 직접 만들기

Helm 차트 직접 만들기

이제 학습메모 2를 참고하여 직접 만든 웹서버를 포함한 helm 차트를 만들어보자

helm create [차트명]
스크린샷 2024-01-08 오후 5 00 35

구조는 대강 이런식인가보다. Chart.yaml, values.yaml, templates/deployment.yaml 파일 등을 수정하여 만들어주면 된다.

Chart.yaml 수정

먼저 MySQL 의존성을 추가하기 위해 Chart.yaml파일의 적혀있는 내용을 그대로 둔 채, 아래에 다음 설정을 추가한다.

dependencies:
  - name: mysql
    version: 9.16.1
    repository: https://charts.bitnami.com/bitnami

아까 mysql 실습 시 helm ls에 조회되었던 version으로 명시하면 되시겠다.

전체 설정 파일은 다음과 같다.

apiVersion: v2
name: kube-test-helm-chart
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"

dependencies:
  - name: mysql
    version: 9.16.1
    repository: https://charts.bitnami.com/bitnami

templates/deployment.yaml 작성

이제 templates/deployment.yamlcontainers: 속성을 찾아가서 아래와 같이 수정하면 된다.

containers:
  - name: {{ .Chart.Name }}
    securityContext:
      {{- toYaml .Values.securityContext | nindent 12 }}
    image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
    imagePullPolicy: {{ .Values.image.pullPolicy }}
    ports:
      - name: http
        containerPort: {{ .Values.service.port }}
        protocol: TCP
    livenessProbe:
      httpGet:
        path: /
        port: http
    readinessProbe:
      httpGet:
        path: /
        port: http
    resources:
      {{- toYaml .Values.resources | nindent 12 }}
    {{- with .Values.volumeMounts }}
    volumeMounts:
      {{- toYaml . | nindent 12 }}
    {{- end }}
    env:
      - name: MYSQL_TYPE
        value: "mysql"
      - name: MYSQL_HOST
        value: {{ .Release.Name }}-mysql
      - name: MYSQL_PORT
        value: "3306"
      - name: MYSQL_USERNAME
        value: {{ .Values.mysql.auth.username }}
      - name: MYSQL_PASSWORD
        valueFrom:
          secretKeyRef:
            name: {{ .Release.Name }}-mysql
            key: mysql-password
      - name: MYSQL_DATABASE
        value: {{ .Values.mysql.auth.database }}

전체 설정파일은 다음과 같다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "kube-test-helm-chart.fullname" . }}
  labels:
    {{- include "kube-test-helm-chart.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "kube-test-helm-chart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "kube-test-helm-chart.labels" . | nindent 8 }}
        {{- with .Values.podLabels }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "kube-test-helm-chart.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.service.port }}
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          {{- with .Values.volumeMounts }}
          volumeMounts:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          env:
            - name: MYSQL_TYPE
              value: "mysql"
            - name: MYSQL_HOST
              value: {{ .Release.Name }}-mysql
            - name: MYSQL_PORT
              value: "3306"
            - name: MYSQL_USERNAME
              value: {{ .Values.mysql.auth.username }}
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: {{ .Release.Name }}-mysql
                  key: mysql-password
            - name: MYSQL_DATABASE
              value: {{ .Values.mysql.auth.database }}
      {{- with .Values.volumes }}
      volumes:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

values.yaml 수정

다음으로 values.yaml로 가서 각종 설정을 해주자.

# image:
#   repository: nginx
#   pullPolicy: IfNotPresent
#   # Overrides the image tag whose default is the chart appVersion.
#   tag: ""

image:
  repository: qkrwogk/kube-test
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: "ver2"

가장 먼저 image: 속성을 찾아 기존에 nginx로 되어있던 repository를 우리가 만들어 push한 이미지로 바꿔준다.

# service:
#   type: ClusterIP
#   port: 80

service:
  type: LoadBalancer
  port: 3000

다음으로 외부 노출을 위해 service:설정을 기존 ClusterIP/80에서 LoadBalancer/3000으로 바꿔준다.

# mysql configuration
mysql:
  auth:
    username: kube-test-mysql-user # root하면 에러뜸 아래에 트러블슈팅있음
    password: kube-test-mysql-password
    database: kube-test

마지막으로 의존성에 추가했던 mysql Helm Chart에 대한 설정을 해준다.
기존에 있던 default value들을 덮어써주는 역할을 함. 아까 테스트했던 것과 동일하게 임의로 설정해줬다.

Helm 의존성 업데이트

모든 설정이 끝났으면 설정을 반영해준다. 루트 디렉토리로 가 다음 명령 입력.

helm dep up
스크린샷 2024-01-08 오후 9 40 09 스크린샷 2024-01-08 오후 9 44 08

mysql-9.16.1.tgz가 다운됐다! 이제 만든 차트로 설치만 하면 됨

만든 Helm Chart 설치 및 실행

Helm Chart 설치

helm install kube-test .

하지만 안됨! 왜냐

트러블 슈팅 1 : helm 릴리즈명 중복

스크린샷 2024-01-08 오후 9 54 33

원인은 아까 실습때 이미 같은 이름(kube-test-mysql)으로 릴리즈명을 해두어서 충돌이 난 것. helm uninstall [릴리즈명]으로 삭제할 수 있었다.

helm uninstall kube-test-mysql
helm ls
스크린샷 2024-01-08 오후 9 54 18

다시 해보니 잘됨!

트러블 슈팅 2 : non-root mysql 계정 생성

스크린샷 2024-01-08 오후 9 59 51

이제 잘떴나 k9s로 확인해봤더니 CrashLoopBackOff가 떠있었다. 접속도 안되고.. 뭘까?

스크린샷 2024-01-08 오후 10 03 20 스크린샷 2024-01-08 오후 10 03 50

로그 확인으로 원인을 찾을 수 있었다. 설정대로 계정을 새로 만드는 건데, root계정을 만들어버려 mysql pod는 정상적으로 진행이 안되고, nestjs pod은 이거때문에 DB연결이 안돼서 에러가 뜨는 것.

# values.yaml

# mysql configuration
mysql:
  auth:
    username: kube-test-mysql-user
    password: kube-test-mysql-password
    database: kube-test

values.yaml의 mysql 유저 설정을 root가 아닌 다른 이름으로 변경해주고, helm uninstall 후 다시 설치해보자.

스크린샷 2024-01-08 오후 10 07 28

다시 설치하고 해보니 또 안됨

트러블 슈팅 3 : PVC 삭제

이번엔 학습메모 2에 나와있는 트러블 슈팅과 같은 pvc 미삭제 문제같아 동일하게 확인해보고 삭제를 해줌. helm uninstall로도 pvc(persistent volume claim)는 삭제되지 않는다고 한다.

kubectl get pvc
kubectl delete pvc [pvc명]
스크린샷 2024-01-08 오후 10 19 14

helm uninstall하고 저거까지 지운 다음에 다시 띄워봤다.

트러블 슈팅 4 : minikube로 서비스 실행

스크린샷 2024-01-08 오후 10 30 18

왜 kube-test-helm-chart 서비스는 펜?딩?인?걸?까?

스크린샷 2024-01-08 오후 10 28 22

비밀은 지난 과제때 친히 써놓고도 까먹었던 "minikube로 서비스를 실행해줘야 한다" 부분.

kubectl get services
minikube service kube-test-kube-test-helm-chart

드디어 해결

스크린샷 2024-01-08 오후 10 32 14

스크린샷 2024-01-08 오후 10 34 08 스크린샷 2024-01-08 오후 10 34 14

와 감격

테스트

스크린샷 2024-01-08 오후 10 35 42 스크린샷 2024-01-08 오후 10 35 58 스크린샷 2024-01-08 오후 10 36 12 스크린샷 2024-01-08 오후 10 36 29 스크린샷 2024-01-08 오후 10 36 39 스크린샷 2024-01-08 오후 10 36 47

진짜 감격

학습메모

  1. Helm으로 MySQL 설치
  2. Helm으로 서버 배포(node.js+mysql)
  3. Helm 설치
  4. ArtifactHub mysql
  5. The Bitnami Library for Kubernetes
profile
해커 출신 개발자

0개의 댓글