Helm은 쿠버네티스 오픈 소스 패키지 매니저
임. 쿠버네티스용으로 구축된 소프트웨어를 제공, 공유 및 사용할 수 있는 기능을 제공함.
chart라는 패키지 형식을 사용하며, chart는 쿠버네티스 리소스 집합을 설명하는 파일 모음임.
단일 차트를 사용해 memcached 파드같은 단순한 것 부터 HTTP 서버, 데이터베이스, 캐시 등이 포함된 전체 웹 앱 스택과 같은 복잡한 것도 배포할 수 있음.
chart는 하나의 디렉토리 내부에 파일 모음으로 구성되며, 디렉토리 이름이 각 chart의 이름임. npm, pip와 같이 helm chart를 저장하고 공유하는 데 사용할 수 있는 chart repository를 지원하는데, 대표적으로 Artifact Hub가 있다.
brew install helm
이후 ArtifactHub에서 mysql 패키지를 검색해 설치 방법을 확인한다.
실제로는 학습메모 3을 참고해 저장소를 등록하고 파라미터로 패스워드등을 설정해 설치해보자.
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install [릴리즈명] --set auth.rootPassword=[루트비밀번호] bitnami/mysql
마지막으로 정상적으로 deploy됐는지 helm ls
로 확인한다.
helm ls
간단한 실행법은 위에서 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
대신 위에서 등록한 비밀번호를 넣어주면 됨.
잘 접속됨!
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으로 테스트해봤다.
모두 잘~ 동작한다. 이제 백엔드 코드는 죄가 없는거다.
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
준비끝
이제 학습메모 2를 참고하여 직접 만든 웹서버를 포함한 helm 차트를 만들어보자
helm create [차트명]
구조는 대강 이런식인가보다. Chart.yaml
, values.yaml
, templates/deployment.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
의 containers:
속성을 찾아가서 아래와 같이 수정하면 된다.
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
로 가서 각종 설정을 해주자.
# 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 dep up
mysql-9.16.1.tgz
가 다운됐다! 이제 만든 차트로 설치만 하면 됨
helm install kube-test .
하지만 안됨! 왜냐
원인은 아까 실습때 이미 같은 이름(kube-test-mysql
)으로 릴리즈명을 해두어서 충돌이 난 것. helm uninstall [릴리즈명]
으로 삭제할 수 있었다.
helm uninstall kube-test-mysql
helm ls
다시 해보니 잘됨!
이제 잘떴나 k9s로 확인해봤더니 CrashLoopBackOff
가 떠있었다. 접속도 안되고.. 뭘까?
로그 확인으로 원인을 찾을 수 있었다. 설정대로 계정을 새로 만드는 건데, 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 후 다시 설치해보자.
다시 설치하고 해보니 또 안됨
이번엔 학습메모 2에 나와있는 트러블 슈팅과 같은 pvc 미삭제 문제같아 동일하게 확인해보고 삭제를 해줌. helm uninstall로도 pvc(persistent volume claim)는 삭제되지 않는다고 한다.
kubectl get pvc
kubectl delete pvc [pvc명]
helm uninstall하고 저거까지 지운 다음에 다시 띄워봤다.
왜 kube-test-helm-chart 서비스는 펜?딩?인?걸?까?
비밀은 지난 과제때 친히 써놓고도 까먹었던 "minikube로 서비스를 실행해줘야 한다" 부분.
kubectl get services
minikube service kube-test-kube-test-helm-chart
와
와 감격
진짜 감격