개요
Mattermost는 Slack을 대체할 수 있는 오픈 소스로 자체 인프라에 직접 호스팅할 수 있는 메시징 플랫폼입니다.
폐쇄망 환경에서도 운영이 가능해 보안이 중요한 조직에서 많이 채택하고 있습니다.
이번 포스팅에서는 Helm 차트를 사용해 Kubernetes에 Mattermost를 배포하는 방법을 알아보려고 합니다.
Mattermost 아키텍처
Mattermost의 애플리케이션 아키텍처는 다음과 같습니다.

이 중 핵심이 되는 Mattermost Server, Database, File Storage에 대해 알아봅시다.
Mattermost Server
애플리케이션의 핵심으로, Go 언어로 작성된 단일 바이너리입니다.
- RESTful API를 통해 클라이언트 요청을 처리합니다.
- WebSocket을 통해 실시간 메시지 및 알림을 전달합니다.
- 인증, 알림, 데이터 관리 등의 서비스를 제공합니다.
Database
메시지, 사용자 정보, 설정 등 모든 영구 데이터를 저장합니다.
- PostgreSQL을 기본으로 사용합니다.
- 고가용성을 위해 복제(Replication) 및 클러스터링 구성이 가능합니다.
File Storage
사용자가 업로드한 파일, 이미지, 첨부파일을 저장합니다.
- Local Storage, NAS, Amazon S3, MinIO 등을 지원합니다.
- 프로덕션 환경에서는 S3 또는 MinIO 사용을 권장합니다.
지금부터 Database는 PostgreSQL을, File Storage는 MinIO를 사용해서 k8s cluster 위에 Mattermost를 구축해보겠습니다.
k8s Operator pattern
Mattermost는 Operator 방식으로 배포할 것이기 때문에 k8s Operator pattern에 대해 알아보겠습니다.
operator는 CRD(사용자 정의 리소스)를 사용하여 애플리케이션을 관리하는 kubernetes extension입니다.
operator는 배포, 설정, 백업, 장애 대응과 같은 운영 지식을 코드에 내장하여 자동으로 처리할 수 있게 도와줍니다.
operator를 통해 CRD를 정의하고 CR을 생성해야 실제 서비스를 배포할 수 있습니다.
Mattermost-operator Helm-Charts
Mattermost를 k8s cluster 위에 구축하기 위해 Mattermost-operator라는 Helm-Charts를 사용해보겠습니다.
https://artifacthub.io/packages/helm/mattermost/mattermost-operator
mattermost-operator 1.0.4 · mattermost/mattermost
A Helm chart for Mattermost Operator
artifacthub.io
Mattermost helm-charts는 v1.0.0 이상부터 MinIO Operator와 MySQL Operator 옵션이 제거되었습니다.
현 시점(2025-11-27) 기준 공식 문서는 Database 배포를 위해 CloudNativePG Operator를, File Storage 배포를 위해 MinIO Operator를 추천하고 있습니다.
배포 순서는 다음과 같이 진행하겠습니다.
| 순서 | 구성 요소 | 배포 방식 | 설명 |
| 1 | NGINX Ingress Controller | Helm | 트래픽 라우팅 |
| 2 | CloudNativePG Operator | Helm | PostgreSQL 관리 |
| 3 | PostgreSQL Cluster | CRD | DB 인스턴스 생성 |
| 4 | MinIO Operator | Helm | Object Storage 관리 |
| 5 | MinIO Tenant | CRD | 스토리지 인스턴스 생성 |
| 6 | Mattermost Operator | Helm | Mattermost 관리 |
| 7 | Database/Filestore Secret | YAML | 인증정보 |
| 8 | Mattermost | CRD | 애플리케이션 배포 |
배포 과정
먼저 Nginx Ingress Controller를 배포해보겠습니다.
보통 ingress controller는 여러 서비스들이 함께 사용하는 경우가 많아 Mattermost와는 분리해서 배포하겠습니다.
# ingress-nginx/ingress-nginx.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ingress-nginx
namespace: argocd
spec:
project: default
sources:
- repoURL: https://kubernetes.github.io/ingress-nginx
chart: ingress-nginx
targetRevision: 4.14.0
helm:
valueFiles:
- $values/ingress-nginx/ingress-nginx-values.yaml
- repoURL: https://github.com/ezcolin2/argocd-test.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: ingress-nginx
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# ingress-nginx/values.yaml
controller:
replicaCount: 1
이제 Mattermost 사용을 위한 여러 구성 요소를 배포해야 하기 때문에 ArgoCD의 App of Apps 패턴을 사용해보겠습니다.
ArgoCD를 사용하지 않으신다면 application 생성은 제외하고 배포하시면 됩니다.
디렉토리 구조는 다음과 같이 정의하겠습니다.
mattermost/
├── app-of-apps.yaml
├── apps
│ ├── cloudnativepg.yaml
│ ├── mattermost.yaml
│ ├── minio.yaml
│ └── resources.yaml
├── cloudnativepg
│ ├── Chart.yaml
│ ├── templates
│ │ └── postgres-cluster.yaml
│ └── values.yaml
├── mattermost
│ ├── Chart.yaml
│ ├── templates
│ │ ├── mattermost-db-secret.yaml
│ │ ├── mattermost-filestore-secret.yaml
│ │ └── mattermost.yaml
│ └── values.yaml
├── minio
│ ├── Chart.yaml
│ ├── templates
│ │ ├── minio-secret.yaml
│ │ └── minio-tenant.yaml
│ └── values.yaml
└── resources
└── namespace.yaml
| 디렉토리 | 설명 |
| apps/ | ArgoCD Application |
| cloudnativepg-operator/ | CloudNativePG Helm Chart |
| minio-operator/ | MinIO Operator Helm Chart |
| mattermost-operator/ | Mattermost Operator Helm Chart |
| mattermost-operator/ | CRD, Secret 등 직접 적용할 YAML |
namespace부터 정의하겠습니다.
# resources/namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
---
apiVersion: v1
kind: Namespace
metadata:
name: cnpg-system
---
apiVersion: v1
kind: Namespace
metadata:
name: minio-operator
---
apiVersion: v1
kind: Namespace
metadata:
name: mattermost-operator
---
apiVersion: v1
kind: Namespace
metadata:
name: mattermost
이제 application 하나씩 YAML 파일을 작성하겠습니다.
CloudNativePG
CloudNativePG operator 사용을 위한 helm charts입니다.
# cloudnativepg/Chart.yaml
apiVersion: v2
name: cloudnativepg
description: CloudNativePG Operator with PostgreSQL Cluster
type: application
version: 0.1.0
appVersion: "1.25.0"
dependencies:
- name: cloudnative-pg
version: "0.26.1"
repository: "https://cloudnative-pg.github.io/charts"
override 할 values.yaml 파일입니다. 테스트를 위해 replica를 1로 설정했습니다.
# cloudnativepg/values.yaml
cloudnative-pg:
replicaCount: 1
CloudNativePG Operator를 통해 CRD를 생성했으면 Cluster CR을 통해 postgres를 배포합니다.
# cloudnativevpg/templates/postgres-cluster.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: mattermost-postgres
namespace: mattermost
spec:
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
bootstrap:
initdb:
database: mattermost
owner: mattermost
postgresql:
parameters:
max_connections: "200"
shared_buffers: "256MB"
storage:
size: 5Gi
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
monitoring:
enablePodMonitor: false
MinIO
MinIO operator 사용을 위한 helm charts입니다.
# mattermost/minio/Chart.yaml
apiVersion: v2
name: minio
description: MinIO Operator with Tenant
type: application
version: 0.1.0
appVersion: "7.1.1"
dependencies:
- name: operator
version: "7.1.1"
repository: "https://operator.min.io/"
override 할 values.yaml 파일입니다. 테스트를 위해 replica를 1로 설정했습니다.
# mattermost/minio/values.yaml
operator:
replicaCount: 1
다음은 admin 계정 정보 설정을 위한 secret입니다.
# minio/templates/
apiVersion: v1
kind: Secret
metadata:
name: minio-env-configuration
namespace: mattermost
type: Opaque
stringData:
config.env: |
export MINIO_ROOT_USER="minio"
export MINIO_ROOT_PASSWORD="minio123"
export MINIO_BROWSER="on"
MinIO Operator를 통해 CRD를 생성했으면 Taint CR을 통해 MinIO를 배포합니다. 위에서 작성한 secret을 연결해야 합니다.
# minio/templates/minio-tenant.yaml
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
name: minio
namespace: mattermost
labels:
app: minio
spec:
image: quay.io/minio/minio:RELEASE.2024-10-02T17-50-41Z
imagePullPolicy: IfNotPresent
pools:
- name: pool-0
servers: 1
volumesPerServer: 1
volumeClaimTemplate:
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# TLS 비활성화 (테스트용)
requestAutoCert: false
configuration:
name: minio-env-configuration
buckets:
- name: mattermost
exposeServices:
minio: true
console: true
Mattermost
Mattermost operator 배포를 위한 helm charts입니다.
# mattermost/Chart.yaml
apiVersion: v2
name: mattermost
description: Mattermost Operator with Mattermost Instance
type: application
version: 0.1.0
appVersion: "10.0.0"
dependencies:
- name: mattermost-operator
version: "1.0.4"
repository: "https://helm.mattermost.com"
override 할 values.yaml 파일입니다. 테스트를 위해 replica를 1로 설정했습니다.
# mattermost/Chart.yaml
mattermost-operator:
replicaCount: 1
image:
repository: mattermost/mattermost-operator
tag: v1.24.0
pullPolicy: IfNotPresent
다음은 mattermost에 연결하기 위한 postegres와 minio의 설정 파일입니다.
먼저 postgres 연결을 위한 secret입니다.
# mattermost/templates/mattermost-db-secret.yaml
# 주의: 배포 후 CloudNativePG가 생성한 password로 업데이트 필요
# kubectl get secret mattermost-postgres-app -n mattermost -o jsonpath='{.data.password}' | base64 -d
apiVersion: v1
kind: Secret
metadata:
name: mattermost-db-secret
namespace: mattermost
type: Opaque
stringData:
DB_CONNECTION_STRING: "postgres://mattermost:<비밀번호>@mattermost-postgres-rw.mattermost.svc.cluster.local:5432/mattermost?sslmode=disable&connect_timeout=10"
DB_CONNECTION_CHECK_URL: "postgres://mattermost:<비밀번호>@mattermost-postgres-rw.mattermost.svc.cluster.local:5432/mattermost?sslmode=disable&connect_timeout=10"
cloud native pg는 비밀번호를 자동 생성하기 때문에 cloud natvie pg를 배포하고 생성된 password를 이 secret 파일에 입력해줘야 합니다.
minio 연결을 위한 secret입니다.
# mattermost/templates/mattermost-filestore-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mattermost-filestore-secret
namespace: mattermost
type: Opaque
stringData:
accesskey: bWluaW8=
secretkey: bWluaW8xMjM=
위에서 minio를 배포할 때 작성한 계정 정보와 비밀번호를 사용할 수 있는데 string을 그냥 입력하면 안 되고 base 64 encoding한 값을 입력해야 합니다.
Mattermost operator를 통해 CRD를 생성했으면 Mattermost CR을 통해 Mattermost를 배포합니다.
# mattermost/templates/mattermost.yaml
apiVersion: installation.mattermost.com/v1beta1
kind: Mattermost
metadata:
name: mattermost
namespace: mattermost
spec:
version: "10.0.0"
size: "100users"
replicas: 1
ingress:
enabled: false
host: "mattermost.local"
ingressClass: "nginx"
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
database:
external:
secret: mattermost-db-secret
fileStore:
external:
url: "minio-hl.mattermost.svc.cluster.local:9000"
bucket: "mattermost"
secret: mattermost-filestore-secret
mattermostEnv:
- name: MM_FILESETTINGS_AMAZONS3SSL
value: "false"
- name: MM_FILESETTINGS_AMAZONS3PATHSTYLE
value: "true"
- name: MM_LOGSETTINGS_CONSOLELEVEL
value: "DEBUG"
- name: MM_LOGSETTINGS_ENABLECONSOLE
value: "true"
namespace
namespace 파일입니다.
# resources/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: mattermost
labels:
app.kubernetes.io/name: mattermost
application
저는 ArgoCD를 통해 배포할 예정이기 때문에 application을 정의하겠습니다. git repo url은 자신의 repo url을 입력하면 됩니다.
CloudNativePG, Mattermost, Minio, resources 순서입니다.
# apps/cloudnativepg.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mattermost-cloudnativepg
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "0"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: <git repo url>
targetRevision: main
path: mattermost/cloudnativepg
destination:
server: https://kubernetes.default.svc
namespace: cnpg-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
# apps/mattermost.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mattermost-app
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "2"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: <git repo url>
targetRevision: main
path: mattermost/mattermost
destination:
server: https://kubernetes.default.svc
namespace: mattermost-operator
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
# apps/minio.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mattermost-minio
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: <git repo url>
targetRevision: main
path: mattermost/minio
destination:
server: https://kubernetes.default.svc
namespace: minio-operator
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
# apps/resources.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mattermost-resources
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-1"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: <git repo url>
targetRevision: main
path: mattermost/resources
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
마지막으로 app of apps 설정입니다.
# app-of-apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mattermost-app-of-apps
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: <git repo url>
targetRevision: main
path: mattermost/apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
위 파일들을 모두 git에 올리고 app-of-apps.yaml 파일만 k8s에 적용하면 됩니다.
kubectl apply -f app-of-apps.yaml
app-of-apps를 적용하면 아래와 같이 mattermost-app-of-apps 아래로 설정했던 application들을 확인할 수 있습니다.

그리고 이 application들은 applications에서 확인하실 수 있습니다.
