[WSL & K8S] 워드프레스 블로그 구축 하기

개발 블로그 운영이 필요하게 되어, 클라우드와 홈서버 등을 검토 하는 도 중, 클라우드의 VPS 를 검토 하였으나, 기능과 비용 면에서 어려움이 있어, 홈서버를 구성하고자 하였습니다.

01. Ubuntu 가 아닌 WSL 선택의 이유

홈서버를 구성할 때, 지금은 도커와 같은 컨테이너 시스템을 선호하고 있습니다. 기존 환경보다 장점은, OS 에 직접적으로 설치하지 않아 종속성(프로그램 간 충돌)으로 인한 문제를 제거할 수 있고, 다른 사용자가 구성한 모범 케이스를 쉽게 구성할 수 있다는 장점이 있습니다.

또한, 기존에 1개 서버에 구성하는 경우, 서버에 있는 모든 기능들을 침투할 수 있지만, 컨테이너는 격리된 환경을 제공하여, 침투 하는 경우, 해당 컨테이너 내에서의 기능만 악용할 수 있는 장점등이 있습니다.

하지만, Windows 환경에서는 컨테이너가 최적화된 환경이 아니기에, 기존의 OS 커널을 원활하게 이용하지 못한다는 단점이 있었습니다. 이에 따라 장기적인 사용 입장에서는 Linux OS 를 가져가는 것이 맞지만, 최근 윈도우의 Hyper-v 그리고 Hyper-v 를 통한 WSL 의 발전으로 인해 속도 저하가 거의 없는 Linux 환경을 실행할 수 있게 되었습니다.

이에 따라, 직접적인 Ubuntu 가 아닌, WSL 을 선택하게 되었습니다. (또한, 가정집인 만큼 전력 사용량을 고려해야 하는데, 우분투의 경우 전력 효율화 면에서 윈도우 보다 최적화가 부족하다고 알려져 있습니다. )

[참고] WSL Ubuntu vs Native Ubuntu ( 직접 설치 ) 실제로, Native(직접 설치) 된 Linux와의 벤치마크 비교시, CPU만 놓고 보는 벤치마크에서는 직접 설치하는 방식과의 차이가 거의 없어질 만큼 발전되었습니다.

CPU Bechamrk

[출처] https://www.phoronix.com/review/windows11-wsl2-zen4/2

02. 시스템 구성 환경

01) OS 시스템 구성

WSL 구조

우리는 빨간 환경을 구성할 예정입니다.

  • 도커가 아닌 쿠버네티스를 사용하는 이유 단순히 워드프레스만 구성하려는 것이 아닌, 여러 컨테이너(모니터링, 여러 웹서버) 등을 운영하려고 하기에 컨테이너 오케스트레이션 도구인 쿠버네티스를 선택 하였습니다.
  • Micro k8s 를 선택한 이유 일반적으로 쿠버네티스 환경은 물리적으로 분리된 서버들 여러대로 구성을 해야 합니다. 이를 통해 HA 를 조금 더 안정적으로 운영할 수 있습니다. 하지만, 지금 환경은 전력을 최소화 하는 것이 목적인 만큼 실제 장비를 늘리지 않아야 하는 것과, WSL을 늘리지는 않고자 하였습니다. 그렇기에 1개 서버로 전체를 운영하는 환경이 필요했고, Microk8s , minikube, k3s를 비교한 결과 현재 / 향후의 추가할 기능들을 고려해 Microk8s 를 선택 하였습니다.
microk8s 구성

[출처] https://microk8s.io/compare

02) 웹서버 구성

Wordpress 구조

[CloudFlare]

DDOS 방지 및 WAF 구성을 위해 구성 하였습니다. 부가적으로 무료 CDN 을 통해 해외에서 접속시 속도 향상 적인 부분도 있으며, SSL 인증을 자체적으로 DNS 단에서 해주기 때문에, Cert-manager 등의 구성이 필요없다는 장점도 있습니다.

