[쿠버네티스] 어플리케이션 커스터마이징

황서희·2023년 2월 20일
0
post-thumbnail

어플리케이션 커스터마이징

image (CMD Entrypoint 변경)

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod-arg
spec:
  containers:
  - name: myapp
    image: ghcr.io/c1t1d0s7/go-myweb:alpine
    args: #CMD 교체, myweb -port=8088으로 실행한다.
    - -port=8088
    ports:
    - containerPort: 8088
      protocol: TCP

env (환경 변수)

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod-env
spec:
  containers:
  - image: ghcr.io/c1t1d0s7/go-myweb:alpine
    name: myapp
    env: #환경변수를 할당할 수 있다.
    - name: MESSAGE
      value: "Customized Hello World!"
    ports:
    - containerPort: 8080
      protocol: TCP

컨피그맵 ConfigMap (cm)

참조

키-밸류 형식의 딕셔너리를 설정할 수 있다. 이것을 평문으로 저장한다. 쉘의 환경 변수, 혹은 볼륨으로 참조할 수 있다. 일반적으로는 볼륨으로 참조한다. 컨피그맵에는 spec이 없고 대신 map형식의 data가 존재한다. 컨피그맵은 쿠버네티스 설치 시에 하나가 생성되어 있다.

컨피그맵은 키-밸류를 저장시켜 환경변수를 제공하거나 설정 파일을 변경하는 데에 쓰인다. 주로 쓰이는 곳은 설정 파일을 변경할 때이다. 컨피그맵의 immutable 값을 true로 설정하면 컨피그맵을 수정할 수 없다.

kubectl describe cm kube-root-ca.cr #쿠버네티스 설치시 생성된 컨피그맵

Data
====
ca.crt:  #key
-----BEGIN CERTIFICATE-----    #value
kubectl create cm my-config1 --from-literal=key1=value1 #데이터 1개

kubectl create cm my-config2 --from-literal=key11=value11 --from literal=key22=value22 #데이터 2개

echo value3 > key3
kubectl create cm my-config3 --from-file=key3 #파일로 생성. 파일명이 key, 내용이 value

kubectl create cm my-config4 --from-file=key-tree=key3 #키 이름 지정가능
#key3 안에 있는 value3가 value가 된다.
cat my-config4.yaml
apiVersion: v1
kind: ConfigMap
meatadata:
	name: my-config4
data:
	key4: value4 

컨피그맵을 설정 파일로 사용할 때는 아래의 방식을 주로 사용한다.

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts: #볼륨을 마운트한다
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    configmap: #컨피그맵
      name: myconfigmap #파일 이름
cat myapp-pod-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata: 
	name: myapp-message
data:
	message: hello world
---
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod-cm
spec:
  containers:
  - image: ghcr.io/c1t1d0s7/go-myweb:alpine
    name: myapp
    env:
    - name: MESSAGE #변수 이름
      valueFrom:
        configMapKeyRef: #키를 참조
          name: myapp-message #컨피그맵 이름
          key: message #키 이름
    args:  
    - $(MESSAGE)
    ports:
    - containerPort: 8080
      protocol: TCP
cat nginx-pod-compress.yaml
cat nginx-cm-compress.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-compress
spec:
  containers:
  - image: nginx
    name: nginx-compress
    volumeMounts:
    - name: nginx-compress-config
      mountPath: /etc/nginx/conf.d/ #nginx의 설정 파일에
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: nginx-compress-config #컨피그맵으로 구성된 볼륨
    configMap: #컨피그맵을 참조해 볼륨으로 마운팅한다
      name: nginx-gzip-config #컨피그맵의 이름
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-gzip-config
data:
  nginx-gzip.conf: | #키명 = 파일명, |은 밸류가 멀티라인이라는 뜻
    server {
    listen              80;
    server_name         myapp.example.com;
    gzip on; #압축 기능을 키게 바꾼다
    gzip_types text/plain application/xml; #압축 타입 지정
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
      }
    }
curl localhost:8080 -I #615바이트로, 압축되지 않았음을 확인할 수 있다.
curl -H "Accept-Encoding: gzip" localhost:8080 -I #압축 요청
curl -H "Accept-Encoding: gzip" localhost:8080 #압축되어서 안 보인다.

시크릿 Secrets

참조

시크릿은 암호, 토큰 또는 키와 같은 소량의 중요한 데이터를 포함하는 오브젝트이다. 이를 사용하지 않으면 중요한 정보가 파드 명세나 컨테이너 이미지에 포함될 수 있다. 시크릿을 사용한다는 것은 사용자의 기밀 데이터를 애플리케이션 코드에 넣을 필요가 없음을 뜻한다.