[Ingeress]


Ingress 를 설정하는 것은, 하나의 서버에서 여러개의 웹서버를 운영하는 경우 유용 합니다. a.com / b.com / c.com 을 운영하는 경우, 서버는 1대 임으로 향하는 IP 는 항상 x.x.x.x 인데, 이때 IP 가 동일하더라도 URL 별로 접근하는 서버를 분리할수 있습니다. 또한, 외부에 웹서버가 직접적으로 노출되지 않는 다는 장점도 있습니다.

03) 외에 쿠버네티스 관련 도구

  • 모니터링 도구
    • Prometheus Stack

03. WSL 환경 구성하기

01) 사전 설정

(01) 제어판 접근

[윈도우 + R] → control

(02) 프로그램 → widnows 기능 켜기/끄기

(03) Hyper-v , Linux용 Windows 하위 시스템 선택 → 확인

(04) 재부팅

02) WSL 설정

(01) WSL 설치

https://apps.microsoft.com/store/detail/windows-subsystem-for-linux/9P9TQF7MRM4R

[Option]

(02) Bridge 네트워크로 구성

기본적으로 WSL은 별도의 내부 네트워크 환경에서 구성 됩니다. 이 상태는 윈도우를 통해서만 접근이 가능한 상태라, 외부에 노출 시키기 위해서는 윈도우 방화벽에 별도로 세팅하고 포트 포워딩을 통한 작업이 필요하게 됩니다.

저는 이런 작업을 없애고자 처음부터 WSL이 공유기에 직접 붙을 수 있도록 구성 하고자 세팅 하였습니다.

[1] hyper-v 관리자 접근

[2] 가상 스위치 관리자 (작업탭 하단 3시 방향 )

[3] 새 가상 네트워크 스위치 → 가상 스위치 만들기

  • 유형은 외부로 설정해주세요.

[4] 가상 스위치 명을 지정한 후 / 네트워크 어댑터 카드를 선택 합니다.

  • 이름 : wsl_Public (원하는 이름)
  • 네트워크 카드 잘 선택 할것 (현재 네트워크에 속성을 보면 어떤 랜카드 인지 알수 있습니다. )

[5] .wslconfig 파일 만들기

생성 해야하는 폴더 : C:\Users\[사용자 이름]\.wslconfig

파일 내용

[WSL2]
kernelCommandLine=ipv6.disable=1
networkingMode = bridged
vmSwitch = wsl_Public

참고 사항

만약, 스위치 명이 wsl_Public 이 아닌 다른 이름으로 지정 하셨다면 내용을 바꿔 주세요. ipv6.disable=1 을 설정하는 이유는 외부에서 WSL 내부 리눅스나 대상들을 접근할 때 xxx.xxx.xxx.xxx(IPv4) 로 접근 하였으나, ipv6 주소로 변환하여 접근하려고 하는 부분이 있습니다.

만약, 여러분의 스위치나 랜카드가 IPv6 설정이 되어 있지 않은 경우 내부 네트워크가 정상적으로 동작하지 않기에 IPv6 로 통신하고자하는 경우가 아니라면 위의 방식을 설정하는 것이 좋습니다.

03) Ubuntu 설치

[Powershell]

wsl --install Ubuntu 

→ 이후 계정 비밀번호 설정 하면 완료 됩니다.

초기에 설치되면 바로 실행 되며, 추후에 우분투 접근시 아래 명령어로 접속해 주세요.

[참고로, wsl 을 쳤을때 접근 되는 대상은 Default 로 설정된 대상으로 여러 WSL 이 있는 경우 별도 설정이 필요합니다.]

wsl

04. Ubuntu 설정

01) 시스템 설정 변경

(01) 우분투 업데이트 서버 변경

WSL 의 기본 우분투 업데이트 서버는 미국 우분투 서버로 설정 됩니다. 최근 국내에서 접속하려고 하면 접속을 실패하는 경우도 많고 다운로드에 많은 시간이 소요됨으로, 국내 서버로 변경하는 것이 좋습니다.