시크릿은 컨피그맵과 거의 비슷하다. 키-밸류 형식의 딕셔너리를 설정할 수 있고, 암호하여 저장한다. spec 대신 data가 존재하는 것은 컨피그맵과 같지만, 컨피그맵과는 다르게 type이 있다. type은 저장하려고 하는 키-밸류 데이터의 타입을 지정한다.

시크릿의 타입은 다음과 같다.

시크릿은 하나의 데이터 당 최대 1.1MiB로 제한되어 있다. 시크릿은 파일로 저장하면 문제가 되기 때문에 해당되는 파드의 kubelet 메모리에 올려 놓는다. 따라서, 용량이 커지면 kubelet의 퍼포먼스에 문제가 생길 수 있으므로 제한이 걸려 있다.

시크릿을 사용하는 경우는 예를 들어, 현업에서는 퍼블릭 이미지를 거의 사용하지 않는다. 믿을 수 없기 때문이다. 프라이빗 이미지를 사용하기 위해서는 인증이 필요하다. 이 때 인증 정보를 넣기 위해 사용하는 것이 시크릿의 타입이다.

시크릿은 파일이 만들어질 때 값이 자동으로 인코딩되어 들어간다. 이를 주석처리하고 일반 값을 yaml파일에 넣고 다시 생성하면 base64 데이터가 아니라며 만들어지지 않는다. 따라서, secret을 직접 yaml파일으로 작성할 땐 인코딩해서 생성해야 한다.

kubectl create secret generic my-secret --from-literal=key1=value1 #value가 안 나옴

kubectl get secret my-secret -o yaml #암호화된 것이 아니라 인코딩된 것

echo dmFsdWUx | base64 -d
value1

시크릿은 암호화하기 위해 만들어진 것이 맞지만, 실제로 저장될 때는 암호화되지 않는다. 따라서 실제로 시크릿은 키 관리 매니저라고 하는 별도의 소프트웨어와 결합시켜야 암호화된다. 쿠버네티스 내에서 하는 것은 단순 인코딩-디코딩일 뿐 실제 암호화되는 것이 아니다.

도커 레지스트리를 이용하기 위한 명령어는 다음과 같다. docker-password에는 token을 넣는다. imagePullSecrets 를 사용하여 도커(또는 다른 컨테이너) 이미지 레지스트리 비밀번호가 포함된 시크릿을 kubelet에 전달할 수 있다. kubelet은 이 정보를 사용해서 파드를 대신하여 프라이빗 이미지를 가져온다.

kubectl create secret docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-file=[key=]source] [--dry-run=server|client|none] [options] 

tls

파드를 https로 통신하도록 설정하려면 다음과 같다.

cat conf/nginx-tls.conf

server {
    listen              80;
    listen		443 ssl; #포트
    server_name         myapp.example.com;
    ssl_certificate	/etc/nginx/ssl/tls.crt; #인증서
    ssl_certificate_key	/etc/nginx/ssl/tls.key; #key파일
    ssl_protocols	TLSv1.2 TLSv1.3; #프로토콜
    ssl_ciphers		HIGH:!aNULL:!MD5; #암호화 방식은 높게, aNULL이나 MD5는 제외
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
    }
}

해당 컨피그 파일의 내용은 configmap으로 제공한다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-tls-config
data:
  nginx-tls.conf: |
    server {
    listen              80;
    listen		          443 ssl;
    server_name         myapp.example.com;
    ssl_certificate	    /etc/nginx/ssl/tls.crt;
    ssl_certificate_key	/etc/nginx/ssl/tls.key;
    ssl_protocols	      TLSv1.2 TLSv1.3;
    ssl_ciphers		      HIGH:!aNULL:!MD5;
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
      }
    }

OpenSSL를 인증하는 파일이다. 여기서 사용하는 인증서는 자체서명 인증서이다.

apiVersion: v1
kind: Secret
metadata:
  name: nginx-tls-secret
type: kubernetes.io/tls #tls 타입은 data가 정해져 있다.
data:
  tls.crt: # base64 nginx-tls/nginx-tls.crt -w 0 인증서 인코딩
  tls.key: # base64 nginx-tls/nginx-tls.key -w 0
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-https
  labels:
  	app: nginx-https
spec:
  containers:
  - image: nginx
    name: nginx-https
    volumeMounts:
    - name: nginx-tls-config
      mountPath: /etc/nginx/conf.d #설정 파일 마운트
    - name: https-cert
      mountPath: /etc/nginx/ssl #인증서 마운트, config의 ssl_certificate에 저장한 경로와 매칭
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
    - containerPort: 443
      protocol: TCP
  volumes: #설정 파일과 인증서 모두 볼륨으로 마운트한다.
  - name: nginx-tls-config #설정 파일
    configMap:
      name: nginx-tls-config
  - name: https-cert #인증서
    secret:
      secretName: nginx-tls-secret