sudo sed -i 's/archive.ubuntu/mirror.kakao/' /etc/apt/sources.list
sudo apt-get update

(02) SSH 설정 (필요한 경우)

sudo apt-get openssh-server

02) Micro K8s 설치 (쿠버네티스 설치)

[참고 사이트] https://microk8s.io/docs/getting-started

sudo snap install microk8s --classic

05. 쿠버네티스 기본 설정

01) Kubectl 변경 설치

기본적으로 microk8s 는 microk8s.kubectl 을 통해서 명령할 수 있도록 하지만, 실제 기능은 kubectl 보다 부족한 편입니다. 이에 따라, 실제 kubectl 에서는 동작하는 명령이 정상적으로 동작하지 않는 경우가 있습니다. 그럼으로, kubectl 을 직접 설치해서 사용하시는 것을 추천 드립니다.

(01) Kubectl 설치

리눅스에 kubectl 설치 및 설정

OS 마다 다르기에 위의 URL 을 참고해 주세요.

(02) config 파일 가져오기

kubectl 을 사용하려면 접근하려는 서버의 microk8s 접속 정보가 필요합니다. 이를 가져오기 위한 명령어를 작성해 봅시다.

microk8s config > ~/.kube/config

(03) 명령 테스트

kubectl get pod 

(04) 쿠버네티스 명령어 자동완성 설정 및 단축 하기

https://kubernetes.io/docs/reference/kubectl/cheatsheet/

02) Ingress 설치 및 테스트

(01) nginx-Ingress 설치

https://microk8s.io/docs/addon-ingress

microk8s enable ingress

(02) 기본 테스트

curl localhost

[결과]

kerneltrek@nuci7:~$ curl localhost
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

→ 만약 timeout 이나 반응이 없다면, WSL 설정 부분 중 ipv6 비활성화 (.wslconfig) 가 되지 않은 경우에 주로 발생합니다.

(03) 정상 동작 테스트

https://kubernetes.io/docs/tasks/access-application-cluster/ingress-minikube/

[Test 용 Web 배포 ]

kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0
kubectl expose deployment web --type=ClusterIP --port=8080

Type은 LB , ClusterIP , NodePort 상관 없습니다. 개인적으로 Ingress 를 사용하신다면 ClusterIP 로 URL 을 통한 접근 외에 제한하는 것이 좋을 수 있습니다.

[Test 용 Ingress 설정 ]

kubectl apply -f <https://k8s.io/examples/service/networking/example-ingress.yaml>
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: hello-world.info
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 8080

→ host 정보가 URL 에 해당하는 부분 입니다. 추후 Ingress 설정시 여러분의 URL 로 변경해주세요.

[확인]

curl --resolve "hello-world.info:80:127.0.0.1" -i <http://hello-world.info>
HTTP/1.1 200 OK
Date: Wed, 26 Jul 2023 03:15:08 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 60
Connection: keep-alive

Hello, world!
Version: 1.0.0
Hostname: web-548f6458b5-9l9gk

[삭제]

kubectl delete deployment web
kubectl delete service web
k delete ingress example-ingress

06. WordPress 구성

01) Secret 설정 (비밀번호 / 계정 / DB 등)

(01) 설정 파일 생성

워드프레스에서 사용할 데이터 베이스의 대한 기본적인 정보를 설정 합니다.

  • [ ] 사이에 대상을 자신이 원하는 정보로 변경 합니다.
vim kustomization.yml
secretGenerator:
- name: mysql-password
  literals:
  - password=[mysql-password]
- name: mysql-user
  literals:
  - username=[mysql-user]
- name: mysql-user-password
  literals:
  - passworduser=[mysql-user-password]
- name: mysql-database
  literals:
  - database=[mysql-database]

(02) 반영

[명령어]

kubectl apply -k .

[결과]

$ kubectl apply -f ./                  

persistentvolume/mysql-pv created
persistentvolumeclaim/mysql-pv-claim created
persistentvolume/wordpress-pv created
persistentvolumeclaim/wordpress-pv-claim created

이 명령이 잘 먹히지 않는 다면 2가지를 확인하셔야 합니다.

  1. 파일명이 kustomization.yml 인지 ?
  2. microk8s.kubectl 을 사용하는지 ? ( microk8s.kubectl 은 이 명령이 kubectl 의 모든 명령을 수행하지는 못합니다. )

02) 데이터 베이스 / 워드 프레스 스토리지 생성

기본적으로 컨테이너로 구성한 서버는 컨테이너가 종료되면 가지고 있던 데이터 들도 다 사라지게 됩니다.

이를 방지하기 위해, 영구 적인 저장을 위한 설정을 합니다.

(01) DataBase 볼륨 생성

  • 기본 용량 : 50Gi [원하는 용량으로 변경하세요]
vim db-volume.yml
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  storageClassName: db-storage
  capacity:
    storage: 80Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/var/lib/mysql"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
spec:
  storageClassName: db-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 80Gi

(02) WordPress 생성

vim wp-volume.yml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: wordpress-pv
spec:
  storageClassName: wp-storage
  capacity: 
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/var/www"
---    
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wordpress-pv-claim
spec:
  storageClassName: wp-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi

(03) 생성 및 확인

[생성 명령어]

kubectl apply -f db-volume.yml
kubectl apply -f wp-volume.yml

[확인 명령어]

kubectl get pv,pvc

[결과 확인] → Status 가 bound 되어야 함

jay@JayLees-MacBook-Pro  ~/Documents/wp-setting/003.volumes   main  k get pv,pvc        
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS        REASON   AGE
persistentvolume/pvc-2e9bb24f-a2e3-42e9-96bd-8309cd9ceda6   20Gi       RWX            Delete           Bound    container-registry/registry-claim   microk8s-hostpath            161m
persistentvolume/mysql-pv                                   80Gi       RWO            Retain           Bound    default/mysql-pv-claim              db-storage                   36s
persistentvolume/wordpress-pv                               50Gi       RWO            Retain           Bound    default/wordpress-pv-claim          wp-storage                   36s

NAME                                       STATUS   VOLUME         CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/mysql-pv-claim       Bound    mysql-pv       80Gi       RWO            db-storage     36s
persistentvolumeclaim/wordpress-pv-claim   Bound    wordpress-pv   50Gi       RWO            wp-storage     36s

참고로, 다중 노드를 사용하는 경우 이 방식이 적합하지 않습니다.

[ 다중 노드를 사용하는 경우 SMB 를 통해서 파일을 저장할 서버에 공유 폴더를 생성 하고 해당 폴더를 지정하여 공유하는 것을 추천 드립니다. ]

03) mysql & 워드프레스 생성

(01) 시크릿 정보 확인하기

[명령어]

kubectl get secret
jay@JayLees-MacBook-Pro  ~/Documents/wp-setting/004.deployment   main  k get secrets                          
NAME                             TYPE     DATA   AGE
mysql-database-86m8k7bm58        Opaque   1      10m
mysql-password-f79kbg8g42        Opaque   1      10m
mysql-user-hhd5dbh6m7            Opaque   1      10m
mysql-user-password-97b57kg42k   Opaque   1      10m

우리는 DB 비밀번호나 여러 정보들을 이미 설정해 두었습니다. 해당 정보를 이용하기 위해, 시크릿의 대한 정보를 미리 확인 합니다.

(02) 데이터베이스 정보

아래 내용 중 name 부분은 본인의 secret 정보를 입력해 주세요

[참고 자료]

  - name: MYSQL_ROOT_PASSWORD
    valueFrom:
      secretKeyRef:
        name: **[본인의 시크릿 번호 입력]**
        key: password
vim mysql-deployment.yml
apiVersion: v1
kind: Service
metadata: 
  name: mysql-wp
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-wp
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
      - image: mysql:latest
        name: mysql
        env:
        - name: MYSQL_DATABASE
          valueFrom:
            secretKeyRef:
              name: mysql-database-86m8k7bm58
              key: database
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-password-f79kbg8g42
              key: password
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: mysql-user-hhd5dbh6m7
              key: username
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-user-password-97b57kg42k
              key: passworduser
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

(03) 워드프레스 디플로이먼트 정보

vim wordpress-deployment.yml
apiVersion: v1
kind: Service
metadata:
  name: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: web
  type: ClusterIP

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: web
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: web
    spec:
      containers:
      - image: wordpress
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: mysql-wp:3306
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-user-password-97b57kg42k
              key: passworduser
        - name: WORDPRESS_DB_USER
          valueFrom:
            secretKeyRef:
              name: mysql-user-hhd5dbh6m7
              key: username
        - name: WORDPRESS_DB_NAME
          valueFrom:
            secretKeyRef:
              name: mysql-database-86m8k7bm58
              key: database
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: persistent-storage
        persistentVolumeClaim:
          claimName: wordpress-pv-claim

(04) 배포 확인

kubectl apply -f mysql-deployment.yml
kubectl apply -f wordpress-deployment.yml

[확인]

kubectl get pod, svc
jay@JayLees-MacBook-Pro  ~/Documents/wp-setting/004.deployment   main  k get pod,svc         
NAME                             READY   STATUS    RESTARTS   AGE
pod/mysql-wp-7d5798b8c4-hnvp6    1/1     Running   0          9m43s
pod/wordpress-5bc7c448f6-4vrrf   1/1     Running   0          98s

NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   10.152.183.1     <none>        443/TCP    3h9m
service/mysql-wp     ClusterIP   10.152.183.191   <none>        3306/TCP   18m
service/wordpress    ClusterIP   10.152.183.122   <none>        80/TCP     98s

→ 만약 Status가 Running이 아닌 경우 dsecribe 로 확인 해주세요.

[체크]

kubectl describe pod [아닌 대상]

(아래 Case 는 secret 정보가 다른 경우 발생하는 에러 입니다. secret name 을 변경 하면 정상화 됩니다. )

kubectl describe pod mysql-wp-7d5798b8c4-hnvp6
Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  60s               default-scheduler  Successfully assigned default/mysql-wp-9b7ddf788-jnqjw to nuci7
  Normal   Pulled     36s               kubelet            Successfully pulled image "mysql:latest" in 23.940980225s (23.940986625s including waiting)
  Normal   Pulled     33s               kubelet            Successfully pulled image "mysql:latest" in 1.658573801s (1.658583301s including waiting)
  Normal   Pulled     18s               kubelet            Successfully pulled image "mysql:latest" in 1.717353538s (1.717363638s including waiting)
  Normal   Pulling    4s (x4 over 60s)  kubelet            Pulling image "mysql:latest"
  Normal   Pulled     2s                kubelet            Successfully pulled image "mysql:latest" in 1.657823986s (1.657868686s including waiting)
  Warning  Failed     2s (x4 over 36s)  kubelet            Error: secret "mysql-password" not found

04) ingress 설정

이제 워드프레스에 접근할 수 있는 ingress 를 만들어 봅시다.

제 샘플중 아래 기록된 부분만 변경하면 됩니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wp-ingress
  annotations:
    nnginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: www.jayslog.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wordpress
                port:
                  number: 80
kubectl get ingress
jay@JayLees-MacBook-Pro  ~/Documents/wp-setting/005.ingress   main  > kubectl get ingress         
NAME                CLASS    HOSTS             ADDRESS   PORTS   AGE
wordpress-ingress   public   www.jayslog.com             80      3s

[현재 보시는 사이트가 위의 방식으로 구성 하였습니다. ]

클라우드 플레어 설정과 다음 설정 사항은 다음 포스팅을 참고해 주세요.

Leave a Comment