openssl genrsa -out nginx-tls/nginx-tls.key 2048 #key파일 생성
openssl req -new -x509 -key nginx-tls/nginx-tls.key \
-out nginx-tls/nginx-tls.crt -days 365 -subj /CN=myapp.example.com #자체서명 인증서 생성
base64 nginx-tls/nginx-tls.crt -w 0 #한줄로 인코딩
base64 nginx-tls/nginx-tls.key -w 0 #인코딩하여 둘다 위 yaml파일의 data에 복사 붙어넣기 해준다.
kubectl create -f nginx-cm-https.yaml -f nginx-secret-https.yaml -f nginx-pod-https.yaml
apiVersion: v1
type: Service
metadata:
	name: nginx-service
spec:
	type: LoadBalancer
    ports:
    - name: http
      ports: 80
      targetPort: 80
    - name: https
      port: 443
      targetPort: 443
    selector:
    	app: nginx-https
kubectl create -f nginx-svc-https.yaml
http://192.168.56.200
https://192.168.56.200 #둘다 접속 가능
kubectl port-forward nginx-pod-https 8443:443 #서비스를 생성하지 않고 port-forward를 사용
curl -k https://localhost:8443 #-k 옵션을 사용하지 않으면 https는 접속 불가

1024 이하의 포트는 개방하기 위해선 관리자 권한이 필요하므로 1024 이상의 포트를 사용하는 것이 편하다.

tls_termination_proxy

tls 종료 프록시라고 하는 기술이다. 매우 복잡하지만, 쿠버네티스를 이용하면 매우 간편하게 구현할 수 있다.

클라이언트-인터넷망-서비스-파드로 연결된 구조에서, 클라이언트부터 파드까지 end-to-end로 tttps 암호화가 걸린다. 문제는 서버 입장에서 end-to-end 암호화는 전혀 안전하지 않다. https는 클라이언트가 서버를 인증하는 과정인데, 서버 입장에서는 클라이언트를 확인하지 못한다(인증을 할 수 없다). 따라서 서버 입장에서는 클라이언트의 요청을 수행할 수 밖에 없는데, 악성 요청도 그대로 수행하게 된다.

이를 방지하기 위해서 쓰이는 것이 WAF지만, 결국 암호화 된 데이터를 중간에서 풀어볼 수 없어 공격인지 아닌지 확인할 수 없다. 이를 위해서 사용하는 것이 tls termination proxy이다.

해당 사진을 예로 들어보면, 클라이언트와 서버 사이에 프록시 서버 (쿠버네티스의 경우에는 인그레스)가 있다. 프라이빗 네트워크 안에서는 사실 접근 제어만 잘 된다는 가정 하에 https로 통신할 필요가 없다.프록시 서버에서 tls 종료 프록시를 통해 https를 종료시키고, 내부에서는 http를 종료한 상태에서 통신한다. 내부적으로 https 통신을 한다고 해도, tls 통신 프록시를 통해 한 번 종료시키고 다시 https로 통신한다.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp-rs
  template:
    metadata:
      labels:
        app: myapp-rs
    spec:
      containers:
      - name: myapp
        image: ghcr.io/c1t1d0s7/go-myweb:alpine
        ports:
        - containerPort: 8080

파드가 직접 시크릿을 가지고 작동하지 않기 때문에 시크릿을 설정하지 않는다. 시크릿 파일은 위와 같다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ing-tls-term
spec:
  tls:
  # - hosts:
    # - myapp.example.com #실습할 때는 없애는 것이 편함
    secretName: myapp-tls-secret #이곳의 인증서와 키를 가져온다.
  rules:
  # - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend: 
          service:
            name: myapp-svc-np
            port: 
              number: 80

파드 자체의 nginx에서 설정 파일을 제공하거나 키를 nginx에 저장할 필요 없이 인그레스에서만 갖고 있으면 된다. 암호화 통신은 클라이언트와 인그레스 사이에서만 하기 때문에 파드의 nginx는 암호를 위한 설정과 인증키가 필요없다. 따라서 사용자가 훨씬 유연하게 사용할 수 있다.

#인증서 파일은 위의 것과 같이 설정하면 된다.

kubectl creat -f .
kubrctl describe ing myapp-ing-tls-term #tls 확인
curl -k https://192.168.56.21
curl -k https://192.168.56.22
curl -k https://192.168.56.23 #접속 확인, host pc에서도 IP주소로 접속 가능하다.
profile
다 아는 건 아니어도 바라는 대로

0개의 댓